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类中的私有属性编写getter-setter方法?