覆盖父命名空间变量

Overriding parents namespace variable

在深入研究元classes 之前,我试图理解Python classes。我遇到了一些我无法理解的代码。在这种情况下,classes 没有使用 self 而是 class 命名空间(没有使用 self 的选项,因此这里有问题)。我如何在一个 class 中定义一些命名空间变量,然后在子 class 中覆盖它们都依赖的值?

第一个案例

class B():
    potato = "hey"
    test = potato
    #here would go a lot of more code that just depends on that potato value

class c(B):
    B.potato = "not hey"

c_potato = c().test
print(c_potato)

它打印 hey。我理解是因为 test 指向字符串 "hey",它是不可变的。更改 B.potato = "not hey" 只会将 class 命名空间 potato 更改为新字符串,但不会更改 test 指向的内容至。所以我想,嘿,如果我用一个列表来做,那是通过引用吗?

class B():
    potato = ["hey"]
    test = potato[0]

class c(B):
    B.potato[0] = "not hey"

c_potato = c().test
print(c_potato)

在我看来,这应该有效。我没有更改 potato 指向的内容,而是更改了值。不?但我知道它 wont' 实际上有效,因为 test 指向 potato[0] 而不是只是 土豆。所以是的,我明白为什么这也会打印 hey.

然后我意识到,如果 test 需要指向结果,它是不可变的,那么我试图用命名空间做的事情是不可能的。

class B():

    @staticmethod
    def get_potato():
      return "hey"

    potato = get_potato.__func__
    test = potato()

class c(B):

    @staticmethod
    def get_potato():
      return "not hey"

    B.potato = "6"

c_potato = c().test
print(c_potato)

我在这里更改了 B.potatoentire 值,但现在 test 已经指向父级 potato() 的结果,所以没关系,仍然打印出 "hey"

然后我想,metaclasses 可以解决这个问题吗?显然是的,它可以。

class Meta(type):
    def __new__(cls, name, bases, attrs):
        x = super().__new__(cls, name, bases, attrs)
        x.potato = "basic hey"
        if 'meta_args' in attrs:
            x.potato = attrs['meta_args'][0]
            del attrs['meta_args'] # clean up
        x.test = x.potato    
        return x

class A():
  pass

class B(A, metaclass=Meta):
  meta_args = ["super hey"]
  pass

class C(B):
    meta_args = ["not hey"]

b = B().test
c = C().test
print(b)
print(c)

并且正确打印 super hey fo bnot hey for c。问题是,这可以在没有 metaclasses 的情况下完成吗?脑袋都疼了

你可能想要 @property:

class B:
    potato = 'hey'

    @property
    def test(self):
        return self.potato


class C(B):
    potato = 'not hey'

您所做的是以某种方式将 "hey" 分配给 test。它的值不会改变,除非你真的给 test 分配了别的东西。通过将其设为 @property,每次访问 .test 时都会调用函数 def test,因此可以动态计算其值;在本例中,基于 potato 的值。子类声明了它自己的 potato,它隐藏了父类的 potato 属性.

正如您所看到的那样,您对 "surprising" 结果进行了大量实验,使用简单的东西,例如将列表的第一个元素分配给变量,以释放您对问题的错误概念。

为什么你认为你有 "nailed" 它并且当你超越简单时就做对了,并且 "find out" 你可以 "change a value" 使用元 class?

不 - 只是继续你之前的趋势,你仍然有错误的假设。

metaclass__new__方法中的代码,就像classbody中的代码是运行创建每个 class 时一次。变量被创建、分配,之后就不再更改。

您的 "metaclass experiment" 通过在 new class 上设置然后为 potatotest 属性创建一个新值在创建这个新的 class (C) 时。

"meta" 与以下操作没有什么不同:

class B:
    potato = 0

class C(B):
   pass

C.potato = 1

print(C.potato)   # Hey it works!

如果您想要 "magic changing values",表现得像计算值在电子表格程序中所做的那样,Python 有一种机制可以做到这一点,并且它与 metaclasses 无关:这些是 "descriptors"。其中最著名的是使用内置 property.

创建的

因此,描述符会按照您在探索中尝试的方式进行操作:它们在读取或写入时看起来像一个普通属性,但可以通过任意数量的代码触发和计算 "behind the scenes"。它们确实是一种可用于实现 "reactive programing".

多个方面的机制

但是因为它们是用来使用的,而不是玩弄的,最流行的形式,property 被设计用于 class 个实例——而不是愚蠢地使用 class es 只是命名空间。

所以,我建议你切换到那个,你将有更多的探索要做:

class B:
    def __init__(self):
        self.potato = Hey

    @property
    def test(self):
        return self.potato

c = B()
print(c.test)
c.potato = "not hey"
print(c.test)