[英]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().b2
是32
,而不是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.