python:不可变的私有类变量?

20 投票
6 回答
25855 浏览
提问于 2025-04-17 02:13

有没有办法把这段Java代码转换成Python呢?

class Foo
{
    final static private List<Thingy> thingies = 
       ImmutableList.of(thing1, thing2, thing3);
}

比如说,thingies 是一个不可变的私有列表,里面存放的是属于Foo类的Thingy对象,而不是某个实例的。

我知道如何在Python中定义静态类变量,可以参考这个问题 Python中的静态类变量,但是我不知道怎么让它们变成不可变和私有的。

6 个回答

9

你可以通过使用属性来让它变得不可写(这和不可变有点不同),但是没有办法让它变成私有的——这和Python的设计理念不符。

class Foo(object):    # don't need 'object' in Python 3
    @property
    def thingies(self):
        return 'thing1', 'thing2', 'thing3'

f = Foo()
print f.thingies
#('thing1', 'thing2', 'thing3')
f.thingies = 9
#Traceback (most recent call last):
#  File "test.py", line 8, in <module>
#    f.thingies = 9
#AttributeError: can't set attribute

它是否不可变取决于你返回的是什么;如果你返回一个可变的对象,你可能会对它进行修改,并且这些修改会在实例或类中显示出来。

class FooMutable(object):
    _thingies = [1, 2, 3]
    @property
    def thingies(self):
        return self._thingies

foo = FooMutable()
foo.thingies.append(4)
print foo.thingies
# [1, 2, 3, 4]

这样你就可以修改thingies,而且因为返回的对象和实例/类中保存的是同一个对象,所以这些修改在后续访问时会反映出来。

对比一下:

class FooMutable(object):
    @property
    def thingies(self):
        return [1, 2, 3]

foo = FooMutable()
foo.thingies.append(4)
print foo.thingies
# [1, 2, 3]

因为每次返回的是一个全新的列表,所以对它的修改在后续访问时不会反映出来。

37

在Python中,通常用一个下划线_作为属性名前缀,表示这个属性是“受保护的”;而用两个下划线__作为前缀,表示这个属性是“私有的”。不过,这个规则并不是语言强制要求的;程序员需要知道,不能写依赖于非公共数据的代码。

如果你真的想要强制属性不可修改,可以使用元类[文档](类的类)。只需要修改__setattr____delattr__,让它们在有人尝试修改属性时抛出异常,同时把属性设置为tuple(不可变的列表)[文档]

class FooMeta(type):
    """A type whose .thingies attribute can't be modified."""

    def __setattr__(cls, name, value):
        if name == "thingies":
            raise AttributeError("Cannot modify .thingies")
        else:
            return type.__setattr__(cls, name, value)

    def __delattr__(cls, name):
        if name == "thingies":
            raise AttributeError("Cannot delete .thingies")
        else:
            return type.__delattr__(cls, name)

thing1, thing2, thing3 = range(3)

class Foo(object):
    __metaclass__ = FooMeta
    thingies = (thing1, thing2, thing3)
    other = [1, 2, 3]

示例

print Foo.thingies # prints "(0, 1, 2)"
Foo.thingies = (1, 2) # raises an AttributeError
del Foo.thingies # raise an AttributeError
Foo.other = Foo.other + [4] # no exception
print Foo.other # prints "[1, 2, 3, 4]"

虽然从技术上讲,仍然可以通过类的内部属性.__dict__来修改这些属性,但这应该足以让大多数用户打消念头,因为完全保护Python对象是非常困难的。

20

在Python中,你不能像在Java里那样做这两件事。

按照约定,名字前面加下划线的变量被认为是私有的,不应该在外部访问。不过,Python并没有强制执行这个约定。这更像是一个警告,告诉你你正在接触一些实现细节,这些细节在未来的版本中可能会没有预警地发生变化。

撰写回答