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?
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.
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".
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
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
.
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
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!
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
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.