python - 是否可以创建一个在修改时实现其属性的对象?
问题描述
在这个例子中,应该怎么做才能print(left_hand.number_of_fingers)
返回4
而不是返回5
?
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
self.number_of_fingers = len(fingers)
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
left_hand.fingers.pop()
print(left_hand.number_of_fingers) # I want this to actualize and be 4, not 5
我找到了一个解决方案@property
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
@property
def number_of_fingers(self):
return len(self.fingers)
但是由于计算能力问题我不满意,如果计算number_of_fingers
成本很高,我们只想在fingers
修改时计算它,而不是每次用户要求属性时计算number_of_fingers
。
现在我找到了一个不太优雅的解决方案来解决计算能力的问题:
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
self.old_fingers = fingers
self.number_of_fingers = len(fingers)
def get_number_of_fingers(self):
if self.fingers != self.old_fingers:
self.old_fingers = self.fingers
self.number_of_fingers = len(self.fingers)
return self.number_of_fingers
解决方案
问题是你的Hand
类中的底层列表,即self.fingers
,没有被充分封装,因此任何用户都可以修改它,例如通过调用left_hand.fingers.pop()
甚至分配一个新列表。因此,您不能假设它在调用之间没有被修改number_of_fingers
,因此您别无选择,只能计算它在该调用中的长度。
解决方案是控制您班级的客户可以做什么和不能做什么。最简单的方法是使用name mangling。也就是说,您在属性名称前加上两个前导下划线字符。这使得您的班级的客户很难(尽管并非不可能)从班级外部访问这些属性(我们假设您的用户不是故意恶意的)。因此我们现在必须提供一个pop
方法:
class Hand:
def __init__(self, fingers:list):
self.__fingers = fingers
self.__number_of_fingers = len(fingers)
def pop(self):
assert(self.__fingers)
self.__number_of_fingers -= 1
return self.__fingers.pop()
@property
def number_of_fingers(self):
return self.__number_of_fingers
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
print(left_hand.pop())
print(left_hand.number_of_fingers)
印刷:
pinkie
4
我并不是建议您实际上执行以下操作,但是如果您愿意,可以通过创建特殊的类装饰器来获得更详细的信息@Private
,@Public
这会将您的类包装在一个新类中并检查对您的属性的访问,以确保您没有访问这些属性定义为私有。您可以使用@Private
装饰器来定义那些私有的属性/方法(其他一切都被认为是公共的),或者使用@Public
装饰器来定义那些公共的属性/方法(其他一切都被认为是私有的),但不能同时使用两者。您通常会使用前导单下划线命名您的私有属性,这是告诉用户属性/方法将被视为私有的约定。
这更多地意味着捕获对私有属性的无意访问。如果您使用-O Python 标志执行代码,则不会进行运行时检查。
def accessControl(failIf):
def onDecorator(aClass):
if not __debug__:
return aClass
else:
class onInstance:
def __init__(self, *args, **kargs):
self.__wrapped = aClass(*args, **kargs)
def __getattr__(self, attr):
if failIf(attr):
raise TypeError('private attribute fetch: ' + attr)
else:
return getattr(self.__wrapped, attr)
def __setattr__(self, attr, value):
if attr == '_onInstance__wrapped':
self.__dict__[attr] = value
elif failIf(attr):
raise TypeError('private attribute change: ' + attr)
else:
setattr(self.__wrapped, attr, value)
return onInstance
return onDecorator
def Private(*attributes):
return accessControl(failIf=(lambda attr: attr in attributes))
def Public(*attributes):
return accessControl(failIf=(lambda attr: attr not in attributes))
@Private('_fingers', '_number_of_fingers')
class Hand:
def __init__(self, fingers:list):
self._fingers = fingers
self._number_of_fingers = len(fingers)
def pop(self):
assert(self._fingers)
self._number_of_fingers -= 1
return self._fingers.pop()
@property
def number_of_fingers(self):
return self._number_of_fingers
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
print(left_hand.pop())
print(left_hand.number_of_fingers)
# Thsis will throw an exception:
print(left_hand._fingers)
印刷:
pinkie
4
Traceback (most recent call last):
File "C:\Booboo\test\test.py", line 50, in <module>
print(left_hand._fingers)
File "C:\Booboo\test\test.py", line 9, in __getattr__
raise TypeError('private attribute fetch: ' + attr)
TypeError: private attribute fetch: _fingers
更新
这是 OP 使用缓存的方法:
class Hand:
def __init__(self, fingers:list):
self._cache = {}
self.fingers = fingers
def get_number_of_fingers(self):
fingers = tuple(self.fingers) # can be key of a dictionary
fingers_length = self._cache.get(fingers)
if fingers_length:
print(self.fingers, 'in cache')
return fingers_length
fingers_length = len(fingers)
self._cache[fingers] = fingers_length
return fingers_length
left_hand_fingers = ["thumb", "index", "middle", "ring", "pinkie"]
right_hand_fingers = ["thumb", "middle", "ring", "pinkie"]
hand = Hand(left_hand_fingers)
print(hand.get_number_of_fingers())
hand.fingers = right_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = left_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = right_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = left_hand_fingers
print(hand.get_number_of_fingers())
印刷:
5
4
['thumb', 'index', 'middle', 'ring', 'pinkie'] in cache
5
['thumb', 'middle', 'ring', 'pinkie'] in cache
4
['thumb', 'index', 'middle', 'ring', 'pinkie'] in cache
5
推荐阅读
- prometheus - Prometheus 获取每个桶的最大值
- python - 如何拆分列表中的元素?
- python - 使用 PyQt5 连接到 MS Access 数据库
- git - Docker 不反映从 git 所做的更改
- html - “flex: 1”父母和“min-height”祖父母的孩子没有采用“width: 100%”
- ruby-on-rails - rails 生成的 CSV 文件无法下载
- javascript - 为什么 $ne: null 返回空数组?
- python - 在python中将csv文件列存储为数组
- php - 卷曲给我一个错误,只删除 CURLOPT_USERAGENT 和 CURLOPT_HTTPHEADER 它有效
- c++ - 非 void 函数不返回值