В каких случаях полезно, чтобы метод `__get__` дескриптора возвращал другой дескриптор? - PullRequest
0 голосов
/ 26 апреля 2018

У меня был контейнер, содержащий методы объекта и, возможно, некоторые дескрипторы. Я хотел проверить, были ли дескрипторы уже развернуты или нет, проверив, есть ли у них метод ' get '. К моему удивлению, метод __get__ метода класса возвращает объект, который также имеет метод __get__. Знаете ли вы, когда это поведение полезно? Это как-то связано с переопределением методов класса в производном классе?

import inspect

class K:    
    @classmethod
    def cm(cls, a, b, c):
        pass

def get_attr_info(attr):
    try:
        sig = inspect.signature(attr)
    except:
        sig = None

    attr_info = [
        ('id ', id(attr),),
        ('type  ', type(attr),),
        ('hasattr ', '__get__', hasattr(attr, '__get__'),),
        ('hasattr ', '__call__', hasattr(attr, '__call__'),),
        ('SIG:  ', sig,)
    ]
    return attr_info

get_label = lambda tpl: ' '.join([str(x) for x in tpl[0:-1]]).ljust(20)

kinst = K()
cm = object.__getattribute__(type(kinst), '__dict__')['cm']

try:
    for idx in range(0, 5):
        info = get_attr_info(cm)        
        print('\n' + '\n'.join([get_label(tpl) + str(tpl[-1]) for tpl in info]))
        cm = cm.__get__(kinst, type(kinst))
except AttributeError:
    print(idx)

Выход:

id                  44545808
type                <class 'classmethod'>
hasattr  __get__    True
hasattr  __call__   False
SIG:                None

id                  6437832
type                <class 'method'>
hasattr  __get__    True
hasattr  __call__   True
SIG:                (a, b, c)

id                  6437832
type                <class 'method'>
hasattr  __get__    True
hasattr  __call__   True
SIG:                (a, b, c)

id                  6437832
type                <class 'method'>
hasattr  __get__    True
hasattr  __call__   True
SIG:                (a, b, c)

id                  6437832
type                <class 'method'>
hasattr  __get__    True
hasattr  __call__   True
SIG:                (a, b, c)

Ответы [ 2 ]

0 голосов
/ 26 апреля 2018

Конкретный вопрос о том, почему связанные методы являются дескрипторами (должно быть очевидно, почему classmethod объекты, верно?), См. пользовательский ответ .


Для общего вопроса о том, почему дескрипторы, не относящиеся к данным, должны быть разрешены: было бы более болезненно писать их. В конце концов, наиболее очевидный способ написать дескриптор, не связанный с данными, часто просто возвращать функцию. И функции должны быть дескрипторами, не относящимися к данным, иначе методы не сработают, победив все дескрипторы причин, которые были добавлены в язык.

Например, рассмотрим пример "чистого Python classmethod" в HOWTO :

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

Или, проще, рассмотрим staticmethod, который даже во встроенной реализации просто возвращает саму функцию при привязке.

Конечно, вы также можете реализовать любой из них, создав объект с помощью пользовательского метода __call__, а иногда это стоит сделать (например, вы можете реализовать classmethod с partial или с чем-то, что действует как partial, в этом случае не было бы никакой причины добавлять __get__) - но когда функция просто работает, почему бы не использовать функцию?


Для более абстрактного вопроса о том, можете ли вы найти применение этой функции ... Ну, конечно. Среди вещей, которые вы можете сделать:

  • Создание явных несвязанных методов в стиле 2.x, которые затем можно просматривать как методы, а не как функции.
  • Создание «перебиваемых» методов как часть построения прототипа объектной системы. (Нормальные объекты метода просто игнорируют свои аргументы в __get__, за исключением проверки того, что либо второй является типом, либо вторым является None, а первый является типом ...)
  • Создайте объект, который имитирует функцию (а не просто вызываемый, как методы, партиалы и т. Д.), Включая возможность действовать как несвязанный метод, результат статического метода и т. Д.

Ничего из этого вы не захотите делать очень часто, но это также не то, что у Python нет никаких причин мешать вам делать.

0 голосов
/ 26 апреля 2018

Специфическое поведение, которое вы видите, имело смысл из-за несвязанных объектов метода. Вернитесь в Python 2, если вы сделали

class Foo(object):
    def useful_method(self):
        do_stuff()

class Bar(object):
    useful_method = Foo.useful_method

Foo.useful_method будет оцениваться как объект несвязанного метода , что-то похожее, но немного отличающееся от функции. Несвязанным объектам метода требовалось __get__, поэтому Bar().useful_method автоматически связывалось бы self, точно так же, как если бы Foo.useful_method вычислял функцию вместо несвязанного метода во время определения Bar.

Объекты несвязанного метода и объекты связанного метода были реализованы с одним и тем же типом, поэтому связанные методы имели один и тот же метод, что и у несвязанных методов __get__. Для объектов связанного метода, однако, __get__ просто вернет объект связанного метода, как если бы логика дескриптора не использовалась.

Теперь, когда несвязанных методов больше не существует, метод объектов методов __get__ является излишним, но он не был удален.

...