Python 静态不可变属性
Python static immutable properties
在Python中实现静态不可变属性的正确方法是什么?
最小示例:
一个程序模块'Family'有一个classParent
,定义如下:
class Parent():
def __init__(self, type):
self.type = type
父项可以是以下两种字符串类型之一:'mother'
或 'father'
。这个class的外部用户应该可以在构造的时候设置类型,然后查询parent.type
得到这两个值中的一个。此外,Family 模块的其他部分使用 Parent
class 并依赖于返回这两个值中的任何一个。因此,对类型的要求如下:
- 在用户外部和家庭模块内部均可使用
- 总是 'mother' 或 'father',因此基于不可变变量
一种天真的方法可以鼓励通过传递字符串来设置类型:
parent = Parent('mother')
但这允许意外拼写错误(例如 Parent('omther')
)。为了防止这种情况,我使用以下方法:
class Parent():
TYPE_MOTHER = 'mother'
TYPE_FATHER = 'father'
TYPES = [TYPE_MOTHER, TYPE_FATHER]
def __init__(self, type):
assert type in self.TYPES, ('Invalid type "%s"' % type)
self.type = type
parent = Parent(Parent.TYPE_MOTHER)
但是,没有什么可以阻止用户随意更改这些静态变量,例如:
parent = Parent('mother')
Parent.TYPE_MOTHER = 'burrito'
print(parent.type == Parent.TYPE_MOTHER)
#> False
为了解决这个问题,我考虑使用 @staticmethod
和 @property
注释:
class Parent():
@property
@staticmethod
def TYPE_MOTHER():
return 'mother'
@property
@staticmethod
def TYPE_FATHER():
return 'father'
这不会阻止用户将字符串传递给构造函数(例如 Parent('mother')
),但至少可以防止他们通过更改父类型来搞砸 Family 模块。
我在使用此方法时遇到的问题:
- 真丑,4 行代码而不是 1 行
- 感觉很老套(可能是由于我在其他语言中使用语言支持的私有静态变量的经验)
- 我的 IDE 的 linter 不喜欢它,因为没有 'self' 参数(即使提供了)
给你的问题:
- 这个方法Pythonic吗?
- 有更好的方法吗?
- 你能提出一个模式来实现这种不变性,同时强制用户只使用我希望他们使用的变量,防止他们将原始字符串传递给构造函数吗?
这不就是类的目的吗。只需 Mother
和 Father
类 继承自:
class Parent():
def __init__(self, type):
self.type = type
class Mother(Parent):
def __init__(self):
super().__init__('mother')
class Father(Parent):
def __init__(self):
super().__init__('father')
parent = Mother()
总的来说这个方法很好,就是有点长。如果您不想实现继承,最短的方法是使 TYPES
列表成为受保护的 class 变量:
class Parent():
def __init__(self, type):
self._TYPES = ['mother', 'father']
assert type in self._TYPES, ('Invalid type "%s"' % type)
self.type = type
我为我的问题找到了一个简洁的答案 - Enum
class (doc).
from enum import Enum
class Parent():
class TYPES(Enum):
MOTHER = 'mother'
FATHER = 'father'
def __init__(self, type:TYPES):
assert type in self.TYPES, ('Invalid type "%s"' % type)
self.type = type
parent = Parent(Parent.TYPES.MOTHER)
在这种情况下,用户仍然可以覆盖 Parent.TYPES
。除了使用 __setattr__
并捕获恶意写入之外,我想不出另一种方法来防止它。如果您能在这里想到什么,请分享您的想法。
这种模式具有以下优势:
- 由于 Enum 的政策,无法覆盖单独的属性 MOTHER 和 FATHER
- 由于
__setattr__
,无法覆盖 TYPES
- 无法使用原始字符串代替 Parent.TYPES.MOTHER(或 .FATHER)
- 兼容
assert type in self.TYPES
或assert isinstance(type, self.TYPES)
到目前为止,我可以看出两个用法上的差异:
- 如果我想获得实际值 - 即。 'mother' 或 'father',那么我需要使用枚举的
.value
属性。因此,我需要使用 parent.type
而不是 parent.type
(return "TYPES.MOTHER"
),我需要使用 parent.type.value
正确 returns 'mother'
.
- 要打印 TYPES 的内容我需要使用
list(self.TYPES)
我认为使用 Enum 我也可以放弃 ALL_CAPS 标准,因为我不需要表示 types
是静态的。
在Python中实现静态不可变属性的正确方法是什么?
最小示例:
一个程序模块'Family'有一个classParent
,定义如下:
class Parent():
def __init__(self, type):
self.type = type
父项可以是以下两种字符串类型之一:'mother'
或 'father'
。这个class的外部用户应该可以在构造的时候设置类型,然后查询parent.type
得到这两个值中的一个。此外,Family 模块的其他部分使用 Parent
class 并依赖于返回这两个值中的任何一个。因此,对类型的要求如下:
- 在用户外部和家庭模块内部均可使用
- 总是 'mother' 或 'father',因此基于不可变变量
一种天真的方法可以鼓励通过传递字符串来设置类型:
parent = Parent('mother')
但这允许意外拼写错误(例如 Parent('omther')
)。为了防止这种情况,我使用以下方法:
class Parent():
TYPE_MOTHER = 'mother'
TYPE_FATHER = 'father'
TYPES = [TYPE_MOTHER, TYPE_FATHER]
def __init__(self, type):
assert type in self.TYPES, ('Invalid type "%s"' % type)
self.type = type
parent = Parent(Parent.TYPE_MOTHER)
但是,没有什么可以阻止用户随意更改这些静态变量,例如:
parent = Parent('mother')
Parent.TYPE_MOTHER = 'burrito'
print(parent.type == Parent.TYPE_MOTHER)
#> False
为了解决这个问题,我考虑使用 @staticmethod
和 @property
注释:
class Parent():
@property
@staticmethod
def TYPE_MOTHER():
return 'mother'
@property
@staticmethod
def TYPE_FATHER():
return 'father'
这不会阻止用户将字符串传递给构造函数(例如 Parent('mother')
),但至少可以防止他们通过更改父类型来搞砸 Family 模块。
我在使用此方法时遇到的问题:
- 真丑,4 行代码而不是 1 行
- 感觉很老套(可能是由于我在其他语言中使用语言支持的私有静态变量的经验)
- 我的 IDE 的 linter 不喜欢它,因为没有 'self' 参数(即使提供了)
给你的问题:
- 这个方法Pythonic吗?
- 有更好的方法吗?
- 你能提出一个模式来实现这种不变性,同时强制用户只使用我希望他们使用的变量,防止他们将原始字符串传递给构造函数吗?
这不就是类的目的吗。只需 Mother
和 Father
类 继承自:
class Parent():
def __init__(self, type):
self.type = type
class Mother(Parent):
def __init__(self):
super().__init__('mother')
class Father(Parent):
def __init__(self):
super().__init__('father')
parent = Mother()
总的来说这个方法很好,就是有点长。如果您不想实现继承,最短的方法是使 TYPES
列表成为受保护的 class 变量:
class Parent():
def __init__(self, type):
self._TYPES = ['mother', 'father']
assert type in self._TYPES, ('Invalid type "%s"' % type)
self.type = type
我为我的问题找到了一个简洁的答案 - Enum
class (doc).
from enum import Enum
class Parent():
class TYPES(Enum):
MOTHER = 'mother'
FATHER = 'father'
def __init__(self, type:TYPES):
assert type in self.TYPES, ('Invalid type "%s"' % type)
self.type = type
parent = Parent(Parent.TYPES.MOTHER)
在这种情况下,用户仍然可以覆盖 Parent.TYPES
。除了使用 __setattr__
并捕获恶意写入之外,我想不出另一种方法来防止它。如果您能在这里想到什么,请分享您的想法。
这种模式具有以下优势:
- 由于 Enum 的政策,无法覆盖单独的属性 MOTHER 和 FATHER
- 由于
__setattr__
,无法覆盖 TYPES
- 无法使用原始字符串代替 Parent.TYPES.MOTHER(或 .FATHER)
- 兼容
assert type in self.TYPES
或assert isinstance(type, self.TYPES)
到目前为止,我可以看出两个用法上的差异:
- 如果我想获得实际值 - 即。 'mother' 或 'father',那么我需要使用枚举的
.value
属性。因此,我需要使用parent.type
而不是parent.type
(return"TYPES.MOTHER"
),我需要使用parent.type.value
正确 returns'mother'
. - 要打印 TYPES 的内容我需要使用
list(self.TYPES)
我认为使用 Enum 我也可以放弃 ALL_CAPS 标准,因为我不需要表示 types
是静态的。