在尝试研究元类之前,我试图了解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)
在我看来,这应该有效。我没有更改马铃薯指向的内容,而是更改了值。没有?但我知道它不会实际上是有效的,因为 test 指向的是土豆[0] ,而不仅仅是土豆 。是的,我知道为什么它还会打印嘿。
然后我意识到,如果 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.potato 的 entire 值,但是现在 test 已经指向了父级的结果potato(),所以没关系,仍然会打印出“嘿” 。
那么我想,元类可以解决这个问题吗?显然可以。
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)
这将正确打印 b 的超级嘿和 c 的不嘿。问题是,可以在没有元类的情况下完成此操作吗?这时我的脑痛。
答案 0 :(得分:1)
您可能想要一个@property
:
class B:
potato = 'hey'
@property
def test(self):
return self.potato
class C(B):
potato = 'not hey'
您所做的是以一种或另一种方式将"hey"
分配给test
。除非您为test
实际分配了其他内容,否则它的值不会改变。通过将其设为@property
,每次访问def test
时都会调用函数.test
,因此可以动态计算其值;在这种情况下,基于potato
的值。子类声明了自己的potato
,它遮盖了父级的potato
属性。
答案 1 :(得分:0)
如您所见,您对“令人惊讶的”结果做了很多实验,并做了一些简单的事情,例如将列表的第一个元素分配给变量,以释放您对该问题的错误观念。
为什么您认为自己“钉住”它并在简单操作时正确使用它,而“查找”您可以使用元类“更改值”呢?
否-继续沿您以前的趋势发展,您仍然有错误的假设。
元类__new__
方法中的代码,就像类 body 中的代码在创建每个类时运行一次。在此之后,将创建,分配和更改变量。
您的“元类实验”为potato
和test
属性创建一个新值,方法是在新类(C)为创建。
没有什么“元”与这样做有所不同:
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)