Python中装饰器类的解释

4 投票
2 回答
783 浏览
提问于 2025-04-16 20:57

在阅读某个Python模块时,我遇到了这个装饰器类:

# this decorator lets me use methods as both static and instance methods
class omnimethod(object):
        def __init__(self, func):
                self.func = func

        def __get__(self, instance, owner):
                return functools.partial(self.func, instance)

我对装饰器的了解是,它们可以扩展功能(比如对一个函数)。能不能请教一下,上面的这个类有什么用,以及它是如何工作的

它在代码中是这样使用的:

@omnimethod:
def some_function(...):
    pass

还有一个问题:

I encountered this piece of code in the same file:

@property
def some_other_function(...):
    pass

@property在文件中并没有定义。这是某种标准的装饰器吗?如果是的话,它具体是做什么的?谷歌在这方面没有给我帮助。

顺便说一下,这是我找到代码的来源: http://code.xster.net/pygeocoder/src/c9460febdbd1/pygeocoder.py

2 个回答

1

omnimethod的作用就是它评论里说的那样;它允许你把some_function当作类的“静态函数”来调用,也可以把它当作类的实例上的函数来使用。@property是一个标准的装饰器(你可以查看Python文档),它可以把一个函数展示得像一个简单的实例变量一样。

class B:
  @omnimethod
  def test(self):
    print 1

  @property
  def prop(self):
    return 2

>>> b = B()
>>> b.test()
1
>>> B.test()
1
>>> b.prop
2
5

这个“全能方法”真是个聪明的东西。它用了一些非常巧妙的技巧来完成它的工作。让我们从头说起。

你可能已经知道,装饰器语法其实就是函数应用的一种简化写法,也就是说:

@somedecorator
def somefunc(...):
    pass

# is the same thing as    

def somefunc(...):
    pass
somefunc = somedecorator(somefunc)

所以,somefunc 实际上是一个 omnimethod 的实例,而不是之前定义的那个函数。这里有趣的是,omnimethod 还实现了 描述符接口。如果一个类的属性定义了 __get__ 方法,那么每当提到这个属性时,解释器就会调用这个对象的 __get__ 方法,而不是直接返回这个属性本身。

__get__ 方法总是以实例作为第一个参数,以该实例的类作为第二个参数。如果这个属性是直接从类本身查找的,那么实例的值将是 None

最后一点小技巧是 functools.partial,这是 Python 中实现函数 柯里化 的方式。当你使用 partial 时,你传入一个函数和一些参数,它会返回一个新函数,这个新函数在调用时,会用原始函数和原始参数,以及你后面传入的参数来调用原始函数。omnimethod 就是用这个技巧来填充它所包装的函数的 self 参数。

看看这个是怎么回事。一个普通方法可以通过实例来调用,但你不能直接从类来调用它。这样会出现一个未绑定的类型错误。

>>> class Foo(object):
...     def bar(self, baz):
...         print self, baz
... 
>>> f = Foo()
>>> f.bar('apples')
<__main__.Foo object at 0x7fe81ab52f90> apples
>>> Foo.bar('quux')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with 
Foo instance as first argument (got str instance instead)
>>> Foo.bar(None, 'quux')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with
Foo instance as first argument (got NoneType instance instead)
>>> 

Python 提供了一个内置的装饰器 classmethod(还有 staticmethod,不过我们暂时不讨论这个),它允许你在类级别使用,但它永远无法看到实例。它总是将类作为第一个参数接收。

>>> class Foo(object):
...     @classmethod
...     def bar(cls, baz):
...         print cls, baz
... 
>>> f = Foo()
>>> Foo.bar('abc')
<class '__main__.Foo'> abc
>>> f.bar('def')
<class '__main__.Foo'> def
>>> 

通过这些巧妙的设计,omnimethod 让你同时拥有了两者的好处。

>>> class Foo(object):
...     @omnimethod
...     def bar(self, baz):
...         print self, baz
... 
>>> f = Foo()
>>> Foo.bar('bananas')
None bananas    
>>> f.bar('apples')
<__main__.Foo object at 0x7fe81ab52f90> apples
>>> 

撰写回答