2008-12-03 8 views
139

No muchos conocen esta función, pero las funciones (y métodos) de Python pueden tener attributes. He aquí:Atributos de función de Python: usos y abusos

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

¿Cuáles son los posibles usos y abusos de esta característica en Python? Un buen uso que conozco es el uso de PLY de la docstring para asociar una regla de sintaxis con un método. Pero, ¿qué pasa con los atributos personalizados? ¿Hay buenas razones para usarlos?

+1

¿Es muy sorprendente? En general, los objetos de Python admiten atributos ad-hoc. Por supuesto, algunos no, especialmente aquellos con tipo integrado. Para mí, aquellos que no apoyan esto parecen ser las excepciones, no la regla. – allyourcode

+3

Una aplicación en Django: [Personalice la lista de cambios del administrador] (https://docs.djangoproject.com/en/1.6/intro/tutorial02/#customize-the-admin-change-list) –

+1

Consulte [PEP 232] (http://www.python.org/dev/peps/pep-0232/). – user140352

Respuesta

118

Normalmente utilizo atributos de función como almacenamiento para anotaciones. Supongamos que yo quiero escribir, al estilo de C# (lo que indica que un determinado método debe ser parte de la interfaz de servicios web)

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

entonces puedo definir

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

Entonces, cuando una llamada de servicio web llega, busco el método, verifico si la función subyacente tiene el atributo is_webmethod (el valor real es irrelevante), y rechazo el servicio si el método está ausente o no para ser llamado a través de la web.

+2

¿Crees que hay desventajas en esto? p.ej. ¿Qué sucede si dos bibliotecas intentan escribir el mismo atributo ad-hoc? – allyourcode

+5

Estaba pensando en hacer exactamente esto. Entonces me detuve. "¿Es una mala idea?" Me preguntaba. Entonces, me acerqué a SO. Después de algunas torpezas, encontré esta pregunta/respuesta. Todavía no estoy seguro si esta es una buena idea. – allyourcode

+5

Este es definitivamente el uso más legítimo de los atributos de función de todas las respuestas (a partir de noviembre de 2012). La mayoría (si no todas) las otras respuestas usan atributos de función como reemplazo de variables globales; sin embargo, NO eliminan el estado global, que es exactamente el problema con las variables globales. Esto es diferente, porque una vez que se establece el valor, no cambia; es constante. Una buena consecuencia de esto es que no se encuentran con problemas de sincronización, que son inherentes a las variables globales. Sí, puede proporcionar su propia sincronización, pero ese es el punto: no es seguro automáticamente. – allyourcode

94

Los he usado como variables estáticas para una función. Por ejemplo, dado el siguiente código C:

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

que puede implementar la función de manera similar en Python:

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

Esto sin duda caer en la final "abusos" del espectro.

+1

Interesante. ¿Hay otras formas de implementar variables estáticas en python? –

+0

También puede usar un sistema global, pero luego el valor está disponible ... bueno, globalmente. – mipadi

+3

-1, esto se implementaría con un generador en python. – hop

8

atributos de función se pueden utilizar para escribir cierres ligeros que el código de envoltura y los datos asociados entre sí:

#!/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

+1

¿Por qué pulsar funciones cuando tenemos clases útiles? Y no olvidemos que las clases pueden emular una función. – muhuk

+1

también 'time.sleep (1)' es mejor que 'os.system ('sleep 1')' –

+1

@bgbg Es cierto, aunque este ejemplo no se trata de dormir. – allyourcode

3

A veces uso un atributo de una función para el almacenamiento en caché de valores ya computados. También puede tener un decorador genérico que generalice este enfoque. ¡Tenga en cuenta los problemas de concurrencia y los efectos secundarios de tales funciones!

36

Usted puede hacer objetos de la manera de JavaScript ... No tiene ningún sentido, pero funciona;)

>>> def FakeObject(): 
... def test(): 
...  print "foo" 
... FakeObject.test = test 
... return FakeObject 
>>> x = FakeObject() 
>>> x.test() 
foo 
+24

+1 Un buen ejemplo de abuso de esta característica, que es una de las cosas que la pregunta solicitó. –

+1

¿En qué se diferencia esto de la respuesta de mipadi? Parece ser lo mismo, excepto que en lugar de un int, el valor del atributo es una función. – allyourcode

+0

es 'def test()' realmente necesario? –

1

Siempre fui de la suposición de que fue así que había un lugar lógico la única razón de que esto era posible para poner una cadena de documentación u otras cosas similares. Sé que si lo usara para cualquier código de producción, confundiría a la mayoría de quienes lo leen.

+1

Estoy de acuerdo con su punto principal acerca de que esto probablemente sea confuso, pero re docstrings: Sí, pero ¿por qué las funciones tienen atributos AD-HOC? Podría haber un conjunto fijo de atributos, uno para mantener el docstring. – allyourcode

8

que ellos usan con moderación, pero pueden ser muy conveniente:

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

ahora puedo usar log lo largo de mi módulo y redirigir la salida simplemente estableciendo log.logfile. Hay muchas y muchas otras maneras de lograr eso, pero este es ligero y sucio. Y aunque olía raro la primera vez que lo hice, he llegado a creer que huele mejor que tener una variable global logfile.función

+4

re smell: sin embargo, esto no elimina el archivo de registro global. Simplemente lo arranca en otro global, la función de registro. – allyourcode

+0

@allyourcode: Pero puede ayudar a evitar conflictos de nombres si tiene que tener un montón de archivos de registro globales para diferentes funciones en el mismo módulo. – firegurafiku

0

He creado este decorador de ayuda para configurar fácilmente los atributos:

caso
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 uso es para crear una colección de fábricas y consultar el tipo de datos que se pueden crear en un nivel meta función.
Por ejemplo (uno muy tonto):

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

¿Cómo ayuda el decorador? ¿Por qué no simplemente establecer 'factory1.datatype = list' justo debajo de la declaración como decoradores de estilo antiguo? –

+0

2 principales diferencias: estilo, múltiples atributos son más fáciles de configurar. Definitivamente puede establecer como un atributo pero obtiene detallado con múltiples atributos en mi opinión y también tiene la oportunidad de ampliar el decorador para su posterior procesamiento (como tener valores predeterminados definidos en un lugar en lugar de todos los lugares que utilizan la función o tener que llamar a una función extra después de establecer los atributos). Hay otras maneras de lograr todos estos resultados, solo me parece que esto es más limpio, pero estoy feliz de cambiar de opinión;) – DiogoNeves

Cuestiones relacionadas