Python-Funktionsattribute - verwendet und missbraucht [geschlossen]

195

Nicht viele kennen diese Funktion, aber Pythons Funktionen (und Methoden) können Attribute haben . Erblicken:

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

Was sind die möglichen Verwendungen und Missbräuche dieser Funktion in Python? Eine gute Verwendung, die mir bekannt ist , ist die Verwendung der Dokumentzeichenfolge durch PLY , um einer Methode eine Syntaxregel zuzuordnen. Aber was ist mit benutzerdefinierten Attributen? Gibt es gute Gründe, sie zu benutzen?

Eli Bendersky
quelle
3
Schauen Sie sich PEP 232 an .
user140352
2
Ist das sehr überraschend? Im Allgemeinen unterstützen Python-Objekte Ad-hoc-Attribute. Natürlich tun dies einige nicht, insbesondere solche mit eingebautem Typ. Für mich scheinen diejenigen, die dies nicht unterstützen, die Ausnahmen zu sein, nicht die Regel.
Allyourcode
3
Eine Anwendung in Django: Passen Sie die Administrator-Änderungsliste an
Grijesh Chauhan
2
@GrijeshChauhan Ich bin zu dieser Frage gekommen, nachdem ich diese Dokumente gesehen habe!
Alexander Suraphel
5
Schade, dass dies geschlossen ist, wollte ich hinzufügen, dass Sie alle benutzerdefinierten Ausnahmen anhängen können, die die Funktion möglicherweise auslöst, um einen einfachen Zugriff zu ermöglichen, wenn Sie sie im aufrufenden Code abfangen. Ich würde ein anschauliches Beispiel liefern, aber das geschieht am besten in einer Antwort.
Will Hardy

Antworten:

153

Normalerweise verwende ich Funktionsattribute als Speicher für Anmerkungen. Angenommen, ich möchte im Stil von C # schreiben (was darauf hinweist, dass eine bestimmte Methode Teil der Webdienstschnittstelle sein sollte).

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

dann kann ich definieren

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

Wenn dann ein Webservice-Aufruf eintrifft, schaue ich nach der Methode, überprüfe, ob die zugrunde liegende Funktion das Attribut is_webmethod hat (der tatsächliche Wert ist irrelevant), und lehne den Service ab, wenn die Methode fehlt oder nicht über das Web aufgerufen werden soll.

Martin v. Löwis
quelle
2
Denken Sie, dass dies Nachteile hat? Beispiel: Was ist, wenn zwei Bibliotheken versuchen, dasselbe Ad-hoc-Attribut zu schreiben?
Allyourcode
18
Ich dachte daran, genau das zu tun. Dann habe ich mich gestoppt. "Ist das eine schlechte Idee?" Ich fragte mich. Dann ging ich zu SO hinüber. Nach einigem Hin und Her fand ich diese Frage / Antwort. Ich bin mir immer noch nicht sicher, ob dies eine gute Idee ist.
Allyourcode
7
Dies ist definitiv die legitimste Verwendung von Funktionsattributen aller Antworten (Stand November 2012). Die meisten (wenn nicht alle) anderen Antworten verwenden Funktionsattribute als Ersatz für globale Variablen. Sie werden jedoch NICHT den globalen Status los, was genau das Problem bei globalen Variablen ist. Dies ist anders, da sich der einmal festgelegte Wert nicht mehr ändert. es ist konstant. Eine schöne Folge davon ist, dass Sie nicht auf Synchronisationsprobleme stoßen, die globalen Variablen inhärent sind. Ja, Sie können Ihre eigene Synchronisation bereitstellen, aber das ist der Punkt: Es ist nicht automatisch sicher.
Allyourcode
In der Tat sage ich, solange das Attribut das Verhalten der betreffenden Funktion nicht ändert, ist es gut. Vergleiche mit.__doc__
Dima Tisnek
Dieser Ansatz kann auch verwendet werden, um eine Ausgabebeschreibung an die dekorierte Funktion anzuhängen, die in Python 2 fehlt. *.
Juh_
125

Ich habe sie als statische Variablen für eine Funktion verwendet. Beispiel: Geben Sie den folgenden C-Code ein:

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

Ich kann die Funktion in Python ähnlich implementieren:

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

Dies würde definitiv in das Ende des Spektrums "Missbrauch" fallen.

Mipadi
quelle
2
Interessant. Gibt es andere Möglichkeiten, statische Variablen in Python zu implementieren?
Eli Bendersky
4
-1, dies würde mit einem Generator in Python implementiert.
124
Das ist ein ziemlich schlechter Grund, diese Antwort abzulehnen, die eine Analogie zwischen C und Python demonstriert und nicht die bestmögliche Art und Weise befürwortet, diese bestimmte Funktion zu schreiben.
Robert Rossney
3
@RobertRossney Aber wenn Generatoren der richtige Weg sind, dann ist dies eine schlechte Verwendung von Funktionsattributen. Wenn ja, dann ist dies ein Missbrauch. Ich bin mir nicht sicher, ob ich Missbräuche positiv bewerten soll, da die Frage auch nach diesen fragt: P
allyourcode
1
Scheint definitiv ein Missbrauch zu sein, laut PEP 232, danke @ user140352, und Hops Kommentar und diese SO-Antwort
Kochfelder
52

Sie können Objekte auf JavaScript-Weise erstellen ... Es macht keinen Sinn, aber es funktioniert;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
defnull
quelle
36
+1 Ein gutes Beispiel für einen Missbrauch dieser Funktion. Dies ist eines der Dinge, nach denen die Frage gestellt wurde.
Michael Dunn
1
Wie unterscheidet sich das von Mipadis Antwort? Scheint dasselbe zu sein, außer dass der Attributwert anstelle eines int eine Funktion ist.
Allyourcode
ist def test()wirklich notwendig?
Keerthana Prabhakaran
15

Ich benutze sie sparsam, aber sie können ziemlich praktisch sein:

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

Jetzt kann ich logmein Modul verwenden und die Ausgabe einfach durch Einstellen umleiten log.logfile. Es gibt viele, viele andere Möglichkeiten, dies zu erreichen, aber dieses ist leicht und schmutzig einfach. Und obwohl es beim ersten Mal komisch roch, habe ich geglaubt, dass es besser riecht als eine globale logfileVariable.

Robert Rossney
quelle
7
Re Geruch: Dadurch wird die globale Protokolldatei jedoch nicht entfernt. Es wird nur in einem anderen globalen Bereich, der Protokollfunktion, entfernt.
Allyourcode
2
@allyourcode: Es kann jedoch helfen, Namenskonflikte zu vermeiden, wenn Sie eine Reihe globaler Protokolldateien für verschiedene Funktionen im selben Modul benötigen.
Firegurafiku
11

Funktionsattribute können verwendet werden, um leichte Verschlüsse zu schreiben, die Code und zugehörige Daten zusammenfassen:

#!/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
quelle
3
Warum Push-Funktionen, wenn wir Klassen zur Hand haben? Und vergessen wir nicht, dass Klassen eine Funktion emulieren können.
Muhuk
1
auch time.sleep(1)ist besser alsos.system('sleep 1')
Boris Gorelik
3
@bgbg Richtig, obwohl es in diesem Beispiel nicht ums Schlafen geht.
Allyourcode
Dies ist definitiv ein Missbrauch; Die Verwendung von Funktionen ist hier völlig unentgeltlich. Muhuk ist genau richtig: Klassen sind eine bessere Lösung.
Allyourcode
1
Ich würde auch fragen: "Was ist der Vorteil davon gegenüber einer Klasse?" um dem Nachteil entgegenzuwirken, dass dies für viele Python-Programmierer nicht so offensichtlich ist.
cjs
6

Ich habe diesen Hilfsdekorator erstellt, um Funktionsattribute einfach festzulegen:

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

Ein Anwendungsfall besteht darin, eine Sammlung von Fabriken zu erstellen und den Datentyp abzufragen, den sie auf Funktionsmetaebene erstellen können.
Zum Beispiel (sehr dumm):

@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
quelle
Wie hilft der Dekorateur? Warum nicht einfach factory1.datatype=listwie Dekorateure alten Stils direkt unter die Erklärung setzen?
Ceasar Bautista
2
2 Hauptunterschiede: Stil, mehrere Attribute sind einfacher festzulegen. Sie können definitiv ein Attribut festlegen, werden aber meiner Meinung nach mit mehreren Attributen ausführlich beschrieben, und Sie haben auch die Möglichkeit, den Dekorator für die weitere Verarbeitung zu erweitern (z. B. wenn Standardwerte an einer Stelle definiert werden, anstatt an allen Stellen, an denen die Funktion verwendet wird oder muss) Rufen Sie eine zusätzliche Funktion auf, nachdem die Attribute festgelegt wurden. Es gibt andere Möglichkeiten, um all diese Ergebnisse zu erzielen. Ich finde das nur sauberer, aber ich ändere gerne meine Meinung;)
DiogoNeves
Schnelles Update: Mit Python 3 müssen Sie items()anstelle von verwenden iteritems().
Scott Means
4

Manchmal verwende ich ein Attribut einer Funktion zum Zwischenspeichern bereits berechneter Werte. Sie können auch einen generischen Dekorateur haben, der diesen Ansatz verallgemeinert. Beachten Sie Parallelitätsprobleme und Nebenwirkungen solcher Funktionen!


quelle
Ich mag diese Idee! Ein häufigerer Trick beim Zwischenspeichern berechneter Werte ist die Verwendung eines Diktats als Standardwert eines Attributs, das der Aufrufer niemals bereitstellen soll. Da Python dies nur einmal beim Definieren der Funktion auswertet, können Sie Daten dort speichern und festhalten um. Die Verwendung von Funktionsattributen ist zwar weniger offensichtlich, fühlt sich für mich jedoch deutlich weniger hackig an.
Soren Bjornstad
1

Ich war immer der Annahme, dass der einzige Grund, warum dies möglich war, darin bestand, dass es einen logischen Ort gab, an dem ein Doc-String oder ähnliches eingefügt werden konnte. Ich weiß, wenn ich es für einen Produktionscode verwenden würde, würde es die meisten, die es lesen, verwirren.

Dale Reidy
quelle
1
Ich stimme Ihrem Hauptpunkt darin zu, dass dies höchstwahrscheinlich verwirrend ist, aber bezüglich der Dokumentationen: Ja, aber warum haben Funktionen AD-HOC-Attribute? Es könnte einen festen Satz von Attributen geben, eines zum Halten der Dokumentzeichenfolge.
Allyourcode
@allyourcode Die Verwendung des allgemeinen Falls anstelle spezifischer Ad-hoc-Fälle in der Sprache vereinfacht die Arbeit und erhöht die Kompatibilität mit älteren Versionen von Python. (Zum Beispiel funktioniert Code, der Docstrings setzt / manipuliert, weiterhin mit einer Version von Python, die Docstrings nicht ausführt, solange er den Fall behandelt, in dem das Attribut nicht vorhanden ist.)
cjs