1331 Stimmen

Wie funktioniert der @property-Dekorator in Python?

Ich würde gerne verstehen, wie die eingebaute Funktion property funktioniert. Was mich verwirrt, ist dass property auch als Dekorator verwendet werden kann, aber nur Argumente akzeptiert, wenn es als eingebaute Funktion verwendet wird und nicht wenn es als Dekorator verwendet wird.

Dieses Beispiel stammt aus der Dokumentation:

class C:
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "Ich bin die 'x' Eigenschaft.")

Die Argumente von property sind getx, setx, delx und eine Dokumentationszeichenkette.

In dem folgenden Code wird property als Dekorator verwendet. Das Objekt davon ist die Funktion x, aber im obigen Code gibt es keinen Platz für eine Objektfunktion in den Argumenten.

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """Ich bin die 'x' Eigenschaft."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Wie werden die Dekoratoren x.setter und x.deleter in diesem Fall erstellt?

10voto

prosti Punkte 34344

Property ist eine Klasse hinter dem @property Decorator.

Sie können dies immer überprüfen:

print(property) #

Ich habe das Beispiel von help(property) umgeschrieben, um zu zeigen, dass die @property Syntax

class C:
    def __init__(self):
        self._x=None

    @property 
    def x(self):
        return self._x

    @x.setter 
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

c = C()
c.x="a"
print(c.x)

funktional identisch ist mit der property() Syntax:

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

Es gibt keinen Unterschied, wie wir die Property verwenden, wie Sie sehen können.

Um die Frage zu beantworten, wird der @property Decorator über die property Klasse implementiert.


Also, die Frage besteht darin, die property Klasse etwas zu erklären. Diese Zeile:

prop = property(g,s,d)

war die Initialisierung. Wir können es wie folgt neu schreiben:

prop = property(fget=g,fset=s,fdel=d)

Die Bedeutung von fget, fset und fdel:

 |    fget
 |      Funktion zum Abrufen eines Attributwerts
 |    fset
 |      Funktion zum Setzen eines Attributwerts
 |    fdel
 |      Funktion zum Löschen eines Attributs
 |    doc
 |      Dokumentationsstring

Das nächste Bild zeigt die Triplets, die wir von der Klasse property haben:

Geben Sie hier eine Bildbeschreibung ein

__get__, __set__ und __delete__ sind dafür gedacht, überladen zu werden. Dies ist die Implementierung des Deskriptor-Musters in Python.

Allgemein gesprochen ist ein Deskriptor ein Objektattribut mit "Bindungsverhalten", dessen Attributzugriff durch Methoden im Deskriptor-Protokoll überschrieben wurde.

Wir können auch die Property setter, getter und deleter Methoden verwenden, um die Funktion an die Property zu binden. Überprüfen Sie das nächste Beispiel. Die Methode s2 der Klasse C wird die Property doubled setzen.

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x

    x=property(g)
    x=x.setter(s)
    x=x.deleter(d)      

c = C()
c.x="a"
print(c.x) # gibt "a" aus

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # gibt "aa" aus

5voto

Yilmaz Punkte 12859

Ein Decorator ist eine Funktion, die eine Funktion als Argument nimmt und eine Closure zurückgibt. Die Closure ist eine Gruppe von inneren Funktionen und Freivariable. Die innere Funktion schließt über die Freivariable ab und deshalb wird sie 'Closure' genannt. Eine Freivariable ist eine Variable, die außerhalb der inneren Funktion liegt und über den Decorator in die innere Funktion übergeben wird.

Wie der Name schon sagt, dekoriert der Decorator die empfangene Funktion.

function decorator(undecorated_func):
    print("Aufruf der Decorator-Funktion")
    inner():
       print("Ich bin innerhalb von inner")
       return undecorated_func
    return inner

Dies ist eine einfache Dekoratorfunktion. Sie erhält "undecorated_func" und übergibt sie als Freivariable an inner(), inner() druckt "Ich bin innerhalb von inner" und gibt undecorated_func zurück. Wenn wir decorator(undecorated_func) aufrufen, wird inner zurückgegeben. Hier ist der Schlüssel, in Dekoratoren benennen wir die innere Funktion als den Namen der Funktion, die wir übergeben haben.

   undecorated_function= decorator(undecorated_func) 

Jetzt wird die innere Funktion als "undecorated_func" bezeichnet. Da inner nun als "undecorated_func" benannt ist, haben wir "undecorated_func" an den Decorator übergeben und "undecorated_func" plus "Ich bin innerhalb von inner" zurückgegeben. Dieser Druck hat unsere "undecorated_func" dekoriert.

Nun definieren wir eine Klasse mit einem Property-Dekorator:

class Person:
    def __init__(self,name):
        self._name=name
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self.value):
        self._name=value

Wenn wir name() mit @property() dekoriert haben, ist dies passiert:

name=property(name) # Person.__dict__ Sie werden name sehen 

Das erste Argument von property() ist der Getter. Dies ist das, was in der zweiten Dekoration passiert ist:

   name=name.setter(name) 

Wie ich oben erwähnt habe, gibt der Dekorator die innere Funktion zurück, und wir benennen die innere Funktion mit dem Namen der Funktion, die wir übergeben haben.

Hier ist etwas Wichtiges zu beachten. "name" ist unveränderlich. In der ersten Dekoration haben wir dies erhalten:

  name=property(name)

Im zweiten haben wir dies erhalten

  name=name.setter(name)

Wir ändern das name-Objekt nicht. In der zweiten Dekoration sieht Python, dass dies ein Property-Objekt ist und es bereits einen Getter hatte. Deshalb erstellt Python ein neues "name"-Objekt, fügt den "fget" aus dem ersten Objekt hinzu und setzt dann den "fset".

2voto

nvd Punkte 429

Ein Eigenschaft kann auf zwei Arten deklariert werden.

  • Erstellen der Getter- und Setter-Methoden für ein Attribut und Übergeben dieser als Argument an die Eigenschaftsfunktion
  • Verwenden des @Eigenschaft Dekorateurs.

Sie können sich ein paar Beispiele anschauen, die ich über Eigenschaften in Python geschrieben habe.

1voto

Masoud Gheisari Punkte 658

Im Folgenden habe ich ein Beispiel gegeben, um @property zu verdeutlichen

Betrachten Sie eine Klasse mit dem Namen Student mit zwei Variablen: name und class_number und Sie möchten, dass class_number im Bereich von 1 bis 5 liegt.

Jetzt erkläre ich zwei falsche Lösungen und schließlich die richtige:


Der folgende Code ist falsch, weil er die class_number nicht validiert (um im Bereich von 1 bis 5 zu liegen)

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = class_number

Trotz Validierung ist auch diese Lösung falsch:

def validate_class_number(number):
    if 1 <= number <= 5:
        return number
    else:
        raise Exception("Klassenzahl sollte im Bereich von 1 bis 5 liegen")

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = validate_class_number(class_number)

Weil die Validierung von class_number nur zum Zeitpunkt der Klasseninstanziierung überprüft wird und danach nicht mehr überprüft wird (es ist möglich, die class_number mit einer Zahl außerhalb des Bereichs von 1 bis 5 zu ändern):

student1 = Student("Masoud", 5)
student1.class_number = 7

Die korrekte Lösung ist:

class Student:
    def __init__(self, name, class_number):
        self.name = name
        self.class_number = class_number

    @property
    def class_number(self):
        return self._class_number

    @class_number.setter
    def class_number(self, class_number):
        if not (1 <= class_number <= 5): raise Exception("Klassenzahl sollte im Bereich von 1 bis 5 liegen")
        self._class_number = class_number

0voto

Acecool Punkte 624

Hier ist ein weiteres Beispiel:

##
## Python-Eigenschaftenbeispiel
##
class GetterSetterBeispiel( object ):
    ## Setzen Sie den Standardwert für x (wir verweisen darauf, indem wir self.x verwenden, setzen einen Wert mit self.x = Wert )
    __x = None

##
## Bei Klasseninitialisierung - etwas tun... wenn wir wollen..
##
def __init__( self ):
    ## Setzen Sie einen Wert für __x über den Getter / Setter... Da __x oben definiert ist, muss dies nicht gesetzt werden...
    self.x = 1234

    return None

##
## Definieren Sie x als Eigenschaft, d.h. als Getter - Alle Getter sollten ein Standardwert-Argument haben, daher habe ich es hinzugefügt - es wird nicht übergeben, wenn ein Wert festgelegt wird, daher müssen Sie es hier festlegen, damit es verwendet wird..
##
@property
def x( self, _default = None ):
    ## Ich habe ein optionales Standardwertargument hinzugefügt, da alle Getter dies haben sollten - setzen Sie es auf den Standardwert, den Sie zurückgeben möchten...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - damit Sie die Reihenfolge der Aufrufe sehen können...
    print( '[ Testklasse ] Get x = ' + str( _value ) )

    ## Gib den Wert zurück - schließlich sind wir ein Getter...
    return _value

##
## Definieren Sie die Setter-Funktion für x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - damit Sie die Reihenfolge der Aufrufe sehen können...
    print( '[ Testklasse ] Setzen x = ' + str( _value ) )

    ## Dies soll zeigen, dass die Setter-Funktion funktioniert.... Wenn der Wert über 0 liegt, setzen Sie ihn auf einen negativen Wert... andernfalls behalten Sie ihn bei ( 0 ist die einzige nicht-negative Zahl, sie kann weder negativ noch positiv sein )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value

##
## Definieren Sie die Löschfunktion für x...
##
@x.deleter
def x( self ):
    ## Entladen Sie die Zuweisung / Daten für x
    if ( self.__x != None ):
        del self.__x

##
## Zu Zeichenfolge / Ausgabefunktion für die Klasse - diese zeigt den Eigenschaftswert für jede hinzugefügte Eigenschaft an...
##
def __str__( self ):
    ## Gib die x-Eigenschaftsdaten aus...
    print( '[ x ] ' + str( self.x ) )

    ## Gib eine neue Zeile zurück - technisch gesehen sollten wir eine Zeichenfolge zurückgeben, damit sie dort gedruckt werden kann, wo wir sie möchten, anstatt frühzeitig gedruckt zu werden, wenn _data = str( C( ) ) verwendet wird....
    return '\n'

##
##
##
_test = GetterSetterBeispiel( )
print( _test )

## Aus irgendeinem Grund wird der Deleter nicht aufgerufen...
del _test.x

Grundsätzlich dasselbe wie das C( object )-Beispiel, außer dass ich x verwende... Außerdem initialisiere ich nicht in __init - ... nun.. ich mache es schon, aber es kann entfernt werden, weil __x als Teil der Klasse definiert ist....

Die Ausgabe ist:

[ Testklasse ] Setzen x = 1234
[ Testklasse ] Get x = -1234
[ x ] -1234

und wenn ich das self.x = 1234 in init auskommentiere, dann ist die Ausgabe:

[ Testklasse ] Get x = None
[ x ] None

und wenn ich das _default = None auf _default = 0 im Getter (da alle Getter ein Standardwert haben sollten, der jedoch nicht über die Eigenschaftswerte übergeben wird, wie ich gesehen habe, können Sie ihn hier definieren, und es ist tatsächlich nicht schlecht, da Sie den Standardwert einmal definieren können und ihn überall verwenden können ) also: def x( self, _default = 0 ):

[ Testklasse ] Get x = 0
[ x ] 0

Hinweis: Die Getter-Logik ist vorhanden, um sicherzustellen, dass der Wert durch sie bearbeitet wird, um sicherzustellen, dass er durch sie bearbeitet wird - das gleiche gilt für die Druckanweisungen...

Hinweis: Ich bin es gewohnt, Lua zu verwenden und in der Lage zu sein, beim Aufrufen einer einzelnen Funktion dynamisch 10+ Helfer zu erstellen, und ich habe etwas Ähnliches für Python ohne die Verwendung von Eigenschaften erstellt, und es funktioniert bis zu einem gewissen Grad, aber auch wenn die Funktionen erstellt werden bevor sie verwendet werden, gibt es manchmal Probleme damit, dass sie aufgerufen werden, bevor sie erstellt werden, was seltsam ist, da es nicht so codiert ist... Ich bevorzuge die Flexibilität von Lua-Meta-Tabellen und die Tatsache, dass ich tatsächliche Setter / Getter verwenden kann, anstatt im Wesentlichen direkt auf eine Variable zuzugreifen.... Mir gefällt, wie schnell einige Dinge mit Python erstellt werden können - zum Beispiel GUI-Programme. obwohl eines, das ich entwerfe, möglicherweise ohne viele zusätzliche Bibliotheken nicht möglich ist - wenn ich es in AutoHotkey codiere, kann ich direkt auf die dll-Aufrufe zugreifen, und dasselbe kann in Java, C#, C++, und mehr getan werden - vielleicht habe ich noch nicht das Richtige gefunden, aber für dieses Projekt könnte ich von Python zu Anfangen wechseln..

Hinweis: Die Codeausgabe in diesem Forum ist fehlerhaft - Ich musste Leerzeichen am Anfang des Codes hinzufügen, damit es funktioniert - beim Kopieren / Einfügen stellen Sie sicher, dass Sie alle Leerzeichen in Tabs umwandeln.... Ich verwende Tabs für Python, weil in einer Datei mit 10.000 Zeilen die Dateigröße mit Leerzeichen 512KB bis 1MB betragen kann und mit Tabs 100 bis 200KB, was einen massiven Unterschied in der Dateigröße und Zeit für die Verarbeitung bedeutet...

Tabs können auch pro Benutzer angepasst werden - wenn Sie also eine Breite von 2 Spaces, 4, 8 oder was auch immer bevorzugen, können Sie sie verwenden, was für Entwickler mit Sehbehinderungen gedacht ist.

Hinweis: Alle in der Klasse definierten Funktionen sind wegen eines Fehlers in der Forumssoftware nicht korrekt eingerückt - stellen Sie sicher, dass Sie sie einrücken, wenn Sie kopieren / einfügen

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X