2011-07-06 3 views
3

Lors de la lecture jusqu'à au sujet de certains module python je rencontrais cette classe de décorateur:Explication d'une classe de décorateur en 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) 

Ce que je sais décorateurs, est que la boîte étendre fonctionnalité (par exemple pour une fonction) . Quelqu'un peut-il être si gentil et m'expliquer pourquoi la classe ci-dessus est-elle utile et comment ça marche exactement?

Il est utilisé dans le code de cette façon:

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

Une autre question:

I encountered this piece of code in the same file: 

@property 
def some_other_function(...): 
    pass 

@property est définie nulle part dans le fichier. Est-ce un décorateur standard? Si oui, que fait-il? Google n'a pas pu m'aider sur cette affaire.

Par ailleurs, voici la source où je trouve le code: http://code.xster.net/pygeocoder/src/c9460febdbd1/pygeocoder.py

+0

http://wiki.python.org/moin/PythonDecorators#What_is_a_Decorator – krs1

+0

Si vous ne trouvez pas ce que 'property' est, a besoin de votre Google-fu travail: recherche de « propriété python » et vous accéder rapidement à des choses comme http://docs.python.org/library/functions.html#property. Et de là à des choses comme http://docs.python.org/glossary.html#term-decorator –

Répondre

5

omniméthode est très intelligent. Il utilise des astuces très subtiles pour faire son travail. Commençons au début.

Vous savez probablement déjà que decorator syntax est juste du sucre pour l'application de la fonction, à savoir:

@somedecorator 
def somefunc(...): 
    pass 

# is the same thing as  

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

donc somefunc est en fait une instance omnimethod, pas la fonction qui avait été défini. ce qui est intéressant à ce sujet est que omnimethod implémente également le descriptor interface. Si un attribut de classe définit une méthode __get__, alors chaque fois que cet attribut est mentionné, l'interpréteur appelle à la place __get__ sur cet objet, et le retourne au lieu de renvoyer l'attribut lui-même.

La méthode __get__ est toujours appelée avec l'instance en tant que premier argument, et la classe de cette instance en second argument. Si l'attribut a été recherché dans la classe elle-même, l'instance sera None.

Le dernier truc de supercherie est functools.partial, qui est la manière python de la fonction currying. Lorsque vous utilisez partial, vous lui passez une fonction et quelques arguments, et il retourne une nouvelle fonction qui, lorsqu'elle sera appelée, appellera la fonction d'origine avec les arguments d'origine en plus des arguments que vous aurez transmis plus tard. omnimethod utilise cette technique pour remplir le paramètre self à la fonction qu'il enveloppe.

Voici à quoi cela ressemble. un regular method peut être appelé lorsque vous le lisez depuis une instance mais vous ne pouvez pas l'utiliser depuis la classe elle-même.vous obtenez un TypeError non lié

>>> 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 fournit un décorateur bultin classmethod (et aussi staticmethod, mais tant pis que), qui vous permettra de l'utiliser au niveau de la classe, mais il ne fait jamais de voir l'instance. toujours reçoit la classe comme son premier argument.

>>> 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 
>>> 

Par c'est peu d'astuce, omnimethod vous donne un peu des deux.

>>> 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 
>>> 
+0

@TokeMacGuy, bonne réponse! Merci beaucoup! – Aufwind

1

omnimethod fait ce qu'il dit dans le commentaire; il vous laissera appeler une fonction comme une fonction statique sur une classe ou comme une fonction sur une instance de la classe. @property est un décorateur standard (voir le python docs) qui expose une fonction d'une manière qui lui donne l'apparence d'une simple variable d'instance.

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