Python中的“私有”属性属性

Posted

技术标签:

【中文标题】Python中的“私有”属性属性【英文标题】:"Private" attribute properties in Python 【发布时间】:2014-06-19 09:08:44 【问题描述】:

我对 Python 比较陌生,所以我希望我没有遗漏一些东西,但是这里......

我正在尝试编写一个 Python 模块,并且我想创建一个具有“私有”属性的类,该属性只能(或者可能“应该”)通过模块中的一个或多个函数进行修改。这是为了使模块更加健壮,因为在这些函数之外设置此属性可能会导致不需要的行为。例如,我可能有:

    为散点图存储 x 和 y 值的类,Data 从文件中读取 x 和 y 值并将它们存储在类中的函数,read() 绘制它们的函数,plot()

在这种情况下,我希望用户无法执行以下操作:

data = Data()
read("file.csv", data)
data.x = [0, 3, 2, 6, 1]
plot(data)

我意识到在名称中添加一个前导下划线向用户表明不应更改该属性,即重命名为 _x 并添加一个属性装饰器,以便用户可以访问该值而不会感到内疚。但是,如果我也想添加一个 setter 属性怎么办:

class Data(object):
    _x = []
    _y = []
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        # Do something with value
        self._x = value

我现在处于与以前相同的位置 - 用户不能再直接访问属性 _x,但他们仍然可以使用以下方式设置它:

data.x = [0, 3, 2, 6, 1]

理想情况下,我会将属性函数定义重命名为 _x(),但这会导致混淆 self._x 的实际含义(取决于它们的声明顺序,这似乎会导致调用 setter递归或忽略设置器以支持属性)。

我能想到的几个解决方案:

    在属性__x 中添加一个双前导下划线,以便名称变得混乱,并且不会与setter 函数混淆。据我了解,这应该保留给一个类不希望与可能的子类共享的属性,所以我不确定这是否合法使用。 重命名属性,例如_x_stored。虽然这完全解决了问题,但它使代码更难阅读并引入了命名约定问题——我要重命名哪些属性?只是那些相关的?只有那些有属性的?只有这个班级的人?

以上任一解决方案都适用吗?如果没有,有没有更好的方法来解决这个问题?

编辑

感谢您迄今为止的回复。 cmets抛出的几点:

    我想保留 setter 属性给我的额外逻辑 - 上面示例中的 # Do something with value 部分 - 所以通过直接访问 self._x 在内部设置属性并不能解决问题。 删除 setter 属性并创建一个单独的函数 _set_x() 确实解决了问题,但不是一个非常简洁的解决方案,因为它允许以两种不同的方式设置 _x - 通过调用该函数或通过直接访问self._x。然后,我必须跟踪哪些属性应该由它们自己的(非属性)setter 函数设置,哪些应该通过直接访问进行修改。我可能更愿意使用我上面建议的解决方案之一,因为即使它们在类中弄乱了命名约定,但它们在类外的使用至少是一致的,即它们都使用属性的语法糖.如果没有办法以更简洁的方式做到这一点,那么我想我只需要选择造成干扰最小的那个。

【问题讨论】:

我很困惑为什么要定义一个不希望人们使用的 setter。 @cmd - 我仍然希望能够在模块内的其他功能中使用设置器。例如,我可能有一个需要能够设置 x 和 y 值的 interpolate() 函数。 但是那些可以只设置_x_y 如果您想在设置时捕获额外的逻辑,我会创建一个内部使用的函数_setx()。我不认为其他选项的复杂性值得有限的语法糖。 【参考方案1】:

如果您想阻止用户更改属性,但希望明确他们可以阅读它,我会使用 @property 而不提供 setter,类似于您之前描述的内容:

class Data(object):
    def __init__(self):
       self._x = []
       self._y = []

    @property 
    def x(self):
        return self._x

    @property 
    def y(self):
        return self._x

我知道您提到“如果我想向属性添加 setter 怎么办?”,但我想我会反驳:如果您不希望您的客户能够设置属性,为什么要添加 setter ?内部可以直接访问self._x

对于直接访问_x_y 的客户端,任何带有“_”前缀的变量在Python 中都被理解为“私有”,因此您应该相信您的客户端会遵守这一点。如果他们不遵守这一点,最终把事情搞砸了,那是他们自己的错。这种思维方式与许多其他语言(C++、Java 等)相反,在这些语言中,保持数据的私密性非常重要,但 Python 的文化在这方面有所不同。

编辑

另一个注意事项,由于在这种特殊情况下您的私有属性是列表,它们是可变的(与字符串或整数不同,它们是不可变的),客户端最终可能会意外地更改它们:

>>> d = Data()
>>> print d.x
['1', '2']
>>> l = d.x
>>> print l
['1', '2']
>>> l.append("3")
>>> print d.x
['1', '2', '3']  # Oops!

如果你想避免这种情况,你需要你的财产返回一份清单:

@property
def x(self):
    return list(self._x)

【讨论】:

我知道使用self._x 的直接访问在内部设置值很容易,但这消除了拥有setter 给我带来的优势,即# Do something with value 位。 啊,你的意思是让 setter 做的不仅仅是简单的self._x = blah?我想在这种情况下,您要么也必须将 @property 添加到私有属性(我认为您提到过您不喜欢),或者在您想分配给私有时始终记得调用一些内部辅助函数财产。 有没有更优雅的方法来做到这一点,不会引入不一致的命名约定?请参阅原始帖子编辑。 (@cmd, @kindall)【参考方案2】:

如果您想要不那么复杂的属性,管理自己的存储而不让其“在后台”更改,您可以定义一个类(类似于属性)并使用它来声明您的类成员:

我称我为“场”:

class Field:    
    def __init__(self,default=None):    
        self.valueName = None               # actual attribute name
        self.default   = default            # type or value or lambda
        if not callable(default): self.default = lambda:default
        self._didSet   = None               # observers
        self._willSet  = None

    def findName(self,owner):                     # find name of field
        if self.valueName: return                 # once per field for class
        for name,attr in owner.__dict__.items():
            if attr is self: 
                self.valueName = f"<name>"      # actual attribute name
                break

    def __get__(self,obj,owner=None):             # generic getter
        if not obj: return self
        self.findName(owner or type(obj))
        value = getattr(obj,self.valueName,self)  # attribute from instance
        if value is self:                 
            value = self.default()                # default value
            setattr(obj,self.valueName,value)     # set at 1st reference
        return value

    def __set__(self,obj,value):                  # generic setter
        self.findName(type(obj))
        if self._willSet: value    = self._willSet(obj,value)
        if self._didSet:  oldValue = self.__get__(obj)
        setattr(obj,self.valueName,value)         # attribute in instance
        if self._didSet: self._didSet(obj,oldValue)

    def willSet(self,f): self._willSet = f
    def didSet(self,f):  self._didSet  = f

用法:

class myClass:

    lastName  = Field("Doe")
    firstName = Field("")
    age       = Field(int)
    gender    = Field("M")
    relatives = Field(list)

    @lastName.willSet
    def _(self,newValue):              # no function name needed
        return newValue.capitalize()

    @lastName.didSet
    def _(self,oldValue):              # no function name needed
        print('last name changed from',oldValue,'to',self.lastName)

c           = myClass()

c.firstName = "John"
c.lastName  = "Smith"
# last name changed from Doe to Smith

c.relatives.extend(['Lucy','Frank'])

print(c.gender)
# M

print(c.__dict__)
# '<lastName>': 'Smith', '<firstName>': 'John', 
   '<relatives>': ['Lucy', 'Frank'], '<gender>': 'M'

无法从 Python 访问添加到实例的属性,因为它们使用的标识符在代码中无效。 因为你在类级别定义了默认值,所以不需要在构造函数中设置字段值(尽管你仍然可以根据需要进行设置)

字段值仅在引用时作为实例属性添加,从而提高实例创建过程的效率。

请注意,我的实际 Field 类要复杂得多,并且支持更改跟踪、更多观察者功能、类型检查和只读/计算字段。我把它归结为这个回应的要点

对于私有/公共方法保护,您可能需要查看此answer

【讨论】:

以上是关于Python中的“私有”属性属性的主要内容,如果未能解决你的问题,请参考以下文章

Python 中的“公共”或“私有”属性?啥是最好的方法?

Python中的面向对象(高级)之私有方法、多继承、多态

如何为python类中的私有属性编写getter-setter方法?

Python中的类方法及属性总结举例,编写memcached启动脚本举例

python-类的封装(私有属性,私有方法)

python-类的封装(私有属性,私有方法)