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


340

我这样做:

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

要么

object.property = value  
value = object.property

我是Python的新手,因此我仍在探索语法,并且我希望对此提供一些建议。

Answers:


684

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

2
实例化_x时,是否在初始化程序中调用了x的setter?
Casey

7
@Casey:否。对._x(不是属性,只是一个普通属性)的引用绕过了property包装。仅引用.x通过property
ShadowRanger

276

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

“ 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

(每个装饰器用法都会复制并更新先前的属性对象,因此请注意,对于每个设置,获取和删除功能/方法,都应使用相同的名称。

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

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):
    """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_...-这就是属性的作用。


除了复制可用功能外,为什么还要避免编写自己的setter和getter?我了解这可能不是Python的方式,但是有人真的会遇到严重的问题吗?
user1350191 '18

4
在您的演示中,__init__方法是指方法,self.protected_value但getter和setter是指self._protected_value。您能解释一下这是如何工作的吗?我测试了您的代码,它按原样工作-所以这不是错字。
codeforester

2
@codeforester我希望能在早些时候回答我,但是直到我可以的时候,此注释才足够。我希望您可以看到它通过公共api使用该属性,从而确保它受到“保护”。用属性“保护”它,然后使用非公共api代替,这是没有道理的__init__吗?
亚伦·霍尔

2
是的,@ AaronHall现在知道了。我没有意识到self.protected_value = start_protected_value实际上是在调用setter函数。我认为这是一项任务。
codeforester '18年

1
恕我直言,这应该是一个可以接受的答案,如果我理解正确的话,与例如Java相比,python恰好相反。您可以将所有内容公开并稍后添加隐私,而不是默认情况下将所有内容默认为私有并在python中公开需要时编写一些额外的代码
idclev 463035818 '18

27
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

17

使用@propertyand @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客户端开发人员的属性,并且还可以检查名称属性类型。请注意,即使在启动过程中也遵循此方法,将调用设置程序。所以:

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

16

34
这几乎是仅链接的答案。
亚伦·霍尔


7
这是一个完整的答案吗?链接不是答案。
Andy_A̷n̷d̷y̷

我认为这是一个很好的答案,因为那里的文档清楚地说明了如何使用它(如果python实现发生更改,它将保持最新状态,并且将OP定向到答案-er建议的方法。@Jean-FrançoisCorbett明确指出“如何”是一个完整的答案
HunnyBear

无论如何,此答案不会对其他答案添加任何有用的信息,因此理想情况下应将其删除。
乔治,

5

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

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

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

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

使用它来访问every *属性!在不使用访问器的情况下使用以下属性访问某些属性@property并使其他属性公开(即名称不带下划线)是不明智的做法,例如,不要这样做

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

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

请注意,self.b即使它是公共的,这里也没有显式访问器。

二传手(或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。由于您提供了一个显式的mutator,所以我不确定是否缺少显式的accessor(@property)意味着我不能访问这些变量之一,或者您在使用时节俭@property

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

最后,PEP8和Google样式指南的建议:

PEP8,继承设计说:

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

另一方面,根据Google样式指南Python语言规则/属性,建议:

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

这种方法的优点:

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

利弊:

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


1
我非常不同意。如果我的对象上有15个属性,而我想用来计算@property,那么其余的属性@property似乎也很难使用。
Quelklef

同意,但前提是您需要该特定属性的某些特定信息@property(例如,在返回属性之前执行一些特殊逻辑)。否则,为什么要用@propery而不是其他装饰一个属性?
Tomasz Bartkowiak

@Quelklef看到帖子中的旁注(标有星号)。
Tomasz Bartkowiak

好吧...如果您没有做旁注中提到的事情之一,那么您不应该从头@property开始,对吗?如果您的getter是return this._x,setter是this._x = new_x,那么@property根本使用起来是很愚蠢的。
Quelklef

1
嗯,也许吧。我个人会说这不是很好-完全多余。但是我可以看到你来自哪里。我想我刚读您的帖子是说“使用时最重要的@property是保持一致”。
Quelklef

-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__仅在找不到属性时调用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.