__get__ called without being bound when a bound method should be called

Đang mở
#98,905 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
25/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Đình trệ
Công nghệ
python
Lĩnh vực
compilers

Hướng nghiên cứu

Bắt đầu bằng cách chạy reproducer trong issue và theo dõi quá trình tra cứu descriptor cho O.d và o.d, tập trung vào cách get được gọi. Công việc được xem là hoàn tất khi hành vi khớp với kỳ vọng đã nêu: O.d trả về O.dict["d"] và o.d trả về None, cùng với kiểm thử hồi quy cho ví dụ.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

interpreter-core

(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': {}}
Ngôn ngữ chính
Python
Star
77.2k
Fork
36k
Merge trung bình
1 ngày 9 giờ
Pull request đã merge (30 ngày)
558

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của python/cpython

Tất cả issue của python/cpython

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.