如何在添加到类中的方法中访问双下划线变量"__"

18

背景

我希望使用元类来添加基于原始类的辅助方法。如果我想要添加的方法使用self.__attributeName,由于名称修饰,会出现AttributeError问题,但对于现有的相同方法则不会有问题。

代码示例

这里是一个简化的示例:

# Function to be added as a method of Test
def newfunction2(self):
    """Function identical to newfunction"""
    print self.mouse
    print self._dog
    print self.__cat

class MetaTest(type):
    """Metaclass to process the original class and
    add new methods based on the original class
    """
    def __new__(meta, name, base, dct):
        newclass = super(MetaTest, meta).__new__(
                meta, name, base, dct
                )

        # Condition for adding newfunction2
        if "newfunction" in dct:
            print "Found newfunction!"
            print "Add newfunction2!"
            setattr(newclass, "newfunction2", newfunction2)

        return newclass

# Class to be modified by MetaTest
class Test(object):
    __metaclass__ = MetaTest

    def __init__(self):
        self.__cat = "cat"
        self._dog = "dog"
        self.mouse = "mouse"

    def newfunction(self):
        """Function identical to newfunction2"""
        print self.mouse
        print self._dog
        print self.__cat

T = Test()
T.newfunction()
T.newfunction2() # AttributeError: 'Test' object has no attribute '__cat'

问题

有没有一种方法可以添加newfunction2,并且可以使用self.__cat

(不需要将self.__cat重命名为self._cat。)

或许更基本的问题是,为什么self.__cat在两种情况下没有被同样地处理,因为newfunction2现在是Test的一部分了?


4个回答

23

当类中的方法被编译时,名称修饰会发生。像 __foo 这样的属性名称会变成 _ClassName__foo,其中 ClassName 是定义该方法的类的名称。请注意,您可以为其他对象的属性使用名称修饰!

在您的代码中,newfunction2 中的名称修饰无法工作,因为当函数进行编译时,它不是类的一部分。因此,对 __cat 的查找没有像在 Test.__init__ 中那样转换为 __Test_cat。如果您想要,可以显式查找属性名称的名称修饰版本,但听起来您希望 newfunction2 是通用的,并且能够添加到多个类中。不幸的是,这在名称修饰中不起作用。

确实,防止未在您的类中定义的代码访问您的属性是使用名称修饰的整个原因。通常情况下,只有在编写代理或混合类型时才值得费心,而且您不希望内部使用的属性与您代理或混合的类的属性发生冲突(您事先不知道)。


在 Django 的文档教程 https://docs.djangoproject.com/en/2.0/intro/tutorial02/ 中,有这样一行代码 'Question.objects.get(pub_date__year=current_year)',其中 pub date year 变量有一个双下划线,这和你提到的是一样的吗?@Blckknght - LordDraagon
1
@LordDraagon:不,那似乎是Django自己的东西。它的拼写可能部分受到Python本地名称混淆的启发,但它是单独实现的,并且具有不同的含义。我认为双下划线在属性表示法中有点像点号(因此pub_date__year有点像pub_date.year)。我对Django了解得不够多,无法给出更完整的解释。 - Blckknght

4
回答你的两个问题:
  1. 当你需要从newfunction2调用它时,你需要将self.__cat更改为self._Test__cat,因为这是名称操作规则。

  1. Python文档:

这种名称操作是在不考虑标识符的语法位置的情况下进行的,只要它出现在类的定义中即可。

简单来说,它并不关心解释器在遇到名称操作时所处的位置。 只有当名称出现在类定义中时,才会进行名称操作,而在您的代码中,它并没有直接位于类定义下面。因此,当它读取self.__cat时,它保持为self.__cat,并不会将其在文本上替换为self._Test__cat,因为它未在Test类内定义。


3
你可以使用<Test实例>._Test__cat访问Test类中的__cat属性。(其中<Test实例>self或任何其他Test类的实例替换)。
请在Python文档中了解更多。

我考虑过这一点,但由于我正在添加newmethod2作为一个方法,所以我认为行为应该是一致的。也就是说,我可以访问self.__cat - Haydon

1
class B:
    def __init__(self):
        self.__private = 0 
    def __private_method(self):
        '''A private method via inheritance'''
        return ('{!r}'.format(self))
    def internal_method(self):
        return ('{!s}'.format(self))

class C(B):
    def __init__(self):
        super().__init__()
        self.__private = 1
    def __private_method(self):
        return 'Am from class C'

c = C()
print(c.__dict__)
b = B()
print(b.__dict__)
print(b._B__private)
print(c._C__private_method())

请问您能否为什么这个解决方案可以解决原帖作者的问题添加一些解释? - Allen M

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