【问题标题】:Overriding parents namespace variable覆盖父命名空间变量
【发布时间】:2020-07-03 22:32:12
【问题描述】:

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

第一个案例

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)

它会打印 。我理解,因为 test 指向字符串“hey”,它是不可变的。更改 B.potato = "not hey" 只会将类命名空间 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 所指的内容,而是改变了价值。不?但我知道它 不会' 真正起作用,因为 test 指向 potato[0] 而不仅仅是 potato .所以,是的,我明白为什么这也会打印 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 已经指向父母 的结果土豆(),所以没关系,仍然打印出“嘿”

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

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。问题是,这可以在没有元类的情况下完成吗?这会儿我的脑子很疼。

【问题讨论】:

  • B.potato = "not hey"class 中一开始没有多大意义。
  • 您的基本误解似乎是这里没有任何东西指向test "hey"。您正在以各种方式将"hey"test 联系起来,仅此而已。然后你永远不会给它分配任何其他东西,所以它保持这种状态。如果您希望 .test 属性根据其他内容动态解析,您可能希望将其设为 @property?!
  • 你大部分时间都在为那里的变量赋值而战,而在类或元类应该做的事情上做的很少。只是不要去那里。

标签: python namespaces metaclass


【解决方案1】:

你可能想要@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 属性。

【讨论】:

  • 完全不清楚为什么存在对self 的限制,或者如何解决它。你必须提供一个比你的玩具代码更真实的例子。
  • 感谢解释+代码。如果我没有被告知不要使用“自我”,我会去买房。这是我遇到的问题,也是我想出的解决方案。我不能提供一个“真实”世界的例子,因为这是一个虚构的问题。
【解决方案2】:

正如您所看到的,您对“令人惊讶”的结果进行了很多实验,通过简单的事情(例如将列表的第一个元素分配给变量)来释放您对该问题的错误概念。

为什么你认为你已经“钉牢”了它并且当你超越了简单并且“发现”你可以用元类“改变一个值”时,它是正确的?

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

元类__new__ 方法中的代码,就像类body 中的代码在每个类创建时运行一次一样。之后变量被创建、分配和更改。

您的“元类实验”通过在创建新类 (C) 时设置 new 类为 potatotest 属性创建一个新值。

没有什么“元”不同于做:

class B:
    potato = 0

class C(B):
   pass

C.potato = 1

print(C.potato)   # Hey it works!

如果您想要“神奇地改变值”,就像电子表格程序中的计算值一样,Python 有一种机制可以做到这一点,并且它与元类无关:这些是“描述符”。其中最广为人知的是使用内置property创建的。

因此,描述符可以完成您在探索中尝试的操作:它们看起来像是读取或写入时的普通属性,但可以通过“幕后”的任意数量的代码触发和计算。它们确实是一种机制,可用于实现“反应式编程”的多个方面。

但由于它们是用来使用而不是被玩弄的,最流行的形式,property 旨在处理类实例 - 而不是将类仅用作命名空间的愚蠢使用。

所以,我建议你改用那个,你会有更多的探索要做:

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)

【讨论】:

  • 正如我所说,@deceze 也说过,出于实际目的,这种事情是通过实例完成的。如果我稍后得到答案,我可能会增加答案。
  • 感谢代码+解释。如果我没有被告知我不能使用“自我”,我肯定会使用财产。这就是为什么我将其设置为问题限制的一部分。谢谢!
  • 别担心增加它,我会删除这个问题,因为不使用 self.我发现尝试这样解决它很有趣。不过谢谢你:)
  • "properties" 是我所说的一种特定类型的描述符。我记得写下“类级别属性”的配方 - 我会检查链接。
  • 有一个:stackoverflow.com/questions/59894757/… 很简单。我建议您在此处阅读有关描述符协议的信息:docs.python.org/3/reference/… - 实际上,如果您阅读了所有该文档(数据模型),它应该可以让您对语言有一些很好的了解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-27
  • 2010-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多