Jak działa dekorator @property?

980

Chciałbym zrozumieć, jak propertydziała funkcja wbudowana . To, co mnie dezorientuje, to fakt, że propertymożna go również używać jako dekoratora, ale wymaga tylko argumentów, gdy jest używany jako funkcja wbudowana, a nie jako dekorator.

Ten przykład pochodzi z dokumentacji :

class C(object):
    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.")

property„s argumenty są getx, setx, delxa ciąg doc.

W poniższym kodzie propertyzastosowano jako dekorator. Jego przedmiotem jest xfunkcja, ale w powyższym kodzie nie ma miejsca na funkcję obiektu w argumentach.

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

Jak powstają dekoratorzy x.setteri x.deleterdekoratorzy? Jestem zdezorientowany.

jak on
źródło
13
Zobacz także: Jak działają właściwości Python?
Martin Thoma,
3
propertyjest w rzeczywistości klasą (a nie funkcją), chociaż prawdopodobnie wywołuje __init__()metodę podczas tworzenia obiektu, oczywiście. Korzystanie help(property)z terminala jest wnikliwe. helpjest także klasą z jakiegoś powodu.
Brōtsyorfuzthrāx
Myślę, że ten link stanowi dobry przykład: [property] ( journaldev.com/14893/python-property-decorator )
Sheng Bi
4
@Shule 2-letni wątek, ale wciąż: wszystko jest klasą. Nawet zajęcia.
Artemis nadal nie ufa SE
2
To też było dla mnie mylące. W końcu znalazłem artykuł, który był w stanie go zepsuć. Mam nadzieję, że to pomaga komuś innemu. programiz.com/python-programming/property Nie jestem w żaden sposób powiązany z witryną.
jjwdesign

Odpowiedzi:

1008

property()Zwraca szczególną obiekt deskryptora :

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

Ten obiekt ma dodatkowe metody:

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

Działają one jak dekoratorów zbyt . Zwracają nowy obiekt właściwości:

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

jest to kopia starego obiektu, ale z zastąpioną jedną z funkcji.

Pamiętaj, że @decoratorskładnia to tylko cukier składniowy; składnia:

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

naprawdę oznacza to samo co

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

więc foofunkcja jest zastąpiona przez property(foo), co widzieliśmy powyżej, jest obiektem specjalnym. Następnie, gdy używasz @foo.setter(), wywołujesz property().settermetodę, którą pokazałem powyżej, która zwraca nową kopię właściwości, ale tym razem z funkcją ustawiającą zastąpioną metodą dekorowaną.

Poniższa sekwencja tworzy również właściwość full-on przy użyciu tych metod dekoratora.

Najpierw tworzymy niektóre funkcje i propertyobiekt za pomocą gettera:

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

Następnie używamy .setter()metody, aby dodać seter:

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

Na koniec dodajemy deleter za pomocą .deleter()metody:

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

Last but not least, propertyobiekt działa jako obiektu deskryptora , a więc ma .__get__(), .__set__()i .__delete__()sposoby, aby podłączyć się do atrybutu instancji uzyskanie, ustawiania i usuwania:

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

Deskryptorze Howto zawiera czystą przykładową realizację Pythona w property()rodzaju:

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__)
Martijn Pieters
źródło
10
Bardzo dobre. Można dodać, że po tym Foo.prop = propmożna zrobić Foo().prop = 5; pront Foo().prop; del Foo().propz pożądanego rezultatu.
glglgl
12
Obiekty metod są tworzone w locie i mogą ponownie wykorzystać tę samą lokalizację pamięci, jeśli jest dostępna.
Martijn Pieters
1
@MarkusMeskanen: Raczej używam type()jako dostępu do atrybutów dundera, a metody mają służyć jako punkty rozszerzenia przez standardowe funkcje i operatory.
Martijn Pieters
2
@MarkusMeskanen: ponieważ obiekt jest niezmienny, a jeśli zmutowano go w miejscu, nie można go było specjalizować w podklasie.
Martijn Pieters
5
@MarkusMeskanen: patrz Python przesłaniający getter bez settera ; gdyby @human.name.getterzmieniono propertyobiekt w miejscu zamiast zwrócić nowy, human.nameatrybut zostałby zmieniony, zmieniając zachowanie tej nadklasy.
Martijn Pieters
200

Dokumentacja mówi, że to tylko skrót do tworzenia właściwości tylko do odczytu. Więc

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

jest równa

def getx(self):
    return self._x
x = property(getx)
J0HN
źródło
19
Pełny kontekst (najbardziej pozytywna odpowiedź) jest dobry, ale ta odpowiedź była praktycznie przydatna, aby dowiedzieć się, dlaczego ktoś inny był @propertydekoratorem w swojej klasie.
ijoseph
1
@property może być również użyte, gdy chcesz dodać atrybut do klasy i zachować zgodność z wcześniej utworzonymi obiektami tej klasy (np. które mogą być zapisane w pliku marynowanym).
AndyP
111

Oto minimalny przykład tego, jak @propertymożna go wdrożyć:

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

>>> print( Thing('ok').word )
'ok'

W przeciwnym razie wordpozostaje metoda zamiast właściwości.

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'
AlexG
źródło
1
Jak wyglądałby ten przykład, gdyby funkcja / właściwość word () wymagała zdefiniowania w init ?
JJ
5
Czy ktoś może wyjaśnić, dlaczego self.word = my_wordprint( Thing('ok').word ) = 'ok'
stworzę
1
@SilverSlash To tylko prosty przykład, prawdziwy przypadek użycia wymagałby bardziej skomplikowanej metody
AlexG
czy możesz mi wyjaśnić, w jaki sposób drukowanie Thing('ok').wordwywołuje tę funkcję wewnętrznie w czasie wykonywania?
Vicrobot,
83

Pierwsza część jest prosta:

@property
def x(self): ...

jest taki sam jak

def x(self): ...
x = property(x)
  • co z kolei jest uproszczoną składnią do tworzenia propertyz tylko geterem.

Następnym krokiem byłoby rozszerzenie tej właściwości o seter i deleter. A dzieje się to za pomocą odpowiednich metod:

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

zwraca nową właściwość, która dziedziczy wszystko po starym xplus podany setter.

x.deleter działa w ten sam sposób.

glglgl
źródło
49

To następujące:

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

Jest taki sam jak:

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.")

Jest taki sam jak:

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)

Jest taki sam jak:

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)

Który jest taki sam jak:

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
Bill Moore
źródło
4
Pierwszy i ostatni przykład kodu są takie same (dosłownie).
Adomas Baliuka,
47

Poniżej znajduje się kolejny przykład, w jaki sposób @propertymożna pomóc, gdy trzeba ponownie kodować kod pobrany z tego miejsca (podsumowuję go tylko poniżej):

Wyobraź sobie, że stworzyłeś taką klasę Money:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

a użytkownik tworzy bibliotekę w zależności od tej klasy, z której korzysta np

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

Załóżmy teraz, że zdecydujesz się zmienić Moneyklasę i pozbyć się atrybutów dollarsi, centsale zamiast tego zdecydujesz się śledzić tylko całkowitą liczbę centów:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

Jeśli wyżej wspomniany użytkownik próbuje teraz uruchomić swoją bibliotekę jak poprzednio

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

spowoduje błąd

AttributeError: Obiekt „Pieniądze” nie ma atrybutu „dolarów”

Oznacza to, że teraz każdy, kto opiera się na oryginalnej Moneyklasie, musiałby zmienić wszystkie wiersze kodu gdzie dollarsicents są używane, co może być bardzo bolesne ... Jak więc można tego uniknąć? Za pomocą @property!

To w jaki sposób:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

kiedy teraz dzwonimy z naszej biblioteki

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

będzie działać zgodnie z oczekiwaniami i nie musieliśmy zmieniać ani jednego wiersza kodu w naszej bibliotece! W rzeczywistości nie musielibyśmy nawet wiedzieć, że biblioteka, na której polegamy, uległa zmianie.

Również setterdziała dobrze:

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.

Możesz używać @propertyrównież w klasach abstrakcyjnych; Daję minimalny przykład tutaj .

Cleb
źródło
twoje podsumowanie jest bardzo dobre, przykład, który bierze strona internetowa jest trochę dziwny .. Początkujący zapytałby ... dlaczego nie możemy trzymać się tego self.dollar = dollars? tyle zrobiliśmy z @property, ale wygląda na to, że nie dodano żadnej funkcji wyodrębniania.
Sheng Bi
1
@ShengBi: Nie skupiaj się tak bardzo na rzeczywistym przykładzie, ale bardziej na podstawowej zasadzie: jeśli - z jakiegokolwiek powodu - musisz zmienić kod, możesz to zrobić bez wpływu na kod innej osoby.
Cleb
21

Przeczytałem wszystkie posty tutaj i zdałem sobie sprawę, że możemy potrzebować prawdziwego przykładu. Dlaczego właściwie mamy @ właściwość? Rozważ więc aplikację Flask, w której korzystasz z systemu uwierzytelniania. Użytkownik deklaruje model użytkownika w 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)

W tym kodzie mamy „ukryty” atrybut, passwordza pomocą @propertyktórego wyzwala się AttributeErrorpotwierdzenie, gdy próbujesz uzyskać do niego bezpośredni dostęp, podczas gdy użyliśmy @ property.setter, aby ustawić rzeczywistą zmienną instancji password_hash.

Teraz auth/views.pymożemy utworzyć użytkownika za pomocą:

...
@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()
...

Zauważ atrybut, passwordktóry pochodzi z formularza rejestracyjnego, gdy użytkownik wypełnia formularz. Potwierdzenie hasła odbywa się na interfejsie użytkownika EqualTo('password', message='Passwords must match')(na wypadek, gdybyś się zastanawiał, ale jest to inny temat związany z formularzami Flask).

Mam nadzieję, że ten przykład się przyda

Leo Skhrnkv
źródło
18

Ten punkt został wyjaśniony przez wielu ludzi tam, ale tutaj jest punkt bezpośredni, którego szukałem. Uważam, że to ważne, aby zacząć od dekoratora @property. na przykład:-

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

Wywołanie funkcji „get_config ()” będzie działać w ten sposób.

util = UtilityMixin()
print(util.get_config)

Jeśli zauważysz, że nie użyłem nawiasów „()” do wywołania funkcji. To jest podstawowa rzecz, której szukałem dekoratora @property. Abyś mógł używać swojej funkcji jak zmiennej.

Devendra Bhat
źródło
1
bardzo przydatny punkt, który pomaga zagęścić tę abstrakcyjną koncepcję.
Info5ek
18

Zacznijmy od dekoratorów Python.

Dekorator Python to funkcja, która pomaga dodać dodatkowe funkcje do już zdefiniowanej funkcji.

W Pythonie wszystko jest przedmiotem. Funkcje w Pythonie są pierwszorzędnymi obiektami, co oznacza, że ​​można do nich odwoływać się zmienną, dodawać do list, przekazywać jako argumenty do innej funkcji itp.

Rozważ następujący fragment kodu.

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

Tutaj możemy powiedzieć, że funkcja dekoratora zmodyfikowała naszą funkcję say_hello i dodała w niej kilka dodatkowych wierszy kodu.

Składnia Pythona dla dekoratora

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

Podsumujmy wszystko niż scenariusz przypadku, ale wcześniej porozmawiajmy o niektórych głównych zasadach.

Gettery i settery są używane w wielu obiektowych językach programowania, aby zapewnić zasadę enkapsulacji danych (jest to postrzegane jako wiązanie danych z metodami operującymi na tych danych).

Te metody są oczywiście narzędziem pobierającym dane i ustawiającym dane.

Zgodnie z tą zasadą atrybuty klasy są prywatne, aby je ukryć i chronić przed innym kodem.

Tak, @property jest w zasadzie pytonicznym sposobem używania programów pobierających i ustawiających.

Python ma świetną koncepcję zwaną właściwością, która znacznie upraszcza życie programisty obiektowego.

Załóżmy, że zdecydujesz się stworzyć klasę, która może przechowywać temperaturę w stopniach Celsjusza.

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

Refactored Code, oto jak moglibyśmy to osiągnąć za pomocą nieruchomości.

W Pythonie property () jest wbudowaną funkcją, która tworzy i zwraca obiekt właściwości.

Obiekt właściwości ma trzy metody: getter (), setter () i delete ().

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

Tutaj,

temperature = property(get_temperature,set_temperature)

mógł zostać rozbity, ponieważ

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

Uwaga:

  • get_temperature pozostaje właściwością zamiast metody.

Teraz możesz uzyskać dostęp do wartości temperatury pisząc.

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

Możemy dalej iść i nie definiować nazw get_temperature i set_temperature, ponieważ są one niepotrzebne i zanieczyszczają przestrzeń nazw klas.

Pythonic sposób radzić sobie z powyższego problemu jest użycie @property .

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

Punkty do odnotowania -

  1. Metodę używaną do uzyskania wartości ozdobiono „@property”.
  2. Metoda, która musi działać jako seter, jest dekorowana za pomocą „@ temperature.setter”. Gdyby funkcja została nazwana „x”, musielibyśmy ją ozdobić za pomocą „@ x.setter”.
  3. Napisaliśmy „dwie” metody o tej samej nazwie i różnej liczbie parametrów „def def (self)” i „def temperature (self, x)”.

Jak widać, kod jest zdecydowanie mniej elegancki.

Porozmawiajmy teraz o jednym praktycznym scenariuszu z życia.

Powiedzmy, że zaprojektowałeś klasę w następujący sposób:

class OurClass:

    def __init__(self, a):
        self.x = a


y = OurClass(10)
print(y.x)

Teraz załóżmy dalej, że nasza klasa stała się popularna wśród klientów i zaczęli używać jej w swoich programach, wykonywali wszelkiego rodzaju przypisania do obiektu.

Pewnego pamiętnego dnia przyszedł do nas zaufany klient i zasugerował, że „x” musi być wartością od 0 do 1000, to naprawdę okropny scenariusz!

Ze względu na właściwości jest to łatwe: tworzymy wersję właściwości „x”.

class OurClass:

    def __init__(self,x):
        self.x = x

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

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

To wspaniale, prawda? Możesz zacząć od najprostszej możliwej implementacji i możesz później migrować do wersji właściwości bez konieczności zmiany interfejsu! Więc właściwości nie są tylko zamiennikiem dla pobierających i ustawiających!

Możesz sprawdzić tę implementację tutaj

Divyanshu Rawat
źródło
2
Twoja klasa Celsjusza będzie się powtarzać w nieskończoność podczas ustawiania (co oznacza przy tworzeniu instancji).
Ted Petrou,
1
@Ted Petrou Nie dostałem cię? Jak będzie się powtarzał nieskończenie podczas ustawiania?
Divyanshu Rawat
To właściwie nie jest jasne ... ludzie pytają dlaczego, ale przykład nie jest przekonujący ...
Sheng Bi
1
To tylko komentarz, moja osobista opinia. Twoja odpowiedź może być naprawdę dobra. więc zostaw to.
Sheng Bi
1
w porównaniu do najczęściej głosowanych odpowiedzi, ta jest przeznaczona dla ludzi; dzięki.
Info5ek
6

propertyjest klasą stojącą za @propertydekoratorem.

Zawsze możesz to sprawdzić:

print(property) #<class 'property'>

Przepisałem przykład z, help(property)aby pokazać, że @propertyskładnia

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)

jest funkcjonalnie identyczny ze property()składnią:

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)

Jak widać, nie ma różnicy, w jaki sposób korzystamy z nieruchomości.

Aby odpowiedzieć na pytanie, @propertydekorator jest wdrażany za pośrednictwem propertyklasy.


Zatem pytanie polega na tym, propertyaby trochę wyjaśnić klasę. Ta linia:

prop = property(g,s,d)

Była inicjalizacja. Możemy przepisać to w następujący sposób:

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

Rozumieniu fget, fseti 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

Następny obraz pokazuje trojaczki, które mamy, z klasy property:

wprowadź opis zdjęcia tutaj

__get__, __set__i czy __delete__są tam do zastąpienia . Jest to implementacja wzorca deskryptora w Pythonie.

Ogólnie rzecz biorąc, deskryptor jest atrybutem obiektu z „zachowaniem wiązania”, takim, którego dostęp do atrybutu został przesłonięty przez metody w protokole deskryptora.

Możemy również korzystać z własności setter, gettera deletermetody wiążą funkcję mienia. Sprawdź następny przykład. Metoda s2klasy Cspowoduje podwojenie właściwości .

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"
prosti
źródło
1

Właściwość można zadeklarować na dwa sposoby.

  • Utworzenie metody pobierającej, ustawiającej dla atrybutu, a następnie przekazanie jej jako argumentu do funkcji właściwości
  • Korzystanie z dekoratora @property .

Możesz rzucić okiem na kilka przykładów, które napisałem o właściwościach w pythonie .

nvd
źródło
czy potrafisz zaktualizować swoją odpowiedź, mówiąc, że własność jest klasą, więc mogę ją głosować.
prosti
1

Najlepsze wyjaśnienie można znaleźć tutaj: Wyjaśnienie Python @ Właściwość - jak używać i kiedy? (Pełne przykłady) Selva Prabhakaran | Wysłany 5 listopada 2018 r

Pomogło mi zrozumieć DLACZEGO nie tylko JAK.

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

Victor Wang
źródło
0

Oto inny przykład:

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

Zasadniczo to samo co w przykładzie C (obiekt), z tym że zamiast tego używam x ... Nie inicjuję też w __init - ... cóż .. Robię, ale można go usunąć, ponieważ __x jest zdefiniowany jako część z klasy ....

Dane wyjściowe to:

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

a jeśli skomentuję self.x = 1234 w init, to wynik będzie:

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

i jeśli ustawię _default = None na _default = 0 w funkcji gettera (ponieważ wszystkie gettery powinny mieć wartość domyślną, ale nie są przekazywane przez wartości właściwości z tego, co widziałem, aby można było ją tutaj zdefiniować, i tak naprawdę nie jest źle, ponieważ możesz zdefiniować wartość domyślną raz i używać jej wszędzie) tj .: def x (self, _default = 0):

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

Uwaga: Logika pobierająca jest po to, aby manipulować wartością, aby upewnić się, że jest nią manipulowana - to samo dla instrukcji print ...

Uwaga: Jestem przyzwyczajony do Lua i jestem w stanie dynamicznie tworzyć ponad 10 pomocników, gdy wywołuję pojedynczą funkcję, i stworzyłem coś podobnego do Pythona bez użycia właściwości i działa do pewnego stopnia, ale mimo że funkcje są tworzone wcześniej gdy są używane, czasami pojawiają się problemy z ich wywoływaniem przed utworzeniem, co jest dziwne, ponieważ nie jest tak kodowane ... Wolę elastyczność meta-tabel Lua i fakt, że mogę używać rzeczywistych setterów / getterów zamiast zasadniczo bezpośredniego dostępu do zmiennej ... Podoba mi się, jak szybko można zbudować pewne rzeczy za pomocą Pythona - na przykład programy GUI. chociaż jedna, którą projektuję, może nie być możliwa bez wielu dodatkowych bibliotek - jeśli koduję to w AutoHotkey, mogę bezpośrednio uzyskać dostęp do wywołań dll, których potrzebuję, i to samo można zrobić w Javie, C #, C ++,

Uwaga: Kod wyjściowy na tym forum jest zepsuty - musiałem dodać spacje do pierwszej części kodu, aby działał - podczas kopiowania / wklejania upewnij się, że przekonwertowałeś wszystkie spacje na tabulatory .... Używam tabulatorów w Pythonie, ponieważ w plik, który ma 10 000 wierszy, rozmiar pliku może wynosić od 512 KB do 1 MB ze spacjami i od 100 do 200 KB z tabulatorami, co odpowiada ogromnej różnicy rozmiaru pliku i skróceniu czasu przetwarzania ...

Tabulatory można również regulować dla każdego użytkownika - więc jeśli wolisz szerokość 2 spacji, 4, 8 lub cokolwiek innego, co możesz zrobić, oznacza to, że jest to rozważne dla programistów z deficytem wzroku.

Uwaga: Wszystkie funkcje zdefiniowane w klasie nie są prawidłowo wcięte z powodu błędu w oprogramowaniu forum - upewnij się, że wcinasz je, jeśli kopiujesz / wklejasz

Acecool
źródło
-3

Jedna uwaga: dla mnie, dla Python 2.x, @propertynie działał tak jak reklamowano, gdy nie dziedziczyłem po object:

class A():
    pass

ale działało, gdy:

class A(object):
    pass

dla Pythona 3 działało zawsze.

Gyula Sámuel Karli
źródło
5
Jest tak, ponieważ w Pythonie 2 klasa, która nie dziedziczy po, objectjest klasą w starym stylu, a klasy w starym stylu nie obsługują protokołu deskryptora (który propertyimplementuje się tak, jak działa). W Pythonie 3 klasy w starym stylu już nie istnieją; wszystkie klasy nazywamy klasami nowego stylu w Pythonie 2.
chepner