Как найти класс связанного метода при создании класса в Python 3.1? - PullRequest
5 голосов
/ 03 февраля 2010

я хочу написать декоратор, который позволит методам классов стать видимыми для других сторон; проблема, которую я описываю, однако, не зависит от этой детали. код будет выглядеть примерно так:

def CLASS_WHERE_METHOD_IS_DEFINED( method ):
  ???

def foobar( method ):
  print( CLASS_WHERE_METHOD_IS_DEFINED( method ) )

class X:

  @foobar
  def f( self, x ):
    return x ** 2

Моя проблема здесь в том, что в тот момент, когда декоратор, foobar(), видит метод, он еще не вызывается; вместо этого он видит несвязанную версию. возможно, это можно решить с помощью другого декоратора в классе, который позаботится обо всем, что должно быть сделано с привязанным методом. Следующее, что я попытаюсь сделать, это просто выделить украшенный метод атрибутом, когда он проходит через декоратор, а затем использовать декоратор класса или метакласс для выполнения постобработки. если я получу это на работу, то мне не придется разгадывать эту загадку, которая все еще озадачивает меня:

Может ли кто-нибудь в вышеприведенном коде заполнить значимые строки под CLASS_WHERE_METHOD_IS_DEFINED, чтобы декоратор мог фактически распечатать класс, в котором определен f, в тот момент, когда он определен? или эта возможность исключена в Python 3?

Ответы [ 3 ]

7 голосов
/ 03 февраля 2010

Когда вызывается декоратор, он вызывается с функцией в качестве аргумента, не методом - поэтому он ничего не даст декоратору, чтобы исследовать и анализировать свой метод столько, сколько он хочет, потому что это всего лишь функция и не несет никакой информации о включающем классе. Я надеюсь, что это решает вашу "загадку", хотя и в отрицательном смысле!

Можно попробовать и другие подходы, такие как глубокий самоанализ для вложенных фреймов стека, но они хакерские, хрупкие и не переносятся на другие реализации Python 3, такие как pynie; Поэтому я от всей души рекомендую избегать их в пользу решения для класса-декоратора, которое вы уже рассматриваете, и оно намного чище и надежнее.

0 голосов
/ 23 января 2019

Как я уже упоминал в некоторых других ответах , начиная с Python 3.6, решение этой проблемы очень легко благодаря object.__set_name__, который вызывается с помощьюОбъект класса, который определяется.

Мы можем использовать его для определения декоратора, который имеет доступ к классу, следующим образом:

class class_decorator:
    def __init__(self, fn):
        self.fn = fn

    def __set_name__(self, owner, name):
        # do something with "owner" (i.e. the class)
        print(f"decorating {self.fn} and using {owner}")

        # then replace ourself with the original method
        setattr(owner, name, self.fn)

, который затем можно использовать в качестве обычного декоратора.:

>>> class A:
...     @class_decorator
...     def hello(self, x=42):
...         return x
...
decorating <function A.hello at 0x7f9bedf66bf8> and using <class '__main__.A'>
>>> A.hello
<function __main__.A.hello(self, x=42)>
0 голосов
/ 26 мая 2016

Это очень старая статья, но интроспекция не способ решить эту проблему, потому что ее легче решить с помощью метакласса и немного умной логики построения классов с использованием дескрипторы .

import types

# a descriptor as a decorator
class foobar(object):

    owned_by = None

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

    def __call__(self, *args, **kwargs):
        # a proxy for `func` that gets used when
        # `foobar` is referenced from by a class
        return self.func(*args, **kwargs)

    def __get__(self, inst, cls=None):
        if inst is not None:
            # return a bound method when `foobar`
            # is referenced from by an instance
            return types.MethodType(self.func, inst, cls)
        else:
            return self

    def init_self(self, name, cls):
        print("I am named '%s' and owned by %r" % (name, cls))
        self.named_as = name
        self.owned_by = cls

    def init_cls(self, cls):
        print("I exist in the mro of %r instances" % cls)
        # don't set `self.owned_by` here because 
        # this descriptor exists in the mro of
        # many classes, but is only owned by one.
        print('')

Ключом к созданию этой работы является метакласс - он просматривает атрибуты, определенные в классах, которые он создает, чтобы найти foobar дескрипторы. После этого он передает им информацию о классах, в которые они вовлечены, через методы дескриптора init_self и init_cls.

init_self вызывается только для класса, для которого определен дескриптор. Здесь необходимо внести изменения в foobar, потому что метод вызывается только один раз. Пока init_cls вызывается для всех классов, которые имеют доступ к оформленному методу. Вот где должны быть сделаны изменения для классов foobar.

import inspect

class MetaX(type):

    def __init__(cls, name, bases, classdict):
        # The classdict contains all the attributes
        # defined on **this** class - no attribute in
        # the classdict is inherited from a parent.
        for k, v in classdict.items():
            if isinstance(v, foobar):
                v.init_self(k, cls)

        # getmembers retrieves all attributes
        # including those inherited from parents
        for k, v in inspect.getmembers(cls):
            if isinstance(v, foobar):
                v.init_cls(cls)

пример

# for compatibility
import six

class X(six.with_metaclass(MetaX, object)):

    def __init__(self):
        self.value = 1

    @foobar
    def f(self, x):
        return self.value + x**2

class Y(X): pass

# PRINTS:
# I am named 'f' and owned by <class '__main__.X'>
# I exist in the mro of <class '__main__.X'> instances

# I exist in the mro of <class '__main__.Y'> instances

print('CLASS CONSTRUCTION OVER\n')

print(Y().f(3))
# PRINTS:
# 10
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...