From cd4c1cf5cfc966d394a8b084acd43a00ea24cda3 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 28 Apr 2019 01:09:08 -0700 Subject: [PATCH 1/5] bpo-36743: __get__ is sometimes called without the owner argument --- Doc/reference/datamodel.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index d1702ccb641767..41effa4cac5e22 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1620,12 +1620,13 @@ class' :attr:`~object.__dict__`. .. method:: object.__get__(self, instance, owner) - Called to get the attribute of the owner class (class attribute access) or of an - instance of that class (instance attribute access). *owner* is always the owner - class, while *instance* is the instance that the attribute was accessed through, - or ``None`` when the attribute is accessed through the *owner*. This method - should return the (computed) attribute value or raise an :exc:`AttributeError` - exception. + Called to get the attribute of the owner class (class attribute access) or + of an instance of that class (instance attribute access). The optional + *owner* argument is the owner class, while *instance* is the instance that + the attribute was accessed through, or ``None`` when the attribute is + accessed through the *owner*. Note that callers are allowed to omit the + *owner* argument when it isn't needed. This method should return the + (computed) attribute value or raise an :exc:`AttributeError` exception. .. method:: object.__set__(self, instance, value) From 755f766a99514299be35e9a9b549ed764c4bacbd Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 28 Aug 2019 22:45:22 -0700 Subject: [PATCH 2/5] Clarify caller and callee responsibilities in practice --- Doc/reference/datamodel.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 41effa4cac5e22..2661a3948a1f0f 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1618,16 +1618,23 @@ refers to the attribute whose name is the key of the property in the owner class' :attr:`~object.__dict__`. -.. method:: object.__get__(self, instance, owner) +.. method:: object.__get__(self, instance, owner=None) Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). The optional *owner* argument is the owner class, while *instance* is the instance that the attribute was accessed through, or ``None`` when the attribute is - accessed through the *owner*. Note that callers are allowed to omit the - *owner* argument when it isn't needed. This method should return the - (computed) attribute value or raise an :exc:`AttributeError` exception. + accessed through the *owner*. + This method should return the computed attribute value or raise an + :exc:`AttributeError` exception. + + :PEP:`252` specifies that :meth:`__get__` is callable with one or two + arguments. Python's own built-in descriptors support this specification; + however, it is likely that some third-party tools have descriptors + that require both arguments. Python's own :meth:`__getattribute__` + implementation always passes in both arguments whether they are required + or not. .. method:: object.__set__(self, instance, value) From 325e6c4ed17a26277f6f9762780f0b3cc16096c3 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 28 Aug 2019 22:54:35 -0700 Subject: [PATCH 3/5] Fix standard library descriptors that unnecessarily required a cls argument to __get__ --- Lib/_pyio.py | 2 +- Lib/functools.py | 6 +++--- Lib/unittest/mock.py | 2 +- Tools/demo/eiffel.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 40e0c9f9e2c66b..27447e0b6a15f1 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -283,7 +283,7 @@ def _open_code_with_warning(path): class DocDescriptor: """Helper for builtins.open.__doc__ """ - def __get__(self, obj, typ): + def __get__(self, obj, typ=None): return ( "open(file, mode='r', buffering=-1, encoding=None, " "errors=None, newline=None, closefd=True)\n\n" + diff --git a/Lib/functools.py b/Lib/functools.py index 9495fbe56eba39..f87d9c571873ad 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -383,7 +383,7 @@ def _method(cls_or_self, /, *args, **keywords): _method._partialmethod = self return _method - def __get__(self, obj, cls): + def __get__(self, obj, cls=None): get = getattr(self.func, "__get__", None) result = None if get is not None: @@ -888,7 +888,7 @@ def register(self, cls, method=None): """ return self.dispatcher.register(cls, func=method) - def __get__(self, obj, cls): + def __get__(self, obj, cls=None): def _method(*args, **kwargs): method = self.dispatcher.dispatch(args[0].__class__) return method.__get__(obj, cls)(*args, **kwargs) @@ -926,7 +926,7 @@ def __set_name__(self, owner, name): f"({self.attrname!r} and {name!r})." ) - def __get__(self, instance, owner): + def __get__(self, instance, owner=None): if instance is None: return self if self.attrname is None: diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 298b41e0d7e4dd..e56e4414d5d351 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2770,7 +2770,7 @@ class PropertyMock(Mock): def _get_child_mock(self, /, **kwargs): return MagicMock(**kwargs) - def __get__(self, obj, obj_type): + def __get__(self, obj, obj_type=None): return self() def __set__(self, obj, val): self(val) diff --git a/Tools/demo/eiffel.py b/Tools/demo/eiffel.py index 736abea81738c8..a76c2324dd6a67 100755 --- a/Tools/demo/eiffel.py +++ b/Tools/demo/eiffel.py @@ -78,7 +78,7 @@ def __init__(self, func, pre, post): self.__name__ = func.__name__ self.__doc__ = func.__doc__ - def __get__(self, obj, cls): + def __get__(self, obj, cls=None): return EiffelMethodWrapper(obj, self) def callmethod(self, inst, args, kwargs): From 5434a7670327c0553d88bc892ec0694dc7f4d581 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 28 Aug 2019 23:23:28 -0700 Subject: [PATCH 4/5] bpo-12077: Cross-reference __set__ to the data descriptor docs --- Doc/reference/datamodel.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 2661a3948a1f0f..d92ac61965ffdd 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1641,6 +1641,9 @@ class' :attr:`~object.__dict__`. Called to set the attribute on an instance *instance* of the owner class to a new value, *value*. + Note, adding :meth:`__set__` or :meth:`__delete__` changes the kind of + descriptor to a "data descriptor". See :ref:`invoking descriptors` for + more details. .. method:: object.__delete__(self, instance) From f31255879b48ce949b10b3e50944bba065c8f5f8 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Thu, 29 Aug 2019 00:06:20 -0700 Subject: [PATCH 5/5] Fix reference link --- Doc/reference/datamodel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index d92ac61965ffdd..8813f57587f01a 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1642,7 +1642,7 @@ class' :attr:`~object.__dict__`. new value, *value*. Note, adding :meth:`__set__` or :meth:`__delete__` changes the kind of - descriptor to a "data descriptor". See :ref:`invoking descriptors` for + descriptor to a "data descriptor". See :ref:`descriptor-invocation` for more details. .. method:: object.__delete__(self, instance)