扩展冻结数据 class 并从基础 class 实例中获取所有数据
Extending frozen dataclass and take all data from base class instance
假设我们有一个 class 来自图书馆,
@dataclass(frozen=True)
class Dog:
name: str
blabla : int
# lot of parameters
# ...
whatever: InitVar[Sequence[str]]
我有一个来自外部库的狗构造器。
pluto = dog_factory() # returns a Dog object
我希望这只狗有一个新成员,比方说'bite
'。
显然 pluto['bite'] = True
会失败,因为数据 class 被冻结。
所以我的想法是从 Dog 中创建一个 subclass 并从 'pluto' 实例中获取所有数据。
class AngryDog(Dog):
# what will come here ?
有没有办法避免手动将所有 class Dog 参数放入 init ?
类似于复制构造函数。
理想情况下:
class AngryDog(Dog):
def __init__(self, dog, bite = True):
copy_construct(dog)
如果你想使用继承来解决你的问题,你需要从编写一个合适的 AngryDog
subclass 开始,你可以用它来构建合理的实例。
下一步将是添加一个 from_dog
classmethod,可能是这样的:
from dataclasses import dataclass, asdict
@dataclass(frozen=True)
class AngryDog(Dog):
bite: bool = True
@classmethod
def from_dog(cls, dog: Dog, **kwargs):
return cls(**asdict(dog), **kwargs)
但是按照这种模式,您将面临一个特定的边缘情况,您自己已经通过 whatever
参数指出了这一点。重新调用 Dog
构造函数时,任何 InitVar
都将在 asdict
调用中丢失,因为它们不是 class 的正确成员。事实上,dataclass' __post_init__
中发生的任何事情,也就是 InitVars
所在的位置,都可能导致错误或意外行为。
如果只是一些小事,例如从 cls
调用中过滤或删除已知参数,并且父 class 预计不会更改,您可以尝试在 [=16= 中处理它].但是在概念上没有办法为这种 from_instance
问题提供通用的解决方案。
Composition 从数据完整性的角度来看可以无错误地工作,但考虑到手头的具体问题,它可能是单一的或笨拙的。这样的 dog-extension 不能代替适当的 dog-instance 使用,但我们可以将其转换为正确的形状以备不时之需:
class AngryDogExtension:
def __init__(self, dog, bite=True):
self.dog = dog
self.bite = bite
def __getattr__(self, item):
"""Will make instances of this class bark like a dog."""
return getattr(self.dog, item)
用法:
# starting with a basic dog instance
>>> dog = Dog(name='pluto', blabla=1, whatever=['a', 'b'])
>>> dog_e = AngryDogExtension(d)
>>> dog_e.bite # no surprise here, just a regular member
True
>>> dog_e.name # this class proxies its dog member, so no need to run `dog_e.dog.name`
pluto
但最终,重点仍然是 isinstance(dog_e, Dog)
将 return False
。如果您决心进行该调用 return True
,可以使用一些高级技巧来帮助您,并让任何继承您代码的人讨厌您:
class AngryDogDoppelganger(Dog):
def __init__(self, bite, **kwargs):
if "__dog" in kwargs:
object.__setattr__(self, "__dog", kwargs["__dog"])
else:
object.__setattr__(self, "__dog", Dog(**kwargs))
object.__setattr__(self, "bite", bite)
@classmethod
def from_dog(cls, dog, bite=True):
return cls(bite, __dog=dog)
def __getattribute__(self, name):
"""Will make instances of this class bark like a dog.
Can't use __getattr__, since it will see its own instance
attributes. To have __dog work as a proxy, it needs to be
checked before basic attribute lookup.
"""
try:
return getattr(object.__getattribute__(self, "__dog"), name)
except AttributeError:
pass
return object.__getattribute__(self, name)
用法:
# starting with a basic dog instance
>>> dog = Dog(name='pluto', blabla=1, whatever=['a', 'b'])
# the doppelganger offers a from_instance method, as well as
# a constructor that works as expected of a subclass
>>> angry_1 = AngryDogDoppelganger.from_dog(dog)
>>> angry_2 = AngryDogDoppelganger(name='pluto', blabla=1, whatever=['a', 'b'], bite=True)
# instances also bark like at dog, and now even think they're a dog
>>> angry_1.bite # from subclass
True
>>> angry_1.name # looks like inherited from parent class, is actually proxied from __dog
pluto
>>> isinstance(angry_1, Dog) #
True
大多数添加数据class的方法,如__repr__
,都会被破坏,包括在诸如dataclass.asdict
甚至vars
之类的东西中插入分身实例- 所以使用风险自负。
假设我们有一个 class 来自图书馆,
@dataclass(frozen=True)
class Dog:
name: str
blabla : int
# lot of parameters
# ...
whatever: InitVar[Sequence[str]]
我有一个来自外部库的狗构造器。
pluto = dog_factory() # returns a Dog object
我希望这只狗有一个新成员,比方说'bite
'。
显然 pluto['bite'] = True
会失败,因为数据 class 被冻结。
所以我的想法是从 Dog 中创建一个 subclass 并从 'pluto' 实例中获取所有数据。
class AngryDog(Dog):
# what will come here ?
有没有办法避免手动将所有 class Dog 参数放入 init ? 类似于复制构造函数。
理想情况下:
class AngryDog(Dog):
def __init__(self, dog, bite = True):
copy_construct(dog)
如果你想使用继承来解决你的问题,你需要从编写一个合适的 AngryDog
subclass 开始,你可以用它来构建合理的实例。
下一步将是添加一个 from_dog
classmethod,可能是这样的:
from dataclasses import dataclass, asdict
@dataclass(frozen=True)
class AngryDog(Dog):
bite: bool = True
@classmethod
def from_dog(cls, dog: Dog, **kwargs):
return cls(**asdict(dog), **kwargs)
但是按照这种模式,您将面临一个特定的边缘情况,您自己已经通过 whatever
参数指出了这一点。重新调用 Dog
构造函数时,任何 InitVar
都将在 asdict
调用中丢失,因为它们不是 class 的正确成员。事实上,dataclass' __post_init__
中发生的任何事情,也就是 InitVars
所在的位置,都可能导致错误或意外行为。
如果只是一些小事,例如从 cls
调用中过滤或删除已知参数,并且父 class 预计不会更改,您可以尝试在 [=16= 中处理它].但是在概念上没有办法为这种 from_instance
问题提供通用的解决方案。
Composition 从数据完整性的角度来看可以无错误地工作,但考虑到手头的具体问题,它可能是单一的或笨拙的。这样的 dog-extension 不能代替适当的 dog-instance 使用,但我们可以将其转换为正确的形状以备不时之需:
class AngryDogExtension:
def __init__(self, dog, bite=True):
self.dog = dog
self.bite = bite
def __getattr__(self, item):
"""Will make instances of this class bark like a dog."""
return getattr(self.dog, item)
用法:
# starting with a basic dog instance
>>> dog = Dog(name='pluto', blabla=1, whatever=['a', 'b'])
>>> dog_e = AngryDogExtension(d)
>>> dog_e.bite # no surprise here, just a regular member
True
>>> dog_e.name # this class proxies its dog member, so no need to run `dog_e.dog.name`
pluto
但最终,重点仍然是 isinstance(dog_e, Dog)
将 return False
。如果您决心进行该调用 return True
,可以使用一些高级技巧来帮助您,并让任何继承您代码的人讨厌您:
class AngryDogDoppelganger(Dog):
def __init__(self, bite, **kwargs):
if "__dog" in kwargs:
object.__setattr__(self, "__dog", kwargs["__dog"])
else:
object.__setattr__(self, "__dog", Dog(**kwargs))
object.__setattr__(self, "bite", bite)
@classmethod
def from_dog(cls, dog, bite=True):
return cls(bite, __dog=dog)
def __getattribute__(self, name):
"""Will make instances of this class bark like a dog.
Can't use __getattr__, since it will see its own instance
attributes. To have __dog work as a proxy, it needs to be
checked before basic attribute lookup.
"""
try:
return getattr(object.__getattribute__(self, "__dog"), name)
except AttributeError:
pass
return object.__getattribute__(self, name)
用法:
# starting with a basic dog instance
>>> dog = Dog(name='pluto', blabla=1, whatever=['a', 'b'])
# the doppelganger offers a from_instance method, as well as
# a constructor that works as expected of a subclass
>>> angry_1 = AngryDogDoppelganger.from_dog(dog)
>>> angry_2 = AngryDogDoppelganger(name='pluto', blabla=1, whatever=['a', 'b'], bite=True)
# instances also bark like at dog, and now even think they're a dog
>>> angry_1.bite # from subclass
True
>>> angry_1.name # looks like inherited from parent class, is actually proxied from __dog
pluto
>>> isinstance(angry_1, Dog) #
True
大多数添加数据class的方法,如__repr__
,都会被破坏,包括在诸如dataclass.asdict
甚至vars
之类的东西中插入分身实例- 所以使用风险自负。