如何使类属性不可变? [英] How can I make class properties immutable?

查看:37
本文介绍了如何使类属性不可变?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

@property 是定义吸气剂的好方法.当属性是可变的时,可以使用返回的引用以不受类定义控制的方式来修改属性.我将用香蕉架作为激励类比,但是这个问题适用于包装容器的任何类.

@property is a nice way to define getters. When the property is mutable, the reference returned can be used to modify the property in ways not controlled by the class definition. I'll use a banana stand as a motivating analogy, but this issue applies to any class that wraps a container.

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 ,他们只是花了一根香蕉而无需付费.

I would like visitors to the banana stand to pay for their bananas. Unfortunately, there's nothing stopping a monkey (who doesn't know any better) from taking one of my bananas. The monkey didn't have to use the internal attribute _banana, they just took a banana without paying.

def take_banana(banana_stand):
    return banana_stand.bananas.pop()

>>> stand = BananaStand()
>>> stand.bananas
['b1', 'b2']
>>> take_banana(stand)
'b2'
>>> stand.bananas
['b1']

这个类比有点愚蠢,但是任何具有可变属性的类都不会受到意外破坏的保护.在我的实际情况中,我有一个带有两个必须保持相同长度的数组属性的类.使用array时,没有什么可以阻止用户将第二个数组拼接到第一个数组中,并且无声地打破了我相等大小的不变式:

This analogy is a little silly, but any class that has mutable attributes is not protected from accidental vandalism. In my actual case, I have a class with two array attributes that must remain the same length. With array, there's nothing stopping a user from splicing a second array into the first and silently breaking my equal size invariant:

>>> 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])

当数组是一个属性时,也会发生同样的情况.

This same behavour occurs when the array is a property.

我可以想到两种避免问题的方法:

I can think of two ways of avoiding issue:

  1. 子类数组并覆盖 __ setitem __ .我对此有所抵触,因为我希望能够在内部使用此数组拼接行为.
  2. 更改访问器以返回阵列的深层副本.返回的数组仍然是可变的,但是对其进行的更改不会影响父对象.
  1. Subclass array and override __setitem__. I am resistant to this because I would like to be able to use this array splicing behaviour internally.
  2. Change the accessor to return a deepcopy of the array. The returned array is still mutable, but changes to it won't affect the parent object.

是否有解决此问题的优雅方法?我对精美的财产继承方式特别感兴趣.

Is there an elegant way around this problem? I'm particularly interested in fancy ways of subclassing property.

推荐答案

您提出的两种方法都是好主意.让我再抛出一个:元组!元组是不可变的.

The two ways you proposed are both good ideas. Let me throw in one more: tuples! Tuples are immutable.

@property
def bananas(self):
    return tuple(self._bananas)

现在您有了这些选择,在选择一项时要牢记一些事情:

Now that you have these alternative, there are a couple of things of things to keep in mind while choosing one over the other:

  • 列表很小,可以使用O(n)访问器吗?选择元组.在大多数情况下,消费者不会看到差异.(除非他当然试图使其变异)
  • 香蕉列表是否需要一些通用的 list 缺乏的特殊功能?子类化列表,并引发有关变异函数的异常.[1]
  • Is the list small and are you okay with a O(n) accessor? Choose tuple. For most part the consumer is not going to see a difference. (Unless of course he tries to mutate it)
  • Does the list bananas need some special abilities that a generic list is falling short on? Subclass a list and raise exceptions on mutating functions. [1]

[1]:jsbueno具有不错的 ReadOnlyList 实现,该实现没有O(n)开销.

[1]: jsbueno has a nice ReadOnlyList implementation that doesn't have the O(n) overhead.

这篇关于如何使类属性不可变?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆