简体   繁体   English

用子级的 class 属性覆盖父级的属性

[英]Overriding parent's attributes with child's class attributes

I have a scheme of cooperative classes based on collection.abc .我有一个基于collection.abc的合作课程计划。 When I subclass them, I want to be able to define just a couple of class attributes that then become the default values at instantiation, like so:当我对它们进行子类化时,我希望能够只定义几个 class 属性,然后在实例化时成为默认值,如下所示:

class MyFancyClass:
    # Defines various attributes, as class attributes and/or in the
    # __init__ method
    def __init__(self, a=1, b=1):
        self.a = a
        self.b = b

class A(myFancyClass):
    # Instances of A should have these values, even if they override
    # a value set in MyFancyClass's __init__ method:
    a = 2
    b = 2
    c = SomeHelperClass

Currently, in the __init__ of FancyClass , I do:目前,在FancyClass__init__中,我这样做:

def __init__(self, *args, **kwargs):
    for k, v in vars(type(self)).items():
        if k.startswith("_"):
            continue
        if k not in kwargs:
            kwargs[k] = v
    super().__init__(*args, **kwargs)

That works fine, but if I make a class B that is a subclass of A , I lose those values defined for A , and I want to keep them.这很好用,但是如果我制作一个 class B ,它是A的子类,我会丢失为A定义的那些值,我想保留它们。

So playing around, I got stuck here...所以玩了,我就卡在这里了...

class InitExtras:
    def __init__(self, *args, **kwargs):
        for cls in type(self).__mro__:
            if cls == InitExtras:
                break
            for k, v in vars(cls).items():
                if k.startswith("_") or callable(v):
                    continue
                if k not in kwargs:
                    print(f"adding\n{k=}\n{v=}\n")
                    kwargs[k] = v
        super().__init__(*args, **kwargs)

class Base:
    def __init__(self, *args, **kwargs):
        print(f"{args = }")
        print(f"{kwargs = }")

class A(Base):
    def fun1(self):
        pass

class B(A):
    def fun2(self):
        pass

    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, value):
        self._b = value
    
    def __init__(self, *args, b=23, b2=32, **kwargs):
        super().__init__(*args, **kwargs)
        self.b = b
        self.b2 = b2

class C(InitExtras, B):
    b = 42

class D(C):
    b2 = 420

class T:
    pass

class E(C):
    b2 = T

    def fun3(self):
        pass

This seem to do most of what I want, except that E().b2 is 32 , not T .这似乎做了我想做的大部分事情,除了E().b232 ,而不是T And if I remove the callable() filter, other stuff can get mixed in too, like extra functionalities one might define later to personalize classes even further if needed ( fun3 in the example).如果我删除callable()过滤器,其他东西也会混入其中,比如稍后可能定义的额外功能,以便在需要时进一步个性化类(示例中的fun3 )。 I don't want to need to do a new __init__ each time.我不想每次都做一个新的__init__

So my question is, how to accomplish that?所以我的问题是,如何做到这一点?

I could solve it, I did by making a metaclass, and to distinguish between different class attributes I limit it to just properties我可以解决它,我通过创建一个元类来解决它,为了区分不同的 class 属性,我将它限制为属性

abc_recipes.py abc_recipes.py

from abc import ABCMeta, ABC, abstractmethod

class PropertyConfigMeta(ABCMeta):

    def __new__(mcls, name, bases, namespace, /, **kwargs):
        #list the properties that the new class would inherit
        properties = {p for bcls in bases
                        for cls in bcls.__mro__
                        for p,v in vars(cls).items()
                      if isinstance(v,property)
                      }
        #proceed to extract the attributes that would
        #overwrite the properties inherited by non-property
        new_default={}
        new_namespace = {}
        for k,v in namespace.items():
            if k in properties:
                if isinstance(v,property):
                    new_namespace[k] = v
                else:
                    new_default[k] = v
            else:
                new_namespace[k] = v
        cls = super().__new__(mcls, name, bases, new_namespace, **kwargs)
        if hasattr(cls,"_new_default"):
            cls._new_default = {**cls._new_default, **new_default}
        else:
            cls._new_default = new_default
        return cls


class PropertyConfig(metaclass=PropertyConfigMeta):
    """cooperative class that transform

       class A(SomeClass):
           a = 1
           b = 2

       into

       class A(SomeClass):
           def __init__(self, *arg, a = 1, b = 2, **karg):
               super().__init__(*arg, a = a, b = b, **karg)

       so long as a and b are defined as properties in SomeClass 
       (or somewhere in the inheritance chain)

       class SomeClass:

           @property
           def a(self):
               ...

           @property
           def b(self):
               ...

        Use as

        class A(PropertyConfig, SomeClass):
           a = 1
           b = 2
       """

    def __init__(self,*arg,**kwargs):
        for k,v in self._new_default.items():
            if k not in kwargs:
                kwargs[k]=v
        super().__init__(*arg,**kwargs)



class ConfigClass(ABC):
    """Cooperative class that offer a default __repr__ method
       based on the abstract property .config"""

    @property
    @abstractmethod
    def config(self) -> dict:
        """configuration of this class"""
        return {}

    def __repr__(self):
        return f"{type(self).__name__}({', '.join( f'{k}={v!r}' for k,v in self.config.items() )})"

sample use样品使用

import abc_recipes

class Base:
    def __init__(self,*arg,**karg):
        if arg:
            print(f"{arg=}")
        if karg:
            print(f"{karg=}")

class A(Base):
    pass

class B(abc_recipes.ConfigClass,A):

    def __init__(self,*a, b=23, b2=32, **k):
        super().__init__(*a,**k)
        self.b  = b
        self.b2 = b2

    @property
    def b(self):
        "b attribute"
        #print("b getter")
        return self._b

    @b.setter
    def b(self,v):
        #print("b setter")
        self._b=v

    @property
    def b2(self):
        "b2 atrribute"
        #print("b2 getter")
        return self._b2

    @b2.setter
    def b2(self,v):
        #print("b2 setter")
        self._b2=v

    @property
    def config(self) -> dict:
        """configuration of this class"""
        res = super().config
        res.update(b=self.b, b2=self.b2)
        return res

    
class C(abc_recipes.PropertyConfig,B):
    b=42
    pass

class D(C):
    b2=420
    pass

class T:
    pass

class E(C):
    b2 = T
    pi  = 3.14

class F(E):

    @property
    def b2(self):
        #print("rewriten b2 getter")
        return "rewriten b2"

    @b2.setter
    def b2(self, value):
        #print("rewriten b2 setter")
        pass

test测试

>>> F()
F(b=42, b2='rewriten b2')
>>> E()
E(b=42, b2=<class '__main__.T'>)
>>> D()
D(b=42, b2=420)
>>> C()
C(b=42, b2=32)
>>> B()
B(b=23, b2=32)
>>> e=E()
>>> e.pi
3.14
>>> f=F()
>>> f.pi
3.14
>>> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM