【问题标题】:Trying to program a "to_class" general decorator尝试编写“to_class”通用装饰器
【发布时间】:2012-11-05 02:52:07
【问题描述】:

假设我已经定义:

def to_class(cls):
  """ returns a decorator
  aimed to force the result to be of class cls. """
  def decorating_func(func):
    def wrapper(*args, **kwargs):
      return cls(func(*args, **kwargs))
    return wrapper
  return decorator(decorating_func)

我希望用它来创建装饰器,将函数结果转换为给定类的对象。但是,这不起作用:

class TestClass(object):
  def __init__(self, value):
    self._value = (value, value)

  def __str__(self):
    return str(self._value)

  @staticmethod
  @to_test_class
  def test_func(value):
    return value

to_test_class = to_class(TestClass)

因为 test_func 将寻找 to_test_class 但不会找到它。另一方面,在类定义之前给 to_test_class 赋值也会失败,因为 TestClass 还没有定义。

试图将 @to_class(TestClass) 放在 test_func 的定义之上也会失败,因为方法是在类之前构造的(如果我没记错的话)。

我发现的唯一解决方法是将 to_test_class 手动定义为装饰器,而不是从一般的“to_class”定义返回。

这可能很重要,这只是一个基本示例,但我希望将 to_class 用于许多应用程序,例如在将返回值“插入”到类的构造函数之前修改它;我也希望将它用作其他类方法的装饰器。

我相信有些人认为“to_class”装饰器毫无意义;相反,可以在装饰方法中进行操作。不过,我觉得它很方便,并且有助于提高可读性。

最后我想补充一点,20% 是出于实际原因,80% 是出于学习原因,因为我发现这是我对 Python 中的装饰器一般不完全了解的内容。

【问题讨论】:

    标签: python decorator python-decorators


    【解决方案1】:

    确实,在类构建的时候,类对象本身还没有被构建,所以你不能把它作为装饰器的基础。

    我能想到的一种解决方法是使用staticmethod 装饰器。相反,在内部 在您自己的装饰器中重用 classmethod 装饰器。这样您就可以确保 Python 至少会为您传递相关的类:

    def to_class(func):
        """ returns a decorator
        aimed to force the result to be of class cls. """
        def wrapper(cls, *args, **kwargs):
            return cls(func(*args, **kwargs))
        return classmethod(wrapper)
    

    然后像这样使用它:

    class TestClass(object):
        def __init__(self, value):
            self._value = (value, value)
    
        def __str__(self):
            return str(self._value)
    
        @to_class
        def test_func(value):
            return value
    

    演示:

    >>> def to_class(func):
    ...     """ returns a decorator
    ...     aimed to force the result to be of class cls. """
    ...     def wrapper(cls, *args, **kwargs):
    ...         return cls(func(*args, **kwargs))
    ...     return classmethod(wrapper)
    ... 
    >>> class TestClass(object):
    ...     def __init__(self, value):
    ...         self._value = (value, value)
    ...     def __str__(self):
    ...         return str(self._value)
    ...     @to_class
    ...     def test_func(value):
    ...         return value
    ... 
    >>> TestClass.test_func('foo')
    <__main__.TestClass object at 0x102a77210>
    >>> print TestClass.test_func('foo')
    ('foo', 'foo')
    

    你的装饰器的通用版本并不容易; other 解决您的难题的唯一方法是使用元类 hack;请参阅another answer of mine,我在其中更详细地描述了该方法。

    您基本上需要进入类正在构建的命名空间,设置一个临时元类,然后依赖至少一个类的实例,您的装饰器才能工作;临时元类方法与类创建机制挂钩,以便稍后检索构造的类。

    但是,当您将此装饰器用作替代类工厂时,这可能 不是 会是理想的;如果有人使用您的修饰函数专门创建类实例,则调用元类为时已晚。

    【讨论】:

    • 不适用于普通方法(它们不能用于类方法),但当然有类似的方法使用.__class__ 来处理这些方法。不错的解决方案!
    • @Alfe: 'normal' 方法已经是实例的一部分,所以不需要返回类的实例。
    • 我们正在讨论包装它们的返回值。 “普通”方法不一定返回它们所在类的实例。但我认为只是你的措辞让我感到困惑。你的意思可能是“不需要给他们传课”(这就是我提到.__class__时的意思)。
    • 他对to_class 的定义相当笼统(例如,将任意类传入其中);并且由于这是一个董事会而不是 PM 会议,其他人可以稍后阅读,因此不仅提问者的意图是相关的;还有其他人可以从他的问题中得出什么结论,而且我至少对他的理解不同,所以你 99.9% 的可能性不是基于硬数字(我的读者人数已经超过 0.1%)。 ;-)
    • 确实如此。我只是认为他想到了一个通用的装饰器(甚至可能在课外的几个地方使用它)并且在这种情况下使用它遇到了麻烦。毕竟,强制结果属于特定类的所有用例绝不限于静态方法。
    【解决方案2】:

    好吧,你忘了类是传递给用classmethod修饰的方法的第一个参数,所以你可以这样写:

    def to_this_class(func):
        def wrapped(cls, value):
            res = func(cls, value)
            return cls(res)
        return wrapped
    
    class TestClass(object):
        def __init__(self, value):
            self._value = (value, value)
    
        def __str__(self):
            return str(self._value)
    
        @classmethod
        @to_this_class
        def test_func(cls, value):
            return value
    
    x = TestClass('a')
    
    print x.test_func('b')
    

    【讨论】:

    • 需要添加 'cls'。如果希望使用不同的类,这是不可能的(毕竟我们可能希望返回与我们当前定义的类不同的类的实例)。
    • @Alfe,是的,这就是我称它为to_this_class 的原因。对于其他类,如果循环依赖没有问题,他可以使用他的装饰器。
    【解决方案3】:

    问题是装饰器在定义它所装饰的东西时被评估,所以在定义方法test_func()时,装饰器to_test_class被调用,即使它已经存在,它应该处理的东西(类TestClass)还不存在(因为这是在创建所有方法之后创建的)。

    也许您可以在使用类的位置使用占位符,然后(在创建类之后)在占位符处填充该值(类)。

    例子:

    lazyClasses = {}
    def to_lazy_class(className):
      """ returns a decorator
      aimed to force the result to be of class cls. """
      def decorating_func(func):
        def wrapper(*args, **kwargs):
          return lazyClasses[className](func(*args, **kwargs))
        return wrapper
      return decorating_func 
    
    class TestClass(object):
      def __init__(self, value):
        self._value = (value, value)
    
      def __str__(self):
        return str(self._value)
    
      @staticmethod
      @to_lazy_class('TestClass')
      def test_func(value):
        return value
    
    lazyClasses['TestClass'] = TestClass
    
    >>> TestClass.test_func('hallo')
    <__main__.TestClass object at 0x7f76d8cba190>
    

    【讨论】:

    • 是的。您必须稍后以某种方式将值注入装饰器,因为当您 调用 装饰器时它还不存在。调用装饰器时并没有实际使用值,而是稍后调用装饰方法时,因此注入工作很懒惰。但是,即使您可以将用例限制为包装到您当前定义的类,问题仍然是如果您所拥有的只是静态方法,那么您无法找到定义静态方法的类。这是对 Python 的愚蠢限制:-/
    猜你喜欢
    • 2021-01-28
    • 2012-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-20
    • 2015-04-18
    • 2021-08-07
    相关资源
    最近更新 更多