试图编写“to_class”通用装饰器

时间:2012-11-16 11:22:22

标签: python decorator python-decorators

假设我已定义:

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”def返回的那个。

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

我确信有人认为“to_class”装饰者毫无意义;操作可以在装饰方法中完成。虽然,我发现它很方便,它有助于我提高可读性。

最后,我想补充一点,由于实际原因,我感兴趣的是20%,研究原因使我感兴趣的是80%,因为我发现这是我对Python中的装饰器一般不太了解的事情。

3 个答案:

答案 0 :(得分:4)

实际上,在类构造时,类对象本身尚未构造,因此您不能将其用作装饰器的基础。

我能想到的一个解决方法是使用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')

装饰师的通用版本并不容易;你的难题唯一的其他解决方法是使用元类黑客攻击;请参阅another answer of mine,我将更详细地描述该方法。

你基本上需要进入class-under-construction命名空间,设置一个临时元类,然后依赖于你的装饰器工作之前至少有一个类的实例;临时元类方法挂钩到类创建机制,以便稍后检索构造的类。

看到你正在使用这个装饰器作为替代类工厂,但可能将是理想的;如果有人使用你的装饰函数来专门创建类实例,则会调用元类太晚了。

答案 1 :(得分:3)

好吧,你忘了这个类是传递给用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')

答案 2 :(得分:0)

问题是装饰器会在定义它装饰的东西时得到评估,因此在定义方法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>