【问题标题】:How can I call a class variable instead of the child variable with the same name?如何调用类变量而不是同名的子变量?
【发布时间】:2022-01-12 17:57:40
【问题描述】:

与往常一样,我首先要说我在任何意义上都不是专家或专业人士,所以请温柔地评判我。不幸的是,我找不到这个问题的解决方案,但我敢肯定这不是第一次出现在这里。因此,如果您将我推荐给相关线程,我将不胜感激。

我有一个 12 层的类和子类结构。比如:

class A:
    pass
class B(A):
    pass
#class M,N,O(A)....
class C(B):
    pass

等等。

每个类和子类可能有也可能没有(通常它们确实有)一个属性(memory['prop'])。并且任何对象的每个 init 都引用它的超类。

class A:
    memory={'prop':{'a':0,'b':1}}
    def __init__(self,**kwargs):
       for k in kwargs|self.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(kwargs|self.memory['prop'])[k])
class B(A):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        for k in self.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(self.memory['prop'])[k])
class C(B):
    memory={'prop':{'c':2,'d':3}}
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        for k in self.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(self.memory['prop'])[k])

这里的问题是,如果我创建一个 C 对象,它会在每次算法调用 init 函数中的变量时调用 C.memory['prop']。我需要的是调用“当前”类内存。

在示例中,如果我运行object=C(**{'e'=4}),我将获得一个具有'c''d''e' 属性的对象,但不是'a''b'

我知道我可以通过以下方式做到这一点:

class A:
    memory={'prop':{'a':0,'b':1}}
    def __init__(self,**kwargs):
       for k in kwargs|A.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(kwargs|A.memory['prop'])[k])
class B(A):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        for k in B.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(B.memory['prop'])[k])
class C(B):
    memory={'prop':{'c':2,'d':3}}
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        for k in C.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(C.memory['prop'])[k])

但这意味着我每次定义一个类时都必须做额外的工作(大约有一千个)

我也可以这样做:

class A:
    memory={'prop':{'a':0,'b':1}}
    def __init__(self,clas,**kwargs):
       for k in kwargs|clas.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(kwargs|clas.memory['prop'])[k])
class B(A):
    def __init__(self,clas,**kwargs):
        super().__init__(clas,**kwargs)
        for k in clas.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(clas.memory['prop'])[k])
class C(B):
    memory={'prop':{'c':2,'d':3}}
    def __init__(self,clas,**kwargs):
        super().__init__(clas,**kwargs)
        for k in clas.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(clas.memory['prop'])[k])

但这意味着我每次创建一个看起来错误的对象object=C(C)时都必须调用同一个类。

最好的方法是什么?

【问题讨论】:

  • 变量可以引用为A.memoryB.memory
  • 如果你不希望子类覆盖它,那么使用相同的变量名有什么意义呢?
  • “我有一个 12 层的类和子类结构”听起来像是一个错误的设计……你真正的目标是什么?
  • @AKX 为什么?我正在建模类似于分类学的东西,如果我对生物进行分类,构建一个 8 层的结构会不会是错误的?
  • @AKX。同意。这里有很多代码气味。循环执行(kwargs|clas.memory['prop'])[k] 真的很困扰我。

标签: python class inheritance subclass init


【解决方案1】:

您希望prop 成为类属性,但需要为每个子类创建一个新的实例。

一个简单的方法是在__init_subclass__ 方法中创建一个prop:这是语言每次创建子类时都会调用的保留方法:

from copy import deepcopy

class A:
    memory={'prop':{'a':0,'b':1}}
    def __init__(self,**kwargs):
       cls = type(self) # it is better to be specific that we want to update a class property
       for k in kwargs|self.memory['prop']:
            if k not in self.__dict__:
                setattr(self,k,(kwargs|cls.memory['prop'])[k])

    def __init_subclass__(cls, *args, **kw):
        super().__init_subclass__(cls, *args, **kw)
        # this will create a copy of A.memory for each subclass.
        # (__class__ is a special value that always points to THIS (A)
        # class, while "cls", received as a parameter, refers to the
        # subclass that is being created: 
        cls.memory = deepcopy(__class__.memory)
        # if you prefer each subclass to start with an empty memory:
        # cls.memory = {}

class B(A):
    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        # no need for any other code in the subclasses __init__ - 
        # the code on the superclass will store it on the apropriate 
        # dictionary for each class

【讨论】:

  • 你在做一个浅拷贝...
  • 确实如此。由于 OP 对“内存”的嵌套字典感兴趣,因此它没有用。现已修复
  • 我喜欢你更短的解决方案。
  • 此代码为我的问题提供了解决方案,因此我接受了它作为正确答案。代码不起作用,但是 init_subclassclass 的使用都清楚
  • 代码应该可以工作。在初稿中,我使用的是“copy”而不是 deepcopy:它不适用于您的特定字典,因为感兴趣的键嵌套得很深。
【解决方案2】:

您似乎对滥用新的字典联合运算符感到非常兴奋。不仅每次您想要访问密钥时都在循环中重复运行它,而且它会掩盖您的实际设计,这可能是有缺陷的:

  1. 申请kwargs
  2. 用类级别默认值覆盖它们

请记住,联合运算符对于字典是不可交换的:在发生冲突时始终使用最后一个右手运算符的值。这与通常的做法相反(当时kwargs 的意义何在?),但让我们暂时忽略它。

您似乎希望继承链中的类能够更新__init__ 中的__dict__。这从根本上与隐藏父属性的继承相反。您可以将字典更新吸收到方法调用中,该方法可以使用super 来确保处理整个链:

class A:
    def __init__(self, **kwargs):
        self.__dict__.update(self.get_defaults())
        self.__dict__.update(kwargs)

     @classmethod
     def set_defaults(cls):
         return {'A': 1, 'B': 2}

class B(A):
    pass

class C(B):
    @classmethod
    def get_defaults(cls):
        return super().get_defaults() | {'b': 3, 'c': 4}

这样,您只需要一个构造函数。它还具有允许您动态生成默认值的潜在优势。

【讨论】:

    猜你喜欢
    • 2016-07-02
    • 2019-10-26
    • 1970-01-01
    • 2022-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多