Attributi della funzione Python - usa e abusa [chiuso]


196

Non molti sono a conoscenza di questa funzionalità, ma le funzioni (e i metodi) di Python possono avere attributi . Ecco:

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

Quali sono i possibili usi e abusi di questa funzione in Python? Un buon uso di cui sono a conoscenza è l'uso da parte di PLY del docstring per associare una regola di sintassi a un metodo. Ma per quanto riguarda gli attributi personalizzati? Ci sono buoni motivi per usarli?


3
Dai un'occhiata a PEP 232 .
user140352

2
È molto sorprendente? In generale, gli oggetti Python supportano attributi ad-hoc. Certo, alcuni no, in particolare quelli con tipo incorporato. Per me, quelli che non lo supportano sembrano essere le eccezioni, non la regola.
codice alleato


2
@GrijeshChauhan Sono arrivato a questa domanda dopo aver visto questi documenti!
Alexander Suraphel,

5
Peccato che questo sia chiuso, volevo aggiungere che è possibile allegare eventuali eccezioni personalizzate che la funzione potrebbe sollevare, per fornire un facile accesso quando viene rilevato nel codice chiamante. Vorrei fornire un esempio illustrativo, ma è meglio farlo in una risposta.
Will Hardy,

Risposte:


154

In genere utilizzo gli attributi della funzione come memoria per le annotazioni. Supponiamo di voler scrivere, nello stile di C # (indicando che un determinato metodo dovrebbe far parte dell'interfaccia del servizio Web)

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

allora posso definire

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

Quindi, quando arriva una chiamata al servizio web, cerco il metodo, controllo se la funzione sottostante ha l'attributo is_webmethod (il valore effettivo è irrilevante) e rifiuto il servizio se il metodo è assente o non è destinato a essere chiamato sul web.


2
Pensi che ci siano degli aspetti negativi in ​​questo? es. Cosa succede se due librerie provano a scrivere lo stesso attributo ad hoc?
codice alleato

18
Stavo pensando di fare esattamente questo. Poi mi sono fermato. "È una cattiva idea?" Mi chiedevo. Poi, mi sono avvicinato a SO. Dopo un po 'di confusione, ho trovato questa domanda / risposta. Non sono ancora sicuro che sia una buona idea.
codice alleato

7
Questo è sicuramente l'uso più legittimo degli attributi di funzione di tutte le risposte (a partire da novembre 2012). La maggior parte (se non tutte) le altre risposte usano gli attributi di funzione in sostituzione di variabili globali; tuttavia, NON si liberano dello stato globale, che è esattamente il problema con le variabili globali. Questo è diverso, perché una volta impostato il valore, non cambia; è costante. Una buona conseguenza di ciò è che non si verificano problemi di sincronizzazione, che sono inerenti alle variabili globali. Sì, puoi fornire la tua sincronizzazione, ma questo è il punto: non è sicuro automaticamente.
codice alleato

Anzi, dico, fintanto che l'attributo non cambia il comportamento della funzione in questione, è buono. Confronta con.__doc__
Dima Tisnek,

Questo approccio può anche essere usato per associare la descrizione dell'output alla funzione decorata, che manca in Python 2. *.
Giovedì

126

Li ho usati come variabili statiche per una funzione. Ad esempio, dato il seguente codice C:

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

Posso implementare la funzione in modo simile in Python:

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

Ciò finirebbe sicuramente con la fine degli "abusi" dello spettro.


2
Interessante. Ci sono altri modi per implementare variabili statiche in Python?
Eli Bendersky,

4
-1, questo sarebbe implementato con un generatore in Python.

124
Questa è una ragione piuttosto scadente per sottovalutare questa risposta, che sta dimostrando un'analogia tra C e Python, non sostenendo il modo migliore per scrivere questa particolare funzione.
Robert Rossney,

3
@RobertRossney Ma se i generatori sono la strada da percorrere, allora questo è un cattivo uso degli attributi della funzione. Se è così, allora questo è un abuso. Non sono sicuro se votare gli abusi, dato che la domanda richiede anche quelli: P
allyourcode

1
Sicuramente sembra un abuso, secondo PEP 232, grazie @ user140352, e il commento di hop e questa risposta SO
piani cottura

53

Puoi fare oggetti nel modo JavaScript ... Non ha senso ma funziona;)

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

36
+1 Un bell'esempio di abuso di questa funzione, che è una delle cose poste dalla domanda.
Michael Dunn,

1
In che modo differisce dalla risposta di mipadi? Sembra essere la stessa cosa, tranne che invece di un int, il valore dell'attributo è una funzione.
codice alleato

è def test()davvero necessario?
Keerthana Prabhakaran,

15

Li uso con parsimonia, ma possono essere piuttosto convenienti:

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

Ora posso usare logtutto il mio modulo e reindirizzare l'output semplicemente impostando log.logfile. Ci sono molti altri modi per farlo, ma questo è leggero e sporco. E mentre aveva un odore divertente la prima volta che l'ho fatto, sono arrivato a credere che abbia un odore migliore che avere una logfilevariabile globale .


7
ri odore: questo però non elimina il file di registro globale. Lo fa semplicemente scoppiare in un altro globale, la funzione log.
codice alleato

2
@allyourcode: può essere d'aiuto evitare gli scontri con i nomi se devi avere un sacco di file di log globali per funzioni diverse nello stesso modulo.
firegurafiku,

11

Gli attributi di funzione possono essere utilizzati per scrivere chiusure leggere che racchiudono codice e dati associati:

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


3
Perché spingere le funzioni quando abbiamo le lezioni a portata di mano? E non dimentichiamo che le classi possono emulare una funzione.
Muhuk,

1
inoltre time.sleep(1)è meglio dios.system('sleep 1')
Boris Gorelik il

3
@bgbg Vero, anche se questo esempio non riguarda il dormire.
codice alleato

Questo è sicuramente un abuso; l'uso delle funzioni qui è totalmente gratuito. muhuk ha esattamente ragione: le classi sono una soluzione migliore.
codice alleato

1
Vorrei anche chiedere: "Qual è il vantaggio di questo rispetto a una lezione?" per contrastare lo svantaggio di questo non essere così ovvio per molti programmatori Python.
cjs

6

Ho creato questo helper decorator per impostare facilmente gli attributi della funzione:

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 d'uso è creare una raccolta di fabbriche e interrogare il tipo di dati che possono creare a un livello meta funzione.
Ad esempio (molto stupido):

@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

In che modo aiuta il decoratore? Perché non impostare factory1.datatype=listproprio sotto la dichiarazione come decoratori di vecchio stile?
Ceasar Bautista,

2
2 differenze principali: stile, più attributi sono più facili da impostare. Puoi sicuramente impostare come attributo ma diventa dettagliato con più attributi secondo me e hai anche l'opportunità di estendere il decoratore per ulteriori elaborazioni (come avere impostazioni predefinite definite in un posto invece di tutti i luoghi che usano la funzione o dover chiamare una funzione aggiuntiva dopo aver impostato gli attributi). Ci sono altri modi per ottenere tutti questi risultati, trovo che questo sia più pulito, ma felice di cambiare idea;)
DiogoNeves,

Aggiornamento rapido: con Python 3 devi usare items()invece di iteritems().
Scott:

4

A volte utilizzo un attributo di una funzione per memorizzare nella cache valori già calcolati. Puoi anche avere un decoratore generico che generalizza questo approccio. Fai attenzione ai problemi di concorrenza e agli effetti collaterali di tali funzioni!


Mi piace questa idea! Un trucco più comune per la memorizzazione nella cache dei valori calcolati consiste nell'utilizzare un dict come valore predefinito di un attributo che il chiamante non ha mai previsto di fornire, poiché Python valuta che solo una volta durante la definizione della funzione, è possibile archiviare i dati e tenerli bloccati in giro. Mentre l'uso degli attributi di funzione potrebbe essere meno ovvio, mi sembra significativamente meno confuso per me.
Soren Bjornstad,

1

Sono sempre stato del presupposto che l'unica ragione per cui ciò era possibile era quindi c'era un posto logico per mettere una stringa di documenti o altre cose del genere. So che se lo usassi per qualsiasi codice di produzione, confonderebbe la maggior parte di chi lo legge.


1
Sono d'accordo con il tuo punto principale sul fatto che molto probabilmente è confuso, ma ri-docstrings: Sì, ma perché le funzioni hanno attributi AD-HOC? Potrebbe esserci un set fisso di attributi, uno per contenere il docstring.
codice alleato

@allyourcode Avere il caso generale piuttosto che casi specifici ad hoc progettati nel linguaggio rende le cose più semplici e aumenta la compatibilità con le versioni precedenti di Python. (Ad esempio, il codice che imposta / manipola i docstring funzionerà comunque con una versione di Python che non fa i docstring, purché gestisca il caso in cui l'attributo non esiste.)
cjs
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.