693 Stimmen

Was ist der pythonische Weg, um Getter und Setter zu verwenden?

Ich mache es so:

def set_property(property,value):  
def get_property(property):  

o

object.property = value  
value = object.property

Ich bin neu in Python, so dass ich immer noch die Syntax zu erkunden, und ich würde gerne einige Ratschläge zu tun dies.

1113voto

Grissiom Punkte 9713

Versuchen Sie dies: Python-Eigenschaft

Der Beispielcode lautet:

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

    @property
    def x(self):
        """I'm the 'x' property."""
        print("getter of x called")
        return self._x

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x

c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called

600voto

Was ist der pythonische Weg, um Getter und Setter zu verwenden?

Der "pythonische" Weg ist nicht "Getter" und "Setter" zu verwenden, sondern einfache Attribute zu verwenden, wie die Frage zeigt, und del zum Löschen (aber die Namen sind geändert, um die Unschuldigen zu schützen... buildins):

value = 'something'

obj.attribute = value  
value = obj.attribute
del obj.attribute

Wenn Sie die Einstellung später ändern wollen, können Sie dies tun, ohne den Benutzercode zu ändern, indem Sie die property Dekorateur:

class Obj:
    """property demo"""
    #
    @property            # first decorate the getter method
    def attribute(self): # This getter method name is *the* name
        return self._attribute
    #
    @attribute.setter    # the property decorates with `.setter` now
    def attribute(self, value):   # name, e.g. "attribute", is the same
        self._attribute = value   # the "value" name isn't special
    #
    @attribute.deleter     # decorate with `.deleter`
    def attribute(self):   # again, the method name is the same
        del self._attribute

(Jede Dekoratorverwendung kopiert und aktualisiert das vorherige Eigenschaftsobjekt, also beachten Sie, dass Sie den gleichen Namen für jede Set-, Get- und Delete-Funktion/Methode verwenden sollten).

Nach der obigen Definition ist der ursprüngliche Code für das Setzen, Abrufen und Löschen derselbe:

obj = Obj()
obj.attribute = value  
the_value = obj.attribute
del obj.attribute

Dies sollten Sie vermeiden:

def set_property(property,value):  
def get_property(property):  

Erstens funktioniert die obige Methode nicht, weil Sie kein Argument für die Instanz angeben, auf die die Eigenschaft gesetzt werden soll (normalerweise self ), die da wären:

class Obj:

    def set_property(self, property, value): # don't do this
        ...
    def get_property(self, property):        # don't do this either
        ...

Zweitens wird dadurch der Zweck von zwei speziellen Methoden dupliziert, __setattr__ y __getattr__ .

Drittens: Wir haben auch die setattr y getattr eingebauten Funktionen.

setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value)  # default is optional

El @property Dekorator dient der Erstellung von Gettern und Settern.

Wir könnten zum Beispiel das Einstellungsverhalten ändern, um den eingestellten Wert einzuschränken:

class Protective(object):

    @property
    def protected_value(self):
        return self._protected_value

    @protected_value.setter
    def protected_value(self, value):
        if acceptable(value): # e.g. type or range check
            self._protected_value = value

Im Allgemeinen wollen wir die Verwendung von property und nur direkte Attribute verwenden.

Das ist es, was die Benutzer von Python erwarten. Nach der Regel der geringsten Überraschung sollten Sie versuchen, Ihren Benutzern das zu geben, was sie erwarten, es sei denn, Sie haben einen sehr zwingenden Grund für das Gegenteil.

Demonstration

Angenommen, das geschützte Attribut unseres Objekts soll eine ganze Zahl zwischen 0 und 100 sein und sein Löschen verhindern, mit entsprechenden Meldungen, die den Benutzer über die richtige Verwendung informieren:

class Protective(object):
    """protected property demo"""
    #
    def __init__(self, start_protected_value=0):
        self.protected_value = start_protected_value
    # 
    @property
    def protected_value(self):
        return self._protected_value
    #
    @protected_value.setter
    def protected_value(self, value):
        if value != int(value):
            raise TypeError("protected_value must be an integer")
        if 0 <= value <= 100:
            self._protected_value = int(value)
        else:
            raise ValueError("protected_value must be " +
                             "between 0 and 100 inclusive")
    #
    @protected_value.deleter
    def protected_value(self):
        raise AttributeError("do not delete, protected_value can be set to 0")

(Beachten Sie, dass __init__ bezieht sich auf self.protected_value aber die Eigenschaftsmethoden beziehen sich auf self._protected_value . Dies ist so, dass __init__ verwendet die Eigenschaft über die öffentliche API und stellt sicher, dass sie "geschützt" ist).

Und Verwendung:

>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0

Sind die Namen wichtig?

Ja, sie tun . .setter y .deleter Kopien des ursprünglichen Eigentums anzufertigen. Dadurch können Unterklassen das Verhalten ordnungsgemäß ändern, ohne das Verhalten der übergeordneten Klasse zu verändern.

class Obj:
    """property demo"""
    #
    @property
    def get_only(self):
        return self._attribute
    #
    @get_only.setter
    def get_or_set(self, value):
        self._attribute = value
    #
    @get_or_set.deleter
    def get_set_or_delete(self):
        del self._attribute

Damit dies funktioniert, müssen Sie die entsprechenden Namen verwenden:

obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'  
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error

Ich bin nicht sicher, wo dies nützlich wäre, aber der Anwendungsfall ist, wenn Sie eine Get-, Set- und/oder Delete-Only-Eigenschaft wünschen. Wahrscheinlich am besten, um semantisch gleiche Eigenschaft mit dem gleichen Namen zu halten.

Schlussfolgerung

Beginnen Sie mit einfachen Attributen.

Wenn Sie später Funktionalität rund um das Setzen, Abrufen und Löschen benötigen, können Sie diese mit dem Eigenschaftsdekorator hinzufügen.

Vermeiden Sie Funktionen mit dem Namen set_... y get_... - dafür sind Immobilien da.

34voto

Autoplectic Punkte 7468
In [1]: class test(object):
    def __init__(self):
        self.pants = 'pants'
    @property
    def p(self):
        return self.pants
    @p.setter
    def p(self, value):
        self.pants = value * 2
   ....: 
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20

27voto

Farzad Vertigo Punkte 2033

Verwendung von @property y @attribute.setter hilft Ihnen, nicht nur den "pythonischen" Weg zu gehen, sondern auch die Gültigkeit der Attribute sowohl bei der Erstellung als auch bei der Änderung des Objekts zu überprüfen.

class Person(object):
    def __init__(self, p_name=None):
        self.name = p_name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if type(new_name) == str: #type checking for name property
            self._name = new_name
        else:
            raise Exception("Invalid value for name")

Auf diese Weise "verstecken" Sie tatsächlich _name Attribut von Client-Entwicklern und führen auch Überprüfungen des Typs der Eigenschaft name durch. Beachten Sie, dass bei diesem Ansatz sogar während der Initialisierung der Setter aufgerufen wird. So:

p = Person(12)

Wird dazu führen:

Exception: Invalid value for name

Aber:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception

17voto

mnesarco Punkte 2380

Dies ist eine alte Frage, aber das Thema ist sehr wichtig und immer aktuell. Für den Fall, dass jemand über einfache Getter/Setter hinausgehen möchte, habe ich einen Artikel über superstarke Eigenschaften in Python mit Unterstützung für Slots, Beobachtbarkeit und reduziertem Boilerplate-Code geschrieben.

from objects import properties, self_properties

class Car:
    with properties(locals(), 'meta') as meta:

        @meta.prop(read_only=True)
        def brand(self) -> str:
            """Brand"""

        @meta.prop(read_only=True)
        def max_speed(self) -> float:
            """Maximum car speed"""

        @meta.prop(listener='_on_acceleration')
        def speed(self) -> float:
            """Speed of the car"""
            return 0  # Default stopped

        @meta.prop(listener='_on_off_listener')
        def on(self) -> bool:
            """Engine state"""
            return False

    def __init__(self, brand: str, max_speed: float = 200):
        self_properties(self, locals())

    def _on_off_listener(self, prop, old, on):
        if on:
            print(f"{self.brand} Turned on, Runnnnnn")
        else:
            self._speed = 0
            print(f"{self.brand} Turned off.")

    def _on_acceleration(self, prop, old, speed):
        if self.on:
            if speed > self.max_speed:
                print(f"{self.brand} {speed}km/h Bang! Engine exploded!")
                self.on = False
            else:
                print(f"{self.brand} New speed: {speed}km/h")
        else:
            print(f"{self.brand} Car is off, no speed change")

Diese Klasse kann wie folgt verwendet werden:

mycar = Car('Ford')

# Car is turned off
for speed in range(0, 300, 50):
    mycar.speed = speed

# Car is turned on
mycar.on = True
for speed in range(0, 350, 50):
    mycar.speed = speed

Dieser Code ergibt die folgende Ausgabe:

Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Turned on, Runnnnnn
Ford New speed: 0km/h
Ford New speed: 50km/h
Ford New speed: 100km/h
Ford New speed: 150km/h
Ford New speed: 200km/h
Ford 250km/h Bang! Engine exploded!
Ford Turned off.
Ford Car is off, no speed change

Mehr über das Wie und Warum erfahren Sie hier: https://mnesarco.github.io/blog/2020/07/23/python-metaprogramming-properties-on-steroids

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