使用getter和setter的pythonic方法是啥?

Posted

技术标签:

【中文标题】使用getter和setter的pythonic方法是啥?【英文标题】:What's the pythonic way to use getters and setters?使用getter和setter的pythonic方法是什么? 【发布时间】:2011-02-07 07:01:58 【问题描述】:

我这样做是这样的:

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

object.property = value  
value = object.property

我是 Python 的新手,所以我仍在探索语法,我想要一些关于这样做的建议。

【问题讨论】:

【参考方案1】:

试试这个: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

【讨论】:

实例化_x时是否在初始化器中调用了x的setter? @Casey:不。对._x 的引用(这不是属性,只是一个普通属性)绕过property 包装。只有对.x 的引用通过property mypy 希望这两个函数在一起(在 x 及其设置器中没有其他函数) 如果您实际上需要在 getter 或 setter 中执行一些操作/操作,而不仅仅是检索私有属性,那么这是 Pythonic。对于这种简单的情况,尽管在其他语言中使用 getter/setter 被认为是最佳实践,但在 Python 中只需创建一个公共属性。简单、禅宗、切中要害【参考方案2】:

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

“Pythonic”方式不是使用“getters”和“setters”,而是使用普通属性,如问题所示,del 用于删除(但名称已更改保护无辜的......内置):

value = 'something'

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

如果以后您想修改设置和获取,您可以通过使用property 装饰器来完成,而无需更改用户代码:

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

(每个装饰器使用都会复制和更新先前的属性对象,因此请注意,您应该为每个 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 之间的整数,并防止其被删除,并通过适当的消息告知用户其正确使用:

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

(注意__init__ 指的是self.protected_value,但属性方法指的是self._protected_value。这样__init__ 通过公共API 使用该属性,确保它是“受保护的”。)

及用法:

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

我不确定这在哪里有用,但用例是如果您想要一个仅获取、设置和/或删除的属性。可能最好坚持语义相同的同名属性。

结论

从简单的属性开始。

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

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

【讨论】:

在您的演示中,__init__ 方法指的是self.protected_value,但getter 和setter 指的是self._protected_value。你能解释一下这是如何工作的吗?我测试了你的代码,它按原样工作 - 所以这不是一个错字。 @codeforester 我希望早点回复我的答案,但在我可以之前,这个评论就足够了。我希望你能看到它通过公共 api 使用该属性,确保它是“受保护的”。用属性“保护”它然后在 __init__ 中使用非公共 api 是没有意义的吗? 是的,@AaronHall 现在知道了。我没有意识到self.protected_value = start_protected_value实际上是在调用setter函数;我以为这是一项任务。 恕我直言,这应该是公认的答案,如果我理解正确的话,python 与 java 相比正好相反。您可以将所有内容公开并在以后添加隐私 @XValidated PEP 8 says: "对于简单的公共数据属性,最好只公开属性名称,不要使用复杂的访问器/修改器方法。请记住,Python 提供了一个简单的路径为了将来的增强,如果您发现一个简单的数据属性需要增加功能行为。在这种情况下,使用属性将功能实现隐藏在简单的数据属性访问语法后面。”【参考方案3】:
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

【讨论】:

【参考方案4】:

使用@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

【讨论】:

我认为self.name 应该读作self._namep = person('Mike') 应该读作p = Person('Mike')。编辑队列已满……干杯! 如果我们在__init__ 方法中将self.name 更改为self._name,我们将在对象实例化时失去验证。使用该Person(12) 可以创建(虽然不能更新为使用另一个数字作为名称)。【参考方案5】:

查看@property decorator。

【讨论】:

这几乎是一个仅链接的答案。 @AaronHall:但事实并非如此。 "Strip the markup, and you still get at least a little bit of useful information." 这是一个完整的答案吗?链接不是答案。 我认为这是一个很好的答案,因为那里的文档清楚地说明了如何使用它(如果 python 实现发生变化,它将保持最新状态,并且它将 OP 引导到 answer-er 的方法建议。@Jean-FrançoisCorbett 清楚地说明了“如何”这是一个完整的答案。 无论如何,这个答案不会对其他答案添加任何有用的东西,理想情况下应该删除。【参考方案6】:

这是一个老问题,但这个话题非常重要,而且总是最新的。如果有人想超越简单的 getter/setter,我写了一篇关于 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 speedkm/h Bang! Engine exploded!")
                self.on = False
            else:
                print(f"self.brand New speed: speedkm/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

【讨论】:

获得objects的推荐方式是什么? @user66081 代码在文章中。您可以复制/粘贴它。这是一个非常小的脚本。【参考方案7】:

属性非常有用,因为您可以将它们用于赋值,但也可以包括验证。您可以在使用装饰器 @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/

【讨论】:

【参考方案8】:

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

如果您使用@property 来简单地访问属性,例如

class myClass:
    def __init__(a):
        self._a = a

    @property
    def a(self):
        return self._a

使用它来访问 every* 属性!使用@property 访问某些属性并在没有访问器的情况下保留一些其他属性public(即没有下划线的名称)是一种不好的做法,例如不要这样做

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self.a

请注意,self.b 在此处没有显式访问器,即使它是公开的。

setter(或mutators)类似,请随意使用@attribute.setter,但保持一致!当你这样做时

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b 

    @a.setter
    def a(self, value):
        return self.a = value

我很难猜出你的意图。一方面,您说ab 都是公开的(它们的名称中没有前导下划线),所以理论上我应该被允许访问/变异(获取/设置)两者。但是你只为a 指定了一个显式的mutator,这告诉我也许我不应该能够设置b。由于您提供了显式修改器,我不确定是否缺少显式访问器 (@property) 是否意味着我无法访问这些变量中的任何一个,或者您只是在使用 @property 时节俭。

*例外情况是,当您明确希望某些变量可访问或可变但不能两者兼有,或者您希望在访问或改变属性时执行一些额外的逻辑。这是我个人使用@property@attribute.setter 的时候(否则没有明确的公共属性访问器/突变器)。

最后,PEP8 和 Google 风格指南建议:

PEP8,Designing for Inheritance 说:

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

另一方面,根据 Google Style Guide Python Language Rules/Properties 的建议是:

在新代码中使用属性来访问或设置通常使用简单、轻量级访问器或设置器方法的数据。应使用 @property 装饰器创建属性。

这种方法的优点:

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

和缺点:

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

【讨论】:

我强烈反对。如果我的对象有 15 个属性,并且我希望使用 @property 计算一个属性,那么让其余的也使用 @property 似乎是一个糟糕的决定。 同意,但前提是您需要 @property 来处理此特定属性的特定内容(例如,在返回属性之前执行一些特殊逻辑)。否则你为什么要用@propery而不是其他属性来装饰一个属性? @Quelklef 请参阅帖子中的旁注(标有星号)。 嗯...如果您没有做旁注中提到的事情之一,那么您不应该使用@property 开始,对吧?如果你的 getter 是 return this._x 而你的 setter 是 this._x = new_x,那么使用 @property 就有点傻了。 嗯,也许吧。我个人会说这不好——这完全是多余的。但我可以看到你来自哪里。我想我刚刚读到您的帖子说“使用@property 时最重要的是保持一致。”【参考方案9】:

您可以使用魔术方法__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__ 仅在未找到该属性时调用。

【讨论】:

以上是关于使用getter和setter的pythonic方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

使用getter和setter的pythonic方法是啥?

Python 抽象 setter 和 getter

Python中的setter和getter

Python 类@property:使用setter 但避开getter?

在 Python 中使用 setter 和 getter 保护 numpy 属性

Python的property _getter和setter方法