什么是使用getter和setter的pythonic方法?

时间:2010-04-13 04:38:24

标签: python getter-setter

我这样做:

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

object.property = value  
value = object.property

我是Python的新手,所以我仍然在探索语法,我想就这样做提出一些建议。

9 个答案:

答案 0 :(得分:601)

试试这个:Python Property

示例代码为:

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

答案 1 :(得分:182)

  

使用getter和setter的pythonic方式是什么?

“Pythonic”方式使用“getters”和“setters”,但使用普通属性(如问题演示)和del用于解除引用(但名称)改变以保护无辜的......内在的):

value = 'something'

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

如果以后要修改设置和获取,则可以使用property装饰器,无需更改用户代码即可完成此操作:

class Obj:
    """property demo"""
    #
    @property
    def attribute(self): # implements the get - this name is *the* name
        return self._attribute
    #
    @attribute.setter
    def attribute(self, value): # name must be the same
        self._attribute = value
    #
    @attribute.deleter
    def attribute(self): # again, name must be the same
        del self._attribute

(每个装饰器复制并更新先前的属性对象,因此请注意,您应该为每个set,get和delete函数/方法使用相同的名称。)

定义上述内容后,原始设置,获取和删除都是相同的:

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

你应该避免这种情况:

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

首先,上面的方法不起作用,因为你没有为属性设置的实例提供一个参数(通常是self),这可能是:

class Obj:

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

其次,这重复了两个特殊方法的目的,__setattr____getattr__

第三,我们还有setattrgetattr内置函数。

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

@property装饰器用于创建getter和setter。

例如,我们可以修改设置行为以限制设置的值:

    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

通常,我们希望避免使用property并仅使用直接属性。

这是Python用户所期望的。遵循最小惊喜的规则,你应该尝试给你的用户他们期望的东西,除非你有一个非常令人信服的理由相反。

示范

例如,假设我们需要对象的protected属性为0到100之间的整数(包括0和100),并阻止删除,并使用适当的消息通知用户其正确用法:

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

用法:

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

这些名字是否重要?

是的,他们.setter.deleter制作原始媒体资源的副本。这允许子类在不改变父行为的情况下正确地修改行为。

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

现在要使用它,你必须使用相应的名称:

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

我不确定这在哪里有用,但用例是你想要一个get,set和/或delete-only属性。可能最好坚持具有相同名称的语义相同的属性。

结论

从简单属性开始。

如果您以后需要围绕设置,获取和删除的功能,可以使用属性装饰器添加它。

避免使用名为set_...get_...的函数 - 这就是属性的用途。

答案 2 :(得分:25)

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

答案 3 :(得分:17)

答案 4 :(得分:9)

使用@property@attribute.setter不仅可以帮助您使用“pythonic”方式,还可以在创建对象和更改对象时检查属性的有效性。

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

通过这种方式,您实际上从客户端开发人员“隐藏”_name属性,并对名称属性类型执行检查。请注意,即使在启动期间也遵循此方法,将调用setter。所以:

p = Person(12)

将导致:

Exception: Invalid value for name

可是:

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

答案 5 :(得分:6)

这是一个古老的问题,但是这个话题非常重要,并且总是最新的。万一有人想超越简单的获取器/设置器,我写了一篇关于python中超能力属性的文章,该文章支持插槽,可观察性和简化的样板代码。

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

此类可以这样使用:

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

此代码将产生以下输出:

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

有关此处的方式和原因的更多信息:https://mnesarco.github.io/blog/2020/07/23/python-metaprogramming-properties-on-steroids

答案 6 :(得分:4)

属性非常有用,因为您可以将它们与赋值一起使用,但也可以包含验证。您可以在使用装饰器 @property 和 @.setter 来创建方法的地方看到此代码:

# Python program displaying the use of @property 
class AgeSet:
    def __init__(self):
        self._age = 0

    # using property decorator a getter function
    @property
    def age(self):
        print("getter method called")
        return self._age

    # a setter function
    @age.setter
    def age(self, a):
        if(a < 18):
            raise ValueError("Sorry your age is below eligibility criteria")
        print("setter method called")
        self._age = a

pkj = AgeSet()

pkj.age = int(input("set the age using setter: "))

print(pkj.age)

我在这篇文章中也写了更多详细信息:https://pythonhowtoprogram.com/how-to-create-getter-setter-class-properties-in-python-3/

答案 7 :(得分:1)

您可以选择是否使用访问器/更改器(即@attr.setter@property),但最重要的是要保持一致!

PEP8 Designing for Inheritance说:

  

对于简单的公共数据属性,最好仅公开属性名称,而无需复杂的访问器/更改器方法。请记住,如果您发现简单的数据属性需要增强功能行为,则Python为将来的增强提供了简便的方法。在这种情况下,请使用属性将功能实现隐藏在简单的数据属性访问语法之后。

另一方面,根据 Google样式指南 Python Language Rules/Properties,建议是:

  

使用新代码中的属性来访问或设置数据,而通常使用简单,轻便的访问器或设置器方法即可。属性应使用@property装饰器创建。

这种方法的优点:

  

通过消除用于简单属性访问的显式get和set方法调用,提高了可读性。允许计算是懒惰的。考虑使用Python方式维护类的接口。在性能方面,当直接变量访问是合理的时,允许属性绕过需要琐碎的访问器方法的情况。这也允许将来在不破坏接口的情况下添加访问器方法。

缺点:

  

必须从Python 2中的object继承。可以隐藏副作用,就像运算符重载一样。可能会混淆子类。

答案 8 :(得分:-1)

您可以使用魔术方法__getattribute____setattr__

class MyClass:
    def __init__(self, attrvalue):
        self.myattr = attrvalue
    def __getattribute__(self, attr):
        if attr == "myattr":
            #Getter for myattr
    def __setattr__(self, attr):
        if attr == "myattr":
            #Setter for myattr

请注意,__getattr____getattribute__是不同的。 __getattr__仅在找不到属性时调用。