python / cpython

The Python programming language
https://www.python.org
Other
62.59k stars 30.03k forks source link

dir(<bound method>) should include dunder attributes of the unbound method #73303

Open 379dc349-3a10-424f-b9d2-a0104f092359 opened 7 years ago

379dc349-3a10-424f-b9d2-a0104f092359 commented 7 years ago
BPO 29117
Nosy @terryjreedy, @anntzer, @zhangyangyu

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: ```python assignee = None closed_at = None created_at = labels = ['interpreter-core', 'type-bug', '3.7'] title = 'dir() should include dunder attributes of the unbound method' updated_at = user = 'https://github.com/anntzer' ``` bugs.python.org fields: ```python activity = actor = 'terry.reedy' assignee = 'none' closed = False closed_date = None closer = None components = ['Interpreter Core'] creation = creator = 'Antony.Lee' dependencies = [] files = [] hgrepos = [] issue_num = 29117 keywords = [] message_count = 2.0 messages = ['284347', '284859'] nosy_count = 3.0 nosy_names = ['terry.reedy', 'Antony.Lee', 'xiang.zhang'] pr_nums = [] priority = 'normal' resolution = None stage = 'test needed' status = 'open' superseder = None type = 'behavior' url = 'https://bugs.python.org/issue29117' versions = ['Python 3.6', 'Python 3.7'] ```

379dc349-3a10-424f-b9d2-a0104f092359 commented 7 years ago
Python 3.5.2 (default, Nov  7 2016, 11:31:36) 
[GCC 6.2.1 20160830] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class C:
...     def f(self): pass
... 
>>> C.f.attr = 42
>>> print(dir(C.f))
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'attr']
>>> print(dir(C().f))
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'attr']
>>> 

Note that dir(C().f) does include the custom attr attribute, but is missing some of the dunder attributes (e.g. __annotations__), which are actually accessible directly from the boun method.

terryjreedy commented 7 years ago

I tested your last claim and it is true as far as I went.

>>> C.f.__annotations__
{'a': <class 'int'>}
>>> C().f.__annotations__
{'a': <class 'int'>}
>>> C.f.__code__
<code object f at 0x000002610BDB5150, file "<pyshell#7>", line 2>
>>> C().f.__code__
<code object f at 0x000002610BDB5150, file "<pyshell#7>", line 2>
blhsing commented 3 months ago

This is not a bug. According to the documentation the list dir returns "is not necessarily complete and may be inaccurate when the object has a custom __getattr__()".

In this case, C.f is of function type while C().f is of method type, and you're able to access attributes of a function from a method because the method type has a custom __getattr__ defined to delegate missing attribute lookups to its underlying function: https://github.com/python/cpython/blob/030b452e34bbb0096acacb70a31915b9590c8186/Objects/classobject.c#L206

It is therefore technically impractical if not impossible for dir to deduce from the custom logics of MethodType.__getattr__ that it's really defaulting attribute lookups to a function.

Perhaps it makes sense for MethodType to have been designed as a subclass of FunctionType to begin with instead of relying on the hackier attribute delagation logics but it's too late to make that change now for compatibility reasons.