@property
是定义getter的好方法。当属性是可变的时,返回的引用可用于以不受类定义控制的方式修改属性。我会使用香蕉架作为激励类比,但这个问题适用于任何包装容器的类。
class BananaStand:
def __init__(self):
self._money = 0
self._bananas = ['b1', 'b2']
@property
def bananas(self):
return self._bananas
def buy_bananas(self, money):
change = money
basket = []
while change >= 1 and self._bananas:
change -= 1
basket.append(self._bananas.pop())
self._money += 1
return change, basket
我希望香蕉站的游客能够支付他们的香蕉费用。不幸的是,没有什么可以阻止一只猴子(谁也不知道更好)从我的香蕉中取出一只。猴子没有必要使用内部属性_banana
,他们只是拿了一根香蕉而没有付钱。
def take_banana(banana_stand):
return banana_stand.bananas.pop()
>>> stand = BananaStand()
>>> stand.bananas
['b1', 'b2']
>>> take_banana(stand)
'b2'
>>> stand.bananas
['b1']
这个类比有点傻,但任何具有可变属性的类都不会受到意外故意破坏的保护。在我的实际情况中,我有一个具有两个数组属性的类,它们必须保持相同的长度。对于数组,没有什么可以阻止用户将第二个数组拼接到第一个数组中并且默默地打破我的相同大小的不变量:
>>> from array import array
>>> x = array('f', [1,2,3])
>>> x
array('f', [1.0, 2.0, 3.0])
>>> x[1:2] = array('f', [4,5,6])
>>> x
array('f', [1.0, 4.0, 5.0, 6.0, 3.0])
当数组是属性时,会发生同样的行为。
我可以想到两种避免问题的方法:
__setitem__
。我对此有抵触,因为我希望能够在内部使用这种数组拼接行为。这个问题有优雅的方法吗?我特别感兴趣的是对子类化属性的奇特方式。
答案 0 :(得分:2)
你提出的两种方式都是好主意。让我再说一遍:元组!元组是不可变的。
@property
def bananas(self):
return tuple(self._bananas)
既然您有这些替代方案,那么在选择其中一项时要记住几件事情:
list
不足的特殊能力?对列表进行子类化并在变异函数上引发异常。 [1] [1]:jsbueno有nice ReadOnlyList
implementation没有O(n)开销。
答案 1 :(得分:1)
我花了很长时间,但我认为我已根据此answer中提供的配方创建了一个非常强大且灵活的解决方案。非常自豪,我提出了FixLen
包装器:
from array import array
from collections import MutableSequence
from inspect import getmembers
class Wrapper(type):
__wraps__ = None
__ignore__ = {
'__class__', '__mro__', '__new__', '__init__', '__dir__',
'__setattr__', '__getattr__', '__getattribute__',}
__hide__ = None
def __init__(cls, name, bases, dict_):
super().__init__(name, bases, dict_)
def __init__(self, obj):
if isinstance(obj, cls.__wraps__):
self._obj = obj
return
raise TypeError(
'wrapped obj must be of type {}'.format(cls.__wraps__))
setattr(cls, '__init__', __init__)
@property
def obj(self):
return self._obj
setattr(cls, 'obj', obj)
def __dir__(self):
return list(set(dir(self.obj)) - set(cls.__hide__))
setattr(cls, '__dir__', __dir__)
def __getattr__(self, name):
if name in cls.__hide__:
return
return getattr(self.obj, name)
setattr(cls, '__getattr__', __getattr__)
for name, _ in getmembers(cls.__wraps__, callable):
if name not in cls.__ignore__ \
and name not in cls.__hide__ \
and name.startswith('__') \
and name not in dict_:
cls.__add_method__(name)
def __add_method__(cls, name):
method_str = \
'def {method}(self, *args, **kwargs):\n' \
' return self.obj.{method}(*args, **kwargs)\n' \
'setattr(cls, "{method}", {method})'.format(method=name)
exec(method_str)
class FixLen(metaclass=Wrapper):
__wraps__ = MutableSequence
__hide__ = {
'__delitem__', '__iadd__', 'append', 'clear', 'extend', 'insert',
'pop', 'remove',
}
# def _slice_size(self, slice):
# start, stop, stride = key.indices(len(self.obj))
# return (stop - start)//stride
def __setitem__(self, key, value):
if isinstance(key, int):
return self.obj.__setitem__(key, value)
#if self._slice_size(key) != len(value):
if (lambda a, b, c: (b - a)//c)(*key.indices(len(self.obj))) \
!= len(value):
raise ValueError('input sequences must have same length')
return self.obj.__setitem__(key, value)
FixLen
保留对传递给其构造函数的可变序列的内部引用,并阻止对其的访问,或提供更改对象长度的方法的备用定义。这允许我在内部改变长度,但是当作为属性传递时保护序列的长度不被修改。这不完美(我认为FixLen
应该是子类Sequence
。
使用示例:
>>> import fixlen
>>> x = [1,2,3,4,5]
>>> y = fixlen.FixLen(x)
>>> y
[1, 2, 3, 4, 5]
>>> y[1]
2
>>> y[1] = 100
>>> y
[1, 100, 3, 4, 5]
>>> x
[1, 100, 3, 4, 5]
>>> y.pop()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable