You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
classmethod does not pass "type/owner" when invoking wrapped __get__ #86239
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=<Date2021-07-15.10:53:50.542>created_at=<Date2020-10-18.20:22:44.119>labels= ['interpreter-core', 'type-bug', '3.10', '3.11']
title='classmethod does not pass "type/owner" when invoking wrapped __get__'updated_at=<Date2021-07-15.13:42:15.372>user='https://github.com/eriknw'
The following is new to Python 3.9, and I consider the implementation incomplete. I have code that works for Python 3.8 and before, but not for Python 3.9:
"Class methods can now wrap other :term:`descriptors <descriptor>` such as :func:`property`."
As implemented, classmethod does not correctly wrap descriptors that mimic classmethod. Previously, __get__of wrapped objects wasn't invoked by classmethod, so it was safe to have an object with both call and get behave like a classmethod. Now, classmethod calling get first gives incorrect results.
Here is a minimal example:
from types import MethodType
class myclassmethod:
def __init__(self, func):
self.func = func
def __call__(self, cls):
return self.func(cls)
def __get__(self, instance, owner=None):
if owner is None:
owner = type(instance)
return MethodType(self, owner)
class A:
@myclassmethod
def f1(cls):
return cls
@classmethod
@myclassmethod
def f2(cls):
return cls
assert A.f1() is A
assert A.f2() is A # <-- fails in 3.9, works in 3.8 and before
This pattern would typically be used to do something extra in __call__.
For the sake of discussion, let's call the two arguments to __get__ "instance" and "owner". Typically, "instance" is an instance of "owner", or, equivalently, "owner" is the type of "instance". If "owner" is None, it is generally assumed to be the type of "instance".
In bpo-19072 (and gh8405), classmethod was changed to call obj.__get__(owner) if the wrapped object "obj" has get. Notice that only the "instance" argument is provided. Moreover, the type owner is passed as the "instance" argument. This means that the "owner" argument (which is None) will be assumed to be the type of the "instance" argument, which is the type of the owner type. This is wrong. The "owner" argument should be owner.
I believe it would be better for classmethod to call obj.__get__(owner, owner) if "obj" has get.
This is kind of difficult to explain. I will make a PR with more informative tests shortly. Here is the simple diff to make the above example pass:
New changeset 2ce8af3 by Miss Islington (bot) in branch '3.10': bpo-42073: allow classmethod to wrap other classmethod-like descriptors (GH-27115) (GH-27162) 2ce8af3
Activity
eriknw commentedon Oct 18, 2020
The following is new to Python 3.9, and I consider the implementation incomplete. I have code that works for Python 3.8 and before, but not for Python 3.9:
"Class methods can now wrap other :term:`descriptors <descriptor>` such as :func:`property`."
#8405
https://bugs.python.org/issue19072
As implemented,
classmethoddoes not correctly wrap descriptors that mimic classmethod. Previously, __get__of wrapped objects wasn't invoked by classmethod, so it was safe to have an object with both call and get behave like a classmethod. Now, classmethod calling get first gives incorrect results.Here is a minimal example:
This pattern would typically be used to do something extra in __call__.
For the sake of discussion, let's call the two arguments to __get__ "instance" and "owner". Typically, "instance" is an instance of "owner", or, equivalently, "owner" is the type of "instance". If "owner" is None, it is generally assumed to be the type of "instance".
In bpo-19072 (and gh8405), classmethod was changed to call
obj.__get__(owner)if the wrapped object "obj" has get. Notice that only the "instance" argument is provided. Moreover, the typeowneris passed as the "instance" argument. This means that the "owner" argument (which is None) will be assumed to be the type of the "instance" argument, which is the type of theownertype. This is wrong. The "owner" argument should beowner.I believe it would be better for classmethod to call
obj.__get__(owner, owner)if "obj" has get.This is kind of difficult to explain. I will make a PR with more informative tests shortly. Here is the simple diff to make the above example pass:
Since I consider the new behavior to have introduced a regression, I think this change should be applied to both 3.9 and 3.10.
Cheers!
ambv commentedon Jul 15, 2021
Thanks! ✨ 🍰 ✨
ambv commentedon Jul 15, 2021
New changeset b83861f by Łukasz Langa in branch 'main':
bpo-42073: allow classmethod to wrap other classmethod-like descriptors (bpo-27115)
b83861f
ambv commentedon Jul 15, 2021
New changeset 2ce8af3 by Miss Islington (bot) in branch '3.10':
bpo-42073: allow classmethod to wrap other classmethod-like descriptors (GH-27115) (GH-27162)
2ce8af3