Python类可以拥有成员变量,这些成员变量可以被访问,但不能从类的实例中访问吗?

4

我并不是计算机科学背景,对于这个问题我在谷歌和Stack Overflow上搜索时遇到了困难。如果我有一个Python类,其中包含一个类变量objects,如下所示:

class MyClass(object):
    objects = None
    pass

MyClass.objects = 'test'
print MyClass.objects    # outputs 'test'

a = MyClass()
print a.objects          # also outputs 'test'

类和类的实例都将可以访问objects变量。我知道我可以这样改变实例值:

a.objects = 'bar'
print a.objects          # outputs 'bar'
print MyClass.objects    # outputs 'test'

在Python中,是否有可能拥有一个类变量,使得该变量可由类的用户(即不仅限于类内部)访问,但不能被该类的实例所访问?我认为其他语言中称之为私有成员或静态成员?


也许我应该澄清一下,我希望MyClass的用户使用objects,但我不希望MyClass的实例使用objects。Jonrsharpe提供的链接详细介绍了如何使某些内容私有化,这并不是我想通过这个问题得到的答案。我更想知道是否可能存在一个类变量仅存在于类中,而不在实例中。 - Randy
为什么需要在类内添加这样的变量?将其添加到模块的根目录不是更简单吗?例如,在类定义之外添加MY_VARIABLE='test' - furins
将其添加到模块根目录可能更简单,但我现在的情况是这样做需要对调用代码进行大量其它更改,因此我希望仍将其保留在类中。 - Randy
这个问题不是重复的!OP在错误地使用术语“private”。这个问题是关于类变量,不能通过实例访问,这与其他问题无关! - Bakuriu
+1 Bakuriu,感谢您删除了重复内容。 - Randy
4个回答

3

Python旨在允许类的实例通过实例访问该类的属性。

这只能深入一层,因此您可以使用元类:

class T(type):
    x = 5

class A(object):
    __metaclass__ = T

请注意,在Python 3中元类语法有所不同。以下写法可行:
>>> A.x
5
>>> A().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'x'

但这并不能阻止您在类的实例上设置属性;要防止这种情况,您需要使用__setattr__魔法方法:

class A(object):
    x = 1
    def __getattribute__(self, name):
        if name == 'x':
            raise AttributeError
        return super(A, self).__getattribute__(name)
    def __setattr__(self, name, value):
        if name == 'x':
            raise AttributeError
        return super(A, self).__setattr__(name, value)
    def __delattr__(self, name):
        if name == 'x':
            raise AttributeError
        return super(A, self).__delattr__(name)

我最近成为元类的粉丝,我的代码已经在以类似的方式使用它们来在将对象移交给用户之前对其进行一些引导。在元类中完成这个任务非常顺畅。 - Randy

2
最简单的实现方法是使用一个描述符。描述符是用于提供更高级别的属性访问控制的工具。例如:
class ClassOnly(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value
    def __get__(self, inst, cls):
        if inst is not None:
            msg = 'Cannot access class attribute {} from an instance'.format(self.name)
            raise AttributeError(msg)
        return self.value

class A(object):
    objects = ClassOnly('objects', [])

用途:

In [11]: a = A()

In [12]: a.objects
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-12-24afc67fd0ba> in <module>()
----> 1 a.objects

<ipython-input-9-db6510cd313b> in __get__(self, inst, cls)
      5     def __get__(self, inst, cls):
      6         if inst is not None:
----> 7             raise AttributeError('Cannot access class attribute {} from an instance'.format(self.name))
      8         return self.value

AttributeError: Cannot access class attribute objects from an instance

In [13]: A.objects
Out[13]: []

1
如果您希望对象有一个“单一的真相源头”,您可以将其设为可变类型:
class MyClass(object):
    objects = []

使用不可变类型时,每个实例最初都具有来自 MyClass 的相同引用是无关紧要的,因为对于该实例第一次更改属性时,它将与类的值“断开连接”。
但是,如果属性是可变的,则在实例中更改它会为类和该类的所有其他实例更改它:
>>> MyClass.objects.append(1)
>>> MyClass.objects
[1]
>>> a = MyClass()
>>> a.objects
[1]
>>> a.objects.append(2)
>>> a.objects
[1, 2]
>>> MyClass.objects
[1, 2] 

在Python中,没有真正的“私有”,所以你不能真正地防止实例访问或更改对象(在这种情况下,它是一个合适的类属性吗?),但是如果你不希望直接访问它们,通常习惯在名称前加下划线:_objects
一种实际保护实例访问对象的方法是覆盖__getattribute__
def __getattribute__(self, name):
    if name == "objects":
        raise AttributeError("Do not access 'objects' though MyClass instances.")
    return super(MyClass, self).__getattribute__(name)

>>> MyClass.objects
[1]
>>> a.objects
...
AttributeError: Do not access 'objects' though MyClass instances.

我希望其他人可以访问 MyClass.objects,但不是 a.objects,所以我想 __init__ 可以将 self.objects = None 设置为默认值? - Randy
1
请注意,该类的实例仍然可以重新定义__getattribute__函数,从而移除保护。除此之外,这是一种优雅的方法。 - furins

0

不可以,你不能这样做(编辑:你不能以完全无法访问的方式,比如在Java或C++中)。

如果你愿意,你可以这样做:

class MyClass(object):
    objects = None
    pass

MyClass_objects = 'test'
print MyClass_objects    # outputs 'test'

a = MyClass()
print a.objects          # outputs 'None'

或者这样:

your_module.py中:

objects = 'test'
class MyClass(object):
    objects = None
    pass

yourapp.py文件中:

import your_module
print your_module.objects    # outputs 'test'

a = your_module.MyClass()
print a.objects          # outputs 'None'

原因是:

当你创建一个类的实例时,没有任何阻止你深入其中并使用各种内部私有方法的东西,这些方法(a)对于类的功能是必要的,但(b)不是直接使用/访问的。

在Python中,没有真正的私有性。没有类或类实例可以将你远离所有内部内容(这使得内省成为可能和强大)。Python信任你。它说:“嘿,如果你想在黑暗的地方四处闲逛,我会相信你有充分的理由,而且你不会制造麻烦。” Karl Fast


感谢@furins,这正是我怀疑的,但我只是想确保这种行为,特别是当我想要访问MyClass.objects时。其他SO答案中的名称混淆等并没有达到我所拥有的这种微妙方法。 - Randy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接