在Python

时间:2017-07-06 23:58:12

标签: python class properties

我是Python的新手(并且喜欢它到目前为止),但是在C ++和C#等OO语言方面有多年的经验,并认为自己是一个强大的OO设计师。

我的理解是Python并没有严格执行私有对象属性,但按照惯例,人们希望如果你用一个下划线命名一个属性,他们就知道不会在类之外访问它。好的,公平的。

我的问题:如果一个对象包含"私有"对象和我将它返回给调用者,我应该复制这样他们不能搞砸了吗?或者Python会自动复制吗?

# My Channel class has a dictionary of capabilities  
class Channel(object):
    def __init__(self):
        self._capabilities = dict()

如果我执行以下操作,调用者可以通过弄乱返回的字典来搞乱我的能力吗?

@property
def capabilities(self):
    return self._capabilities

或者我应该这样做并返回一份副本以保护自己?

@property
def capabilities(self):
    # I'm assuming that this creates a new copy of the dictionary
    return dict(self._capabilities)

我猜Python会返回一个引用,以便调用者确实可以搞乱我的私人字典(或列表,或其他),所以我最好先复制一份。

3 个答案:

答案 0 :(得分:0)

如果您使用的是Python 3.3及更高版本,则标准库types.MappingProxyType中有一个类。它的构造函数接受一个字典并返回一个只读视图。如果返回此类对象而不是字典的副本,则返回的MappingProxyType会在客户端代码尝试更改它时引发异常。

您还可以通过继承collections.abc.Mapping并实现三种特殊方法来使您的类模拟不可变映射:__getitem__, __iter__, and __len__。然后客户端代码可以访问_capabilities中的任何项目,但无法修改它。客户甚至可以遍历整个集合。

但Python哲学(“我们都是成年人”)说,也许最好还是返回字典并信任用户的代码而不是弄乱它。试图让Python模拟C ++并不一定是最好的方法。正如您所指出的,Python实际上并没有阻止客户端使用以下划线开头的变量。

答案 1 :(得分:0)

我睡着了,意识到我可以写一个测试并找出我自己的答案。当我返回一个对象(我的测试中的一个字典)然后我得到一个对实际私有对象的引用。如果我在返回的内容中添加一个条目,那么它会在原始对象的字典中添加一个条目。

因此,如果我想要防止这种情况,那么我需要创建一个副本并返回。

    @property
    def capabilities(self):
        # I'm assuming that this creates a new copy of the dictionary
        return dict(self._capabilities)

我认为我最初的问题的一部分是这种方法是否是Python的常见模式。它在C#中,我打算这样做作为一般做法。

答案 2 :(得分:0)

是的,以一个下划线开头的属性被视为私有。您可以访问或修改它们,但不应该。

但是,当您创建公共属性时,您基本上会授予用户权限,以便可以修改它返回的内容。 Python总是返回引用,问题是如果引用是可变的或不可变的,字典和列表是可变的,因此它们可以更改内容,而其他类型如数字和字符串是不可变的,因此它们“可以安全返回”。

不考虑如何返回副本,而应考虑_capabilities的哪些“属性”和“方法”对用户感兴趣。例如,如果你只想要一个“has_capability”和“value_of_capability”,你可以简单地为它创建函数:

class Channel(object):
    def __init__(self):
        self._capabilities = dict()

    def has_capability(self, capability):
        return capability in self._capabilities

    def value_of_capability(self, capability):
        return self._capabilities[capability]

同样适用于应该支持的其他操作。隐藏属性然后“暴露”它(无论是作为引用还是作为副本)都没有意义。副本的问题在于它很慢并且可能会导致意外,因为您可以修改它,但更改不会传播回来。这不是很直观。