Atributos da função Python - usos e abusos [fechado]

195

Muitos não estão cientes desse recurso, mas as funções (e métodos) do Python podem ter atributos . Ver:

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

Quais são os possíveis usos e abusos desse recurso no Python? Um bom uso que eu conheço é o uso da doutrina PLY para associar uma regra de sintaxe a um método. Mas e os atributos personalizados? Existem boas razões para usá-los?

Eli Bendersky
fonte
3
Confira o PEP 232 .
User140352
2
Isso é muito surpreendente? Em geral, os objetos Python suportam atributos ad-hoc. Obviamente, alguns não, principalmente aqueles com o tipo embutido. Para mim, aqueles que não apóiam isso parecem ser as exceções, não a regra.
Allyourcode #
3
Uma aplicação em Django: Personalize a listagem do admin
Grijesh Chauhan
2
@GrijeshChauhan Cheguei a esta pergunta depois de ver esses documentos!
Alexander Suraphel
5
Pena que isso esteja encerrado, gostaria de acrescentar que você pode anexar quaisquer exceções personalizadas que a função possa gerar, para fornecer acesso fácil ao capturá-la no código de chamada. Eu daria um exemplo ilustrativo, mas é melhor fazer isso em uma resposta.
Will Hardy

Respostas:

153

Normalmente, uso atributos de função como armazenamento para anotações. Suponha que eu queira escrever, no estilo de C # (indicando que um determinado método deve fazer parte da interface de serviço da web)

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

então eu posso definir

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

Em seguida, quando chega uma chamada de serviço da web, procuro o método, verifico se a função subjacente tem o atributo is_webmethod (o valor real é irrelevante) e recuso o serviço se o método estiver ausente ou não for chamado pela Web.

Martin v. Löwis
fonte
2
Você acha que há desvantagens nisso? eg E se duas bibliotecas tentarem escrever o mesmo atributo ad-hoc?
Allyourcode #
18
Eu estava pensando em fazer exatamente isso. Então eu me parei. "Isso é uma péssima idéia?" Eu pensei. Então, eu vaguei até o SO. Após algumas discussões, encontrei esta pergunta / resposta. Ainda não tenho certeza se é uma boa ideia.
allyourcode
7
Esse é definitivamente o uso mais legítimo dos atributos de função de todas as respostas (em novembro de 2012). A maioria (senão todas) das outras respostas usa atributos de função como substituto de variáveis ​​globais; no entanto, eles NÃO se livram do estado global, que é exatamente o problema com variáveis ​​globais. Isso é diferente porque, uma vez que o valor é definido, ele não muda; é constante. Uma boa conseqüência disso é que você não encontra problemas de sincronização, que são inerentes às variáveis ​​globais. Sim, você pode fornecer sua própria sincronização, mas esse é o ponto: não é seguro automaticamente.
Allyourcode #
Na verdade, eu digo, desde que o atributo não mude o comportamento da função em questão, é bom. Compare com.__doc__
Dima Tisnek
Essa abordagem também pode ser usada para anexar a descrição da saída à função decorada, que está ausente no python 2. *.
Juh_
125

Eu os usei como variáveis ​​estáticas para uma função. Por exemplo, dado o seguinte código C:

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

Eu posso implementar a função da mesma forma em Python:

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

Isso definitivamente se enquadra no "abuso" do fim do espectro.

mipadi
fonte
2
Interessante. Existem outras maneiras de implementar variáveis ​​estáticas em python?
Eli Bendersky
4
-1, isso seria implementado com um gerador em python.
124
Essa é uma razão muito ruim para rebaixar essa resposta, que está demonstrando uma analogia entre C e Python, não defendendo a melhor maneira possível de escrever essa função específica.
9113 Robert Rossney
3
@RobertRossney Mas se os geradores são o caminho a seguir, esse é um mau uso dos atributos das funções. Se sim, então isso é um abuso. No entanto, não tenho certeza se os
votos serão positivos
1
Definitivamente, parece um abuso, de acordo com o PEP 232, obrigado @ user140352, e o comentário de hop e essa resposta SO
hobs
52

Você pode fazer objetos da maneira JavaScript ... Não faz sentido, mas funciona;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
defnull
fonte
36
+1 Um bom exemplo de abuso desse recurso, que é uma das coisas que a pergunta foi feita.
Michael Dunn
1
Como isso difere da resposta de mipadi? Parece ser a mesma coisa, exceto em vez de um int, o valor do atributo é uma função.
allyourcode
é def test()realmente necessário?
Keerthana Prabhakaran
15

Eu os uso com moderação, mas eles podem ser bastante convenientes:

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

Agora eu posso usar em logtodo o meu módulo e redirecionar a saída simplesmente configurando log.logfile. Existem muitas outras maneiras de conseguir isso, mas essa é leve e simples. E embora tenha cheirado engraçado na primeira vez que fiz isso, acredito que cheira melhor do que ter uma logfilevariável global .

Robert Rossney
fonte
7
cheiro: Isso não se livra do arquivo de log global. Apenas o esquiva em outra função global de log.
allyourcode
2
@allourcode: Mas pode ajudar a evitar conflitos de nome se você precisar de um monte de arquivos de log globais para diferentes funções no mesmo módulo.
Firegurafiku
11

Os atributos de função podem ser usados ​​para gravar fechamentos leves que agrupam código e dados associados:

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

Kevin Little
fonte
3
Por que empurrar funções quando temos aulas à mão? E não vamos esquecer que as classes podem emular uma função.
muhuk
1
também time.sleep(1)é melhor queos.system('sleep 1')
Boris Gorelik
3
@bgbg É verdade, embora este exemplo não seja sobre dormir.
Allyourcode #
Definitivamente, é um abuso; o uso de funções aqui é totalmente gratuito. O muhuk está exatamente certo: as aulas são uma solução melhor.
allyourcode
1
Eu também perguntava: "Qual é a vantagem disso em relação a uma aula?" contrariar a desvantagem de não ser tão óbvio para muitos programadores de Python.
CJS
6

Eu criei este decorador auxiliar para definir facilmente os atributos das funções:

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

Um caso de uso é criar uma coleção de fábricas e consultar o tipo de dados que eles podem criar no nível meta de função.
Por exemplo (muito idiota):

@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
DiogoNeves
fonte
Como o decorador ajuda? Por que não colocar factory1.datatype=listlogo abaixo da declaração como decoradores de estilo antigo?
Ceasar Bautista
2
2 principais diferenças: estilo, vários atributos são mais fáceis de definir. Definitivamente, você pode definir como um atributo, mas é detalhado com vários atributos, na minha opinião, e também tem a oportunidade de estender o decorador para processamento adicional (como definir padrões em um local em vez de todos os locais que usam a função ou chamar uma função extra depois que os atributos foram definidos). Há outras maneiras de conseguir todos esses resultados, eu só acho que isso seja mais limpo, mas feliz para mudar minha mente;)
DiogoNeves
Atualização rápida: com o Python 3 você precisa usar em items()vez de iteritems().
Scott significa
4

Às vezes, uso um atributo de uma função para armazenar em cache valores já calculados. Você também pode ter um decorador genérico que generalize essa abordagem. Esteja ciente de problemas de simultaneidade e efeitos colaterais de tais funções!


fonte
Eu gosto desta ideia! Um truque mais comum para armazenar em cache valores calculados é usar um dict como o valor padrão de um atributo que o chamador nunca deve fornecer - uma vez que o Python avalia que apenas uma vez ao definir a função, você pode armazenar dados e mantê-los por aí. Embora o uso de atributos de função possa ser menos óbvio, parece significativamente menos invasivo para mim.
Soren Bjornstad 9/08/19
1

Eu sempre assumi que a única razão pela qual isso era possível era que havia um lugar lógico para colocar uma sequência de documentos ou outras coisas desse tipo. Eu sei que se eu o usasse para qualquer código de produção, confundiria a maioria dos que o liam.

Dale Reidy
fonte
1
Eu concordo com o seu ponto principal sobre o fato de isso provavelmente ser confuso, mas replica: Sim, mas por que as funções têm atributos AD-HOC? Pode haver um conjunto fixo de atributos, um para armazenar a sequência de documentos.
allyourcode
@allyourcode Ter o caso geral em vez de casos ad-hoc específicos projetados na linguagem simplifica as coisas e aumenta a compatibilidade com versões mais antigas do Python. (Por exemplo, o código que define / manipula docstrings continuará a funcionar com uma versão do Python que não faz docstrings, enquanto ele lida com o caso em que o atributo não existe.)
CJS