web-dev-qa-db-fra.com

Python attributs de fonction - utilisations et abus

Peu de gens sont conscients de cette fonctionnalité, mais les fonctions (et méthodes) de Python peuvent avoir attributs . Voir:

>>> def foo(x):
...     pass
...     
>>> foo.score = 10
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name', 'score']
>>> foo.score
10
>>> foo.score += 1
>>> foo.score
11

Quels sont les usages possibles et les abus de cette fonctionnalité dans Python? Un bon usage que je connaisse est PLY ' s utilisation de la docstring pour associer une règle de syntaxe à une méthode, mais qu’en est-il des attributs personnalisés? Y at-il de bonnes raisons de les utiliser?

175
Eli Bendersky

J'utilise généralement les attributs de fonction comme stockage pour les annotations. Supposons que je veuille écrire dans le style de C # (indiquant qu'une certaine méthode devrait faire partie de l'interface du service Web)

class Foo(WebService):
    @webmethod
    def bar(self, arg1, arg2):
         ...

alors je peux définir

def webmethod(func):
    func.is_webmethod = True
    return func

Ensuite, quand un appel de service Web arrive, je cherche la méthode, vérifie si la fonction sous-jacente a l'attribut is_webmethod (la valeur réelle n'est pas pertinente) et refuse le service si la méthode est absente ou non destinée à être appelée sur le Web.

141
Martin v. Löwis

Je les ai utilisées comme variables statiques pour une fonction. Par exemple, étant donné le code C suivant:

int fn(int i)
{
    static f = 1;
    f += i;
    return f;
}

Je peux implémenter la fonction de manière similaire en Python:

def fn(i):
    fn.f += i
    return fn.f
fn.f = 1

Cela tomberait définitivement dans la catégorie des "abus".

117
mipadi

Vous pouvez faire des objets de la manière JavaScript ... Cela n’a aucun sens mais cela fonctionne;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
50
defnull

Je les utilise avec parcimonie, mais ils peuvent être assez pratiques:

def log(msg):
   log.logfile.write(msg)

Maintenant, je peux utiliser log dans tout mon module et rediriger la sortie simplement en définissant log.logfile. Il existe de nombreuses autres façons d'y parvenir, mais celle-ci est légère et simple. Et même si cela sentait drôle la première fois que je l’ai fait, j’en suis venu à croire que ça sent mieux que d’avoir une variable globale logfile.

14
Robert Rossney

Les attributs de fonction peuvent être utilisés pour écrire des fermetures légères qui encapsulent le code et les données associées:

#!/usr/bin/env python

SW_DELTA = 0
SW_MARK  = 1
SW_BASE  = 2

def stopwatch():
   import time

   def _sw( action = SW_DELTA ):

      if action == SW_DELTA:
         return time.time() - _sw._time

      Elif action == SW_MARK:
         _sw._time = time.time()
         return _sw._time

      Elif action == SW_BASE:
         return _sw._time

      else:
         raise NotImplementedError

   _sw._time = time.time() # time of creation

   return _sw

# test code
sw=stopwatch()
sw2=stopwatch()
import os
os.system("sleep 1")
print sw() # defaults to "SW_DELTA"
sw( SW_MARK )
os.system("sleep 2")
print sw()
print sw2()

1,00934004784

2.00644397736

3.01593494415

10
Kevin Little

Parfois, j'utilise un attribut d'une fonction pour mettre en cache des valeurs déjà calculées. Vous pouvez également avoir un décorateur générique qui généralise cette approche. Soyez conscient des problèmes de concurrence et des effets secondaires de telles fonctions!

4
unbeknown

J'ai créé cet assistant décorateur pour définir facilement les attributs de fonction:

def with_attrs(**func_attrs):
    """Set attributes in the decorated function, at definition time.
    Only accepts keyword arguments.
    E.g.:
        @with_attrs(counter=0, something='boing')
        def count_it():
            count_it.counter += 1
        print count_it.counter
        print count_it.something
        # Out:
        # >>> 0
        # >>> 'boing'
    """
    def attr_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)

        for attr, value in func_attrs.iteritems():
            setattr(wrapper, attr, value)

        return wrapper

    return attr_decorator

Un cas d'utilisation consiste à créer une collection d'usines et à interroger le type de données qu'elles peuvent créer au niveau méta d'une fonction.
Par exemple (très stupide):

@with_attrs(datatype=list)
def factory1():
    return [1, 2, 3]

@with_attrs(datatype=SomeClass)
def factory2():
    return SomeClass()

factories = [factory1, factory2]

def create(datatype):
    for f in factories:
        if f.datatype == datatype:
            return f()
    return None
2
DiogoNeves

J'avais toujours supposé que la seule raison pour laquelle cela était possible était qu'il y avait donc un endroit logique pour mettre une chaîne de documentation ou autre. Je sais que si je l’utilisais pour n’importe quel code de production, cela confondre la plupart des lecteurs.

1
Dale Reidy