Hoe werkt de @property-decorateur in Python?

Ik zou graag willen weten hoe de ingebouwde functie propertywerkt. Wat me in de war brengt, is dat propertyook als decorateur kan worden gebruikt, maar dat het alleen argumenten nodig heeft als het als ingebouwde functie wordt gebruikt en niet als het als decorateur wordt gebruikt.

Dit voorbeeld komt uit de documentatie:

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, "I'm the 'x' property.")

De argumenten van

propertyzijn getx, setx, delxen een doc-tekenreeks.

In de onderstaande code wordt propertygebruikt als decorateur. Het object ervan is de functie x, maar in de bovenstaande code is er geen plaats voor een objectfunctie in de argumenten.

class C:
    def __init__(self):
        self._x = None
    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
    @x.deleter
    def x(self):
        del self._x

Hoe worden de decorateurs x.setteren x.deleterin dit geval gemaakt?


Antwoord 1, autoriteit 100%

De functie property()retourneert een speciaal descriptorobject:

>>> property()
<property object at 0x10ff07940>

Dit object heeft extramethoden:

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

Deze fungeren ookals decorateurs. Ze retourneren een nieuw eigenschapsobject:

>>> property().getter(None)
<property object at 0x10ff079f0>

dat is een kopie van het oude object, maar met een van de functies vervangen.

Onthoud dat de syntaxis @decoratorgewoon syntactische suiker is; de syntaxis:

@property
def foo(self): return self._foo

betekent eigenlijk hetzelfde als

def foo(self): return self._foo
foo = property(foo)

dus foode functie wordt vervangen door property(foo), waarvan we hierboven zagen dat het een bijzonder object is. Als je dan @foo.setter()gebruikt, roep je die property().settermethode aan die ik je hierboven heb laten zien, die een nieuwe kopie van de eigendom, maar deze keer met de setter-functie vervangen door de gedecoreerde methode.

De volgende reeks creëert ook een volledige eigenschap, door gebruik te maken van deze decorateurmethoden.

Eerst maken we enkele functies en een propertyobject met alleen een getter:

>>> def getter(self): print('Get!')
... 
>>> def setter(self, value): print('Set to {!r}!'.format(value))
... 
>>> def deleter(self): print('Delete!')
... 
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

Vervolgens gebruiken we de .setter()methode om een setter toe te voegen:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

Als laatste voegen we een deleter toe met de .deleter()methode:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Last but not least fungeert het property-object als een descriptorobject, dus het heeft .__get__(), .__set__()en .__delete__()methoden om aan te sluiten op instantiekenmerk ophalen, instellen en verwijderen:

>>> class Foo: pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

De descriptor HOWTO bevat een pure python monster implementatie van de property()Type:

class Property:
    "Emulate PyProperty_Type() in Objects/descrobject.c"
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)
    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)
    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)
    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)
    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)
    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

2, Autoriteit 23%

Documentatie zegt Het is slechts een snelkoppeling voor het maken van readonly-eigenschappen. Dus

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

is gelijk aan

def getx(self):
    return self._x
x = property(getx)

3, Autoriteit 12%

Hier is een minimaal voorbeeld van hoe @propertykan worden geïmplementeerd:

class Thing:
    def __init__(self, my_word):
        self._word = my_word 
    @property
    def word(self):
        return self._word
>>> print( Thing('ok').word )
'ok'

Anders wordblijft een methode in plaats van een eigendom.

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word
>>> print( Thing('ok').word() )
'ok'

4, Autoriteit 8%

Het eerste deel is eenvoudig:

@property
def x(self): ...

is hetzelfde als

def x(self): ...
x = property(x)
  • die op zijn beurt de vereenvoudigde syntaxis is voor het maken van een propertymet slechts een getter.

De volgende stap zou zijn om deze accommodatie uit te breiden met een Setter en een Deleter. En dit gebeurt met de juiste methoden:

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

Retourneert een nieuw eigenschap die alles erft van de oude xplus de gegeven setter.

x.deleterWerkt op dezelfde manier.


Antwoord 5, autoriteit 5%

Dit volgende:

class C(object):
    def __init__(self):
        self._x = None
    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
    @x.deleter
    def x(self):
        del self._x

Is hetzelfde als:

class C(object):
    def __init__(self):
        self._x = None
    def _x_get(self):
        return self._x
    def _x_set(self, value):
        self._x = value
    def _x_del(self):
        del self._x
    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

Is hetzelfde als:

class C(object):
    def __init__(self):
        self._x = None
    def _x_get(self):
        return self._x
    def _x_set(self, value):
        self._x = value
    def _x_del(self):
        del self._x
    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

Is hetzelfde als:

class C(object):
    def __init__(self):
        self._x = None
    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")
    def _x_set(self, value):
        self._x = value
    x = x.setter(_x_set)
    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

Wat hetzelfde is als :

class C(object):
    def __init__(self):
        self._x = None
    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
    @x.deleter
    def x(self):
        del self._x

Antwoord 6, autoriteit 2%

Ik las alle berichten hier en realiseerde me dat we misschien een voorbeeld uit het echte leven nodig hebben. Waarom hebben we eigenlijk @property?
Overweeg dus een Flask-app waarbij u een authenticatiesysteem gebruikt.
U geeft een modelgebruiker aan in models.py:

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))
    ...
    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')
    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)
    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

In deze code hebben we het kenmerk password“verborgen” door gebruik te maken van @propertydie de bewering AttributeErroractiveert wanneer u deze rechtstreeks probeert te openen , terwijl we @property.setter gebruikten om de daadwerkelijke instantievariabele password_hashin te stellen.

Nu kunnen we in auth/views.pyeen gebruiker instantiëren met:

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

Kenmerkkenmerk passworddat afkomstig is van een registratieformulier wanneer een gebruiker het formulier invult. Wachtwoordbevestiging gebeurt aan de voorkant met EqualTo('password', message='Passwords must match')(voor het geval je het je afvraagt, maar het is een ander onderwerp gerelateerd aan Flask-formulieren).

Ik hoop dat dit voorbeeld nuttig zal zijn


Antwoord 7, autoriteit 2%

Dit punt is door veel mensen daarboven opgelost, maar hier is een direct punt waar ik naar op zoek was.
Dit is wat ik belangrijk vind om te beginnen met de @property-decorateur.
bijv.:-

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"

Het aanroepen van de functie “get_config()” werkt als volgt.

util = UtilityMixin()
print(util.get_config)

Als je merkt dat ik geen “()” haakjes heb gebruikt om de functie aan te roepen. Dit is het basisding dat ik zocht naar de @property-decorateur. Zodat u uw functie net als een variabele kunt gebruiken.


Antwoord 8

propertyis een klasse achter @propertydecorateur.

Je kunt dit altijd controleren:

print(property) #<class 'property'>

Ik heb het voorbeeld van help(property)herschreven om aan te tonen dat de syntaxis @property

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)

is functioneel identiek aan property()syntaxis:

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)

Het maakt niet uit hoe we het pand gebruiken, zoals je kunt zien.

Om de vraag te beantwoorden @propertywordt decorateur geïmplementeerd via de klasse property.


Dus de vraag is om de klasse propertyeen beetje uit te leggen.
Deze regel:

prop = property(g,s,d)

Was de initialisatie. We kunnen het als volgt herschrijven:

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

De betekenis van fget, fseten fdel:

|    fget
 |      function to be used for getting an attribute value
 |    fset
 |      function to be used for setting an attribute value
 |    fdel
 |      function to be used for del'ing an attribute
 |    doc
 |      docstring

De volgende afbeelding toont de drieling die we hebben, van de klasse property:

__get__, __set__en __delete__zijn er om overschreven. Dit is de implementatie van het descriptorpatroon in Python.

Over het algemeen is een descriptor een objectattribuut met “bindingsgedrag”, een waarvan de attribuuttoegang is overschreven door methoden in het descriptorprotocol.

We kunnen ook eigenschappen setter, getteren deletergebruiken om de functie aan een eigenschap te binden. Controleer het volgende voorbeeld. De methode s2van de klasse Czal de eigenschap verdubbeldinstellen.

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) # outputs "a"
C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"

Antwoord 9

De beste uitleg vind je hier:
Python @Property uitgelegd – Hoe te gebruiken en wanneer? (Volledige voorbeelden)
door Selva Prabhakaran | Geplaatst op 5 november 2018

Het hielp me te begrijpen WAAROM niet alleen HOE.

https://www.machinelearningplus.com/python/python-property/


Antwoord 10

Decorator is een functie die een functie als argument neemt en een sluiting retourneert. Sluiting is een set van innerlijke functie en vrije variabele. De innerlijke functie sluit de vrije variabele af en daarom wordt het ‘afsluiting’ genoemd. Vrije variabele is een variabele die buiten de innerlijke functie valt en via de docorator aan de innerlijke wordt doorgegeven.

Zoals de naam al zegt, decoreert de decorateur de ontvangen functie.

function decorator(undecorated_func):
    print("calling decorator func")
    inner():
       print("I am inside inner")
       return undecorated_func
    return inner

dit is een eenvoudige decoratiefunctie. Het ontving “undecorated_func” en gaf het door aan inner() als een vrije variabele, inner() drukte “I am inside inner” af en retourneerde undecorated_func. Wanneer we decorator(undecorated_func)aanroepen, retourneert het de inner. Hier is de sleutel, in decorateurs noemen we de innerlijke functie als de naam van de functie die we hebben doorgegeven.

  undecorated_function= decorator(undecorated_func) 

nu wordt de innerlijke functie “undecorated_func” genoemd. Omdat inner nu “undecorated_func” wordt genoemd, hebben we “undecorated_func” doorgegeven aan de decorateur en hebben we “undecorated_func” geretourneerd plus afgedrukt “I am inside inner”. dus deze printverklaring sierde onze “undecorated_func”.

laten we nu een klasse definiëren met de eigenschap decorateur:

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

toen we name() versierden met @property(), gebeurde dit:

name=property(name) # Person.__dict__ you ll see name 

eerste argument van property() is getter. dit is wat er gebeurde in de tweede decoratie:

  name=name.setter(name) 

Zoals ik hierboven al zei, geeft decorateur de innerlijke functie terug, en we noemen de innerlijke functie met de naam van de functie die we hebben doorgegeven.

Dit is iets belangrijks om op te letten. “naam” is onveranderlijk. in de eerste decoratie kregen we dit:

 name=property(name)

in de tweede kregen we dit

  name=name.setter(name) 

We wijzigen de naam obj niet. In de tweede decoratie ziet python dat dit een eigendomsobject is en dat het al een getter had. Dus python maakt een nieuw “name” -object, voegt de “fget” toe vanaf de eerste obj en stelt vervolgens de “fset” in.


Antwoord 11

Een eigenschap kan op twee manieren worden gedeclareerd.

  • De getter-, setter-methoden voor een attribuut maken en deze vervolgens als argument doorgeven aan de functie eigenschap
  • De @property-decorateur gebruiken.

Je kunt een paar voorbeelden bekijken die ik heb geschreven over eigenschappen in python .


Antwoord 12

Hier is nog een voorbeeld:

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None
##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234
    return None
##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )
    ## Return the value - we are a getter afterall...
    return _value
##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )
    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value
##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x
##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )
    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'
##
##
##
_test = GetterSetterExample( )
print( _test )
## For some reason the deleter isn't being called...
del _test.x

eigenlijk, hetzelfde als het C (Object) -voorbeeld, behalve ik gebruik X in plaats daarvan … Ik initialiseer ook niet in __init – … nou .. ik doe, maar Het kan worden verwijderd omdat __x wordt gedefinieerd als onderdeel van de klas ….

De uitvoer is:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

en als ik het zelf opmerkte, is de self.x = 1234 in init dan de uitvoer:

[ Test Class ] Get x = None
[ x ] None

en als ik de _default = geen in de GETTER-functie instel (omdat alle Getters een standaardwaarde moeten hebben, maar het wordt niet doorgegeven door de vastgoedwaarden van wat ik heb gezien, kunt u het definiëren hier, en het is eigenlijk niet slecht omdat je de standaard eenmaal kunt definiëren en overal gebruikt), dat wil zeggen: def x (self, _default = 0):

[ Test Class ] Get x = 0
[ x ] 0

Opmerking: de Getter Logic is er alleen om de waarde te laten manipuleren om ervoor te zorgen dat deze door het wordt gemanipuleerd – hetzelfde voor de afdrukverklaringen …

Opmerking: ik ben gewend aan Lua en kan dynamisch 10+ helpers maken wanneer ik een enkele functie aanroep en ik heb iets soortgelijks gemaakt voor Python zonder eigenschappen te gebruiken en het werkt tot op zekere hoogte, maar hoewel de functies worden gemaakt voordat ze worden gebruikt, zijn er soms nog steeds problemen waarbij ze worden aangeroepen voordat ze worden gemaakt, wat vreemd is omdat het niet op die manier is gecodeerd … Ik geef de voorkeur aan de flexibiliteit van Lua-metatabellen en het feit dat ik echte setters / getters in plaats van in wezen directe toegang tot een variabele … Ik vind het wel leuk hoe snel sommige dingen met Python kunnen worden gebouwd – bijvoorbeeld gui-programma’s. hoewel een die ik aan het ontwerpen ben misschien niet mogelijk is zonder veel extra bibliotheken – als ik het codeer in AutoHotkey, heb ik direct toegang tot de dll-aanroepen die ik nodig heb, en hetzelfde kan worden gedaan in Java, C #, C++ en meer – misschien ik heb het juiste nog niet gevonden, maar voor dat project kan ik overstappen van Python..

Opmerking: de code-uitvoer in dit forum is defect – ik moest spaties toevoegen aan het eerste deel van de code om het te laten werken – zorg er bij het kopiëren / plakken voor dat je alle spaties naar tabs converteert…. Ik gebruik tabs voor Python omdat in een bestand van 10.000 regels de bestandsgrootte 512 KB tot 1 MB kan zijn met spaties en 100 tot 200 KB met tabbladen, wat gelijk staat aan een enorm verschil voor de bestandsgrootte en een vermindering van de verwerkingstijd…

Tabs kunnen ook per gebruiker worden aangepast – dus als u de voorkeur geeft aan 2 spaties in de breedte, 4, 8 of wat u ook kunt doen, is dit attent voor ontwikkelaars met een slecht gezichtsvermogen.

Opmerking: alle functies die in de klasse zijn gedefinieerd, zijn niet correct ingesprongen vanwege een bug in de forumsoftware – zorg ervoor dat u deze inspringt als u kopieert / plakt

Other episodes