Atributos de función de Python-usos y abusos [cerrado]


No muchos son conscientes de esta característica, pero las funciones (y métodos) de Python pueden tener atributos. He aquí:

>>> 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 del docstring para asociar una regla de sintaxis con un método. Pero, ¿qué pasa con los atributos personalizados ? ¿Hay buenas razones para usarlos ?

Author: Eli Bendersky, 2008-12-03

8 answers

Normalmente uso atributos de función como almacenamiento para anotaciones. Supongamos que quiero escribir, en el estilo de C# (indicando que un cierto método debe ser parte de la interfaz del servicio web)

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

Entonces puedo definir

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

Luego, cuando llega una llamada a un servicio web, busco el método, compruebo 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 está destinado a ser llamado sobre el web.

 130
Author: Martin v. Löwis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-03 18:06:51

Las 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;
}

Puedo implementar la función de manera similar en Python:

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

Esto definitivamente caería en el extremo de los "abusos" del espectro.

 108
Author: mipadi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-04 22:01:40

Puede hacer objetos de la manera JavaScript... No tiene sentido, pero funciona ;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
 42
Author: defnull,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-04 13:53:06

Los uso con moderación, pero pueden ser bastante convenientes:

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

Ahora puedo usar log a lo largo de mi módulo, y redirigir la salida simplemente configurando log.logfile. Hay muchas y muchas otras formas de lograr eso, pero esta es liviana y muy simple. Y aunque olía raro la primera vez que lo hice, he llegado a creer que huele mejor que tener una variable global logfile.

 12
Author: Robert Rossney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-07 18:20:06

Los atributos de función se pueden usar para escribir cierres ligeros que envuelven código y datos asociados juntos:

#!/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
Author: Kevin Little,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-03 20:27:41

A veces uso un atributo de una función para almacenar en caché valores ya calculados. 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!

 3
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-04 08:43:52

Siempre asumí que la única razón por la que esto era posible era que había un lugar lógico para poner una cadena de documentos u otras cosas similares. Sé que si lo usara para cualquier código de producción confundiría a la mayoría de los que lo leen.

 1
Author: Dale Reidy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-17 14:21:56

He creado este decorador auxiliar para establecer fácilmente atributos de función:

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 caso de uso es crear una colección de fábricas y consultar el tipo de datos que pueden crear a un nivel meta de 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
Author: DiogoNeves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-26 18:50:05