【问题标题】:How to find the containing class of a decorated method in Python如何在 Python 中找到装饰方法的包含类
【发布时间】:2012-11-15 08:53:17
【问题描述】:

我正在尝试实现一个装饰器类,该类将装饰其他类中的方法。但是,我需要包含装饰器中可用的装饰方法的类。我似乎在任何地方都找不到它。

这是一个例子:

class my_decorator(object):

  def __init__(self, arg1, arg2):
    print(self.__class__.__name__ + ".__init__")
    self.arg1 = arg1
    self.arg2 = arg2

  def __call__(self, my_callable):
    print(self.__class__.__name__ + ".__call__")
    print(type(my_callable))
    self.my_callable = my_callable
#    self.my_callable_method_class = ?where to get this?

    def function_wrapper(*args, **kwargs):
      print(self.__class__.__name__ + ".function_wrapper")
      print(self.arg1)
      self.my_callable.__call__(*args, **kwargs)
      print(self.arg2)

    return function_wrapper


class MyClass(object):

  @my_decorator(arg1="one", arg2="two")
  def decorated_method(self):
    print(self.__class__.__name__ + ".decorated_method")
    print(type(self.decorated_method))
    print("hello")


m = MyClass()
m.decorated_method()

这将打印出这个:

my_decorator.__init__
my_decorator.__call__
<type 'function'>
my_decorator.function_wrapper
one
MyClass.decorated_method
<type 'instancemethod'>
hello
two

在装饰器类中,可调用对象是函数类型,而在类本身中,它是实例方法类型。我可以从instancemethod中得到im_class,但是函数中没有这样的东西。

如何从装饰器中获取包含装饰方法的类?

我可以这样做:

class my_decorator(object):

  def __init__(self, cls, arg1, arg2):

.
.

class MyClass(object):

  @my_decorator(cls=MyClass, arg1="one", arg2="two")
  def decorated_method(self):

.
.

但我不想这样做,因为它是多余的而且不好。

或者我应该以其他方式实现它吗?我基本上需要装饰器的几个参数,并且我需要装饰器中的装饰方法的类。

【问题讨论】:

  • 你什么时候需要上课?您可以将内部包装器修改为def function_wrapper(self, *args, **kwargs),并获取类为self.__class__。如果您需要装饰器之外的类,那么正如 katrielalex 已经指出的那样,它要困难得多。

标签: python


【解决方案1】:

你可以装饰

@decorate
class MyClass(object):

  @my_decorator(arg1="one", arg2="two")
  def decorated_method(self):

并使用外部装饰器将类参数发送到内部。


您的任何提议都不起作用,因为它们需要在类存在之前访问。当您定义一个类时,您首先在其主体内执行代码(定义函数等),然后将结果范围分配给该类作为其__dict__。所以在定义decorated_method 的时候,MyClass 还不存在。

【讨论】:

  • 这看起来像是要走的路。但是如何链接类装饰器和方法装饰器呢?我将有很多使用相同装饰器的类和方法...
  • @my_decorator 应该标记它已经装饰的方法,比如给它们一个特殊的属性。 @decorate 应该遍历所有类方法,过滤那些没有特殊属性的方法,并依次对每个方法进行修饰。
【解决方案2】:

这是一个有效的修订版本。

# This holds all called method_decorators
global_method_decorator_list = []

class class_decorator(object):
  def __init__(self, arg1, arg2):
    print(self.__class__.__name__ + ".__init__")
    self.arg1 = arg1
    self.arg2 = arg2

  def __call__(self, my_class):
    print(self.__class__.__name__ + ".__call__")
    print(repr(my_class))
    print(my_class.__name__)
    self.cls = my_class
    class_decorators[my_class] = self
    self.my_class = my_class

    # Call each method decorator's second_init()
    for d in global_method_decorator_list:
      d._method_decorator_.second_init(self, my_class)

    def wrapper(*args, **kwargs):
      print(self.__class__.__name__ + ".wrapper")
      print(self.arg1)
      retval = self.my_class.__call__(*args, **kwargs)
      print(self.arg2)
      return retval

    return wrapper


class method_decorator(object):
  def __init__(self, arg1, arg2):
    print(self.__class__.__name__ + ".__init__")
    self.arg1 = arg1
    self.arg2 = arg2

  def __call__(self, my_callable):
    print(self.__class__.__name__ + ".__call__")
    print(repr(my_callable))
    self.my_callable = my_callable

    # Mark the callable and add to global list
    my_callable._method_decorator_ = self
    global_method_decorator_list.append(my_callable)

    def wrapper(*args, **kwargs):
      print(self.__class__.__name__ + ".wrapper")
      print(self.arg1)
      retval=self.my_callable.__call__(*args, **kwargs)
      print(self.arg2)
      return retval

    return wrapper

  def second_init(self, the_class_decorator, the_class):
    print(self.__class__.__name__ + ".second_init")
    print("The Class: " + repr(the_class))**


@class_decorator(arg1="One", arg2="Two")
class MyClass(object):

  @method_decorator(arg1="one", arg2="two")
  def decorated_method(self):
    print(self.__class__.__name__ + ".decorated_method")
    print(type(self.decorated_method))
    print("hello")


m = MyClass()
m.decorated_method()

输出如下:

class_decorator.__init__
method_decorator.__init__
method_decorator.__call__
<function decorated_method at 0x3063500>
class_decorator.__call__
<class '__main__.MyClass'>
MyClass
method_decorator.second_init
The Class: <class '__main__.MyClass'>
class_decorator.wrapper
One
Two
method_decorator.wrapper
one
MyClass.decorated_method
<type 'instancemethod'>
hello
two

不同之处在于该类现在有一个单独的装饰器。类装饰器的 call() 将调用每个方法装饰器的“second_init()”方法,并在那里传递类。

有趣的是,method_decorator 的 call() 将在 class_decorator 之前调用。

【讨论】:

    【解决方案3】:

    如果将装饰器返回的对象设为descriptor,则可以挂钩属性查找以返回链接方法和类(或实例)的其他对象。

    对于一个方法风格的描述符,只需要实现__get__方法即可。在类上查找方法时,下面两个是等价的:

    m = MyClass.decorated_method
    # It will actually get the object from any parent class too.  But this will do for a simple example
    m = MyClass.__dict__['decorated_method'].__get__(MyClass)
    

    例如,以下是等价的:

    instance = MyClass()
    m = instance.decorated_method
    m = type(instance).__dict__['decorated_method'].__get__(instance, type(instance))
    

    所以instance.decorated_method(...) 表达式实际上调用了__get__ 方法返回的对象。这与允许简单函数对象转换为添加隐式 self 参数的绑定方法对象的过程相同。

    创建此可调用对象时,您应该拥有所需的所有信息。

    【讨论】:

    • 这个问题是我需要在创建第一个实例之前处理类。
    • 很公平。然后你可能需要一个装饰器来标记像 katrielalex 建议的函数,然后从类装饰器或元类构造函数中修复。
    猜你喜欢
    • 2011-06-26
    • 2014-12-29
    • 2013-09-05
    • 2021-07-18
    • 2012-02-09
    • 2010-11-24
    • 1970-01-01
    • 2018-07-14
    • 2019-11-06
    相关资源
    最近更新 更多