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

Jor*_*rte 298 python getter-setter

我这样做:

def set_property(property,value):  
def get_property(property):  
Run Code Online (Sandbox Code Playgroud)

要么

object.property = value  
value = object.property
Run Code Online (Sandbox Code Playgroud)

我是Python的新手,所以我还在探索语法,我想对此做一些建议.

Gri*_*iom 645

试试这个:Python属性

示例代码是:

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
Run Code Online (Sandbox Code Playgroud)

  • 如果您实际上需要在 getter 或 setter 中执行某些操作/操作而不仅仅是检索私有属性,那么这就是 Pythonic。对于这个简单的情况,尽管在其他语言中使用 getter/setter 被认为是最佳实践,但在 Python 中只需创建一个公共属性。简单、禅宗、切中要害 (12认同)
  • 实例化 _x 时,是否在初始化程序中调用了 x 的 setter? (3认同)
  • @Casey:否。对`._x`(不是属性,只是一个普通的属性)的引用绕过了“ property”包装。只有对.x的引用才能通过属性。 (3认同)

Aar*_*all 220

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

"Pythonic"方式不是使用"getters"和"setter",而是使用普通属性,如问题演示,以及del解除引用(但更改名称以保护无辜......内置):

value = 'something'

obj.attribute = value  
value = obj.attribute
del obj.attribute
Run Code Online (Sandbox Code Playgroud)

如果以后要修改设置和获取,则可以通过使用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
Run Code Online (Sandbox Code Playgroud)

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

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

obj = Obj()
obj.attribute = value  
the_value = obj.attribute
del obj.attribute
Run Code Online (Sandbox Code Playgroud)

你应该避免这个:

def set_property(property,value):  
def get_property(property):  
Run Code Online (Sandbox Code Playgroud)

首先,上述方法不起作用,因为您没有为属性设置为(通常self)的实例提供参数,这将是:

class Obj:

    def set_property(self, property, value): # don't do this
        ...
    def get_property(self, property):        # don't do this either
        ...
Run Code Online (Sandbox Code Playgroud)

其次,这复制了两种特殊方法的目的,__setattr__并且__getattr__.

第三,我们也有setattrgetattr内建函数.

    setattr(object, 'property_name', value)
    getattr(object, 'property_name', default_value)  # default is optional
Run Code Online (Sandbox Code Playgroud)

@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
Run Code Online (Sandbox Code Playgroud)

通常,我们希望避免使用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")
Run Code Online (Sandbox Code Playgroud)

用法:

>>> 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
Run Code Online (Sandbox Code Playgroud)

名字是否重要?

是的他们这样做..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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

结论

从简单的属性开始.

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

避免命名函数set_...get_...-这就是属性是.

  • 是的,@AaronHall 现在明白了。我没有意识到 `self.protected_value = start_protected_value` 实际上是在调用 setter 函数;我以为是作业。 (3认同)
  • 恕我直言,这应该是公认的答案,如果我理解正确的话,python 与例如 java 相比正好相反。默认情况下将所有内容设为私有并在需要时在 Python 中公开需要时编写一些额外的代码,您可以将所有内容设为公开并稍后添加隐私 (3认同)
  • 在您的演示中,__init__方法指的是self.protected_value,但getter和setter指的是self._protected_value。您能解释一下这是如何工作的吗?我测试了您的代码,它按原样工作-所以这不是错字。 (2认同)
  • @codeforester 我希望早点在我的回答中做出回应,但在我可以之前,这条评论应该足够了。我希望您可以看到它通过公共 api 使用该属性,确保它受到“保护”。用属性“保护”它然后在`__init__`中使用非公共api是没有意义的吗? (2认同)
  • @XValidated [PEP 8 说](https://www.python.org/dev/peps/pep-0008/#designing-for-inheritance): *“对于简单的公共数据属性,最好只公开属性名称,没有复杂的访问器/修改器方法。请记住,如果您发现简单的数据属性需要增长功能行为,Python 提供了一条未来增强的简单路径。在这种情况下,请使用属性将功能实现隐藏在简单数据属性后面访问语法。"* (2认同)

Aut*_*tic 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
Run Code Online (Sandbox Code Playgroud)


Kev*_*tle 17

看看@property装饰师.

  • 这几乎只是一个链接答案. (29认同)
  • 这是一个完整的答案?链接不是答案. (7认同)
  • @AaronHall:但事实并非如此.["删除标记,你仍然至少得到一些有用的信息."](https://meta.stackexchange.com/questions/225370/your-answer-is-in-another-castle-when-是-的回答 - 不的回答) (6认同)

Far*_*igo 14

使用@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")
Run Code Online (Sandbox Code Playgroud)

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

p = Person(12)
Run Code Online (Sandbox Code Playgroud)

将导致:

Exception: Invalid value for name
Run Code Online (Sandbox Code Playgroud)

但:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception
Run Code Online (Sandbox Code Playgroud)


mne*_*rco 10

这是一个古老的问题,但该主题非常重要且始终是最新的。如果有人想超越简单的 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} {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")
Run Code Online (Sandbox Code Playgroud)

这个类可以这样使用:

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
Run Code Online (Sandbox Code Playgroud)

此代码将产生以下输出:

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
Run Code Online (Sandbox Code Playgroud)

关于如何以及为什么在这里的更多信息:https : //mnesarco.github.io/blog/2020/07/23/python-metaprogramming-properties-on-steroids


小智 6

属性非常有用,因为您可以将它们与赋值一起使用,但也可以包含验证。您可以在此代码中看到使用装饰器 @property 和 @<property_name>.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)
Run Code Online (Sandbox Code Playgroud)

在我写的这篇文章中还有更多细节:https : //pythonhowtoprogram.com/how-to-create-getter-setter-class-properties-in-python-3/


Tom*_*iak 5

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

如果您只是@property用来访问一个属性,例如

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

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

Run Code Online (Sandbox Code Playgroud)

使用它来访问每个*属性!在没有访问器的情况下使用某些属性访问某些属性@property并将某些其他属性设为public(即没有下划线的名称)将是一种不好的做法,例如do not do

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

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

Run Code Online (Sandbox Code Playgroud)

请注意,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
Run Code Online (Sandbox Code Playgroud)

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

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

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

PEP8,为继承而设计说:

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

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

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

这种方法的优点:

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

和缺点:

必须从objectPython 2 中继承。可以像操作符重载一样隐藏副作用。对于子类可能会造成混淆。

  • 我强烈不同意。如果我的对象有 15 个属性,并且我希望使用“@property”计算其中一个属性,那么让其余属性也使用“@property”似乎是一个糟糕的决定。 (2认同)