__get__ called without being bound when a bound method should be called
まだ誰も着手していません。
評価
調査の方向性
まず issue の reproducer を実行し、O.d と o.d の descriptor lookup を追跡して、get がどのように呼び出されるかに注目します。次の期待どおりの動作になれば完了です: O.d は O.dict["d"] を返し、o.d は None を返し、例に対する回帰テストのカバレッジがあること。
索引モデルが issue の本文から書いたものです。
説明
(Edited a not important part of the code.)
Suppose a class D has a __get__ method D.__dict__["__get__"] for its instances, and O.__dict__["d"] is an object of type D, where O is a class. Then, for an object o of type O, my expectation was the value o.d would be got as (O.__dict__["d"]).__get__(o, O). In reality, its evaluation tries to call D.__dict__["__get__"] (on the arguments O.__dict__["d"], o, O). See the code below. I used Python 3.9.7 on MacOS.
This means it wouldn't help if d.__get__ will be callable for every object d of type D. For instance, D.__dict__["__get__"] can't be of type property, functools.partialmethod or staticmethod to work properly. Another concern is if D.__dict__["__get__"] happens to be callable in an unexpected way, then one can get an unexpected value for o.d, which would be worse if it happens. (If D.__dict__["__get__"] is callable and its class type(D.__dict__["__get__"]) doesn't have or inherit a __get__ method, then that __get__ method of D seems to be meant static.)
I don't know where else a dunder method may be required to be callable without being bound, but I think such a requirement should be replaced with one that bound methods be callable. In the case above, evaluation of o.d (or O.d) should call the bound method (O.__dict__["d"]).__get__.
Here's a code for your convenience.
from functools import partial
# Utility
def unpack(iterator):
next_ = iterator.__next__
while True:
try:
next_()
except StopIteration:
return
# Context
class Get:
'''Creates a method to be __get__ .
'''
def __get__(self, descriptor, owner=None):
return self if descriptor is None\
else partial(self._do, descriptor)
def _do(self, descriptor, instance, owner=None):
return descriptor if instance is None else None
# For the purpose of inspection, instances will be callable in an
# unexpected way.
def __call__(self, /, *args, **kwargs):
print("Called:", (dict_ := locals()).pop("self"))
return dict_
class D:
'''A class of descriptors.
'''
__get__ = Get()
def __set__(self, instance, value):
return
def __delete__(self, instance):
return
class O:
'''A class with a descriptor of type D .
'''
d = D()
# Test
unpack(map(print,
map(eval,
map("f'{{{}=}}'".format,
('D.__dict__["__get__"]',
'O.__dict__["d"]',
'O.d',
'(o := O())',
'o.d', )))))
The expectation from the code is
O.d ---> O.__dict__["d"],
o.d ---> None.
An example of what actually get printed:
D.__dict__["__get__"]=<__main__.Get object at 0x100e21400>
O.__dict__["d"]=<__main__.D object at 0x100e213d0>
Called: <__main__.Get object at 0x100e21400>
O.d={'args': (<__main__.D object at 0x100e213d0>, None, <class '__main__.O'>), 'kwargs': {}}
(o := O())=<__main__.O object at 0x100e210a0>
Called: <__main__.Get object at 0x100e21400>
o.d={'args': (<__main__.D object at 0x100e213d0>, <__main__.O object at 0x100e210a0>, <class '__main__.O'>), 'kwargs': {}}
- 主要言語
- Python
- スター
- 77.2k
- フォーク
- 36k
- 平均マージ
- 1日 9時間
- マージ済み PR(30日)
- 558
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
python/cpython のほかの issue
-
docs pending
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
stdlib type-feature
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
stdlib type-feature
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
-
build type-bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
-
stdlib topic-email type-feature
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
-
🐛 Bug 🔔 Pending processing
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
jumpserver/jumpserver#17584 ·