我目前正在尝试Python 3.7中引入的新数据类结构。我目前被困在试图做一些继承的父类。看起来参数的顺序被我当前的方法搞砸了,比如子类中的bool形参在其他形参之前传递。这将导致一个类型错误。
from dataclasses import dataclass
@dataclass
class Parent:
name: str
age: int
ugly: bool = False
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f'The Name is {self.name} and {self.name} is {self.age} year old')
@dataclass
class Child(Parent):
school: str
ugly: bool = True
jack = Parent('jack snr', 32, ugly=True)
jack_son = Child('jack jnr', 12, school = 'havard', ugly=True)
jack.print_id()
jack_son.print_id()
当我运行这段代码时,我得到这个TypeError:
TypeError: non-default argument 'school' follows default argument
我怎么解决这个问题?
下面的方法在使用纯python数据类和没有太多样板代码的情况下处理这个问题。
丑陋的:数据类。InitVar[bool]只是作为一个伪字段来帮助我们进行初始化,一旦创建实例就会丢失。而_ugly: bool = field(init=False)是一个实例成员,它不会通过__init__方法初始化,但也可以使用__post_init__方法初始化(你可以在这里找到更多)。
from dataclasses import dataclass, field, InitVar
@dataclass
class Parent:
name: str
age: int
ugly: InitVar[bool]
_ugly: bool = field(init=False)
def __post_init__(self, ugly: bool):
self._ugly = ugly
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f'The Name is {self.name} and {self.name} is {self.age} year old')
@dataclass
class Child(Parent):
school: str
jack = Parent('jack snr', 32, ugly=True)
jack_son = Child('jack jnr', 12, school='havard', ugly=True)
jack.print_id()
jack_son.print_id()
注意,这使得字段ugly成为强制性的,使其成为可选的。你可以在父类上定义一个类方法,其中包含ugly作为可选参数:
from dataclasses import dataclass, field, InitVar
@dataclass
class Parent:
name: str
age: int
ugly: InitVar[bool]
_ugly: bool = field(init=False)
def __post_init__(self, ugly: bool):
self._ugly = ugly
@classmethod
def create(cls, ugly=True, **kwargs):
return cls(ugly=ugly, **kwargs)
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f'The Name is {self.name} and {self.name} is {self.age} year old')
@dataclass
class Child(Parent):
school: str
jack = Parent.create(name='jack snr', age=32, ugly=False)
jack_son = Child.create(name='jack jnr', age=12, school='harvard')
jack.print_id()
jack_son.print_id()
现在您可以使用create(…)类方法作为创建父/子类的工厂方法,并使用默认值ugly。注意,这种方法必须使用命名参数才能工作。
一种可行的解决方法是使用monkey-patch来附加父字段
import dataclasses as dc
def add_args(parent):
def decorator(orig):
"Append parent's fields AFTER orig's fields"
# Aggregate fields
ff = [(f.name, f.type, f) for f in dc.fields(dc.dataclass(orig))]
ff += [(f.name, f.type, f) for f in dc.fields(dc.dataclass(parent))]
new = dc.make_dataclass(orig.__name__, ff)
new.__doc__ = orig.__doc__
return new
return decorator
class Animal:
age: int = 0
@add_args(Animal)
class Dog:
name: str
noise: str = "Woof!"
@add_args(Animal)
class Bird:
name: str
can_fly: bool = True
Dog("Dusty", 2) # --> Dog(name='Dusty', noise=2, age=0)
b = Bird("Donald", False, 40) # --> Bird(name='Donald', can_fly=False, age=40)
也可以预先添加非默认字段,
通过检查f.default是否为dc。失踪,
但这可能太脏了。
虽然猴子补丁缺乏遗传的一些特征,
它仍然可以用于向所有伪子类添加方法。
对于更细粒度的控制,请设置默认值
使用直流。字段(compare=False, repr=True,…)
基于Martijn Pieters的解决方案,我做了以下工作:
1)创建一个实现post_init的混合
from dataclasses import dataclass
no_default = object()
@dataclass
class NoDefaultAttributesPostInitMixin:
def __post_init__(self):
for key, value in self.__dict__.items():
if value is no_default:
raise TypeError(
f"__init__ missing 1 required argument: '{key}'"
)
2)然后在有继承问题的类中:
from src.utils import no_default, NoDefaultAttributesChild
@dataclass
class MyDataclass(DataclassWithDefaults, NoDefaultAttributesPostInitMixin):
attr1: str = no_default
编辑:
一段时间后,我也发现这个解决方案与mypy的问题,下面的代码修复这个问题。
from dataclasses import dataclass
from typing import TypeVar, Generic, Union
T = TypeVar("T")
class NoDefault(Generic[T]):
...
NoDefaultVar = Union[NoDefault[T], T]
no_default: NoDefault = NoDefault()
@dataclass
class NoDefaultAttributesPostInitMixin:
def __post_init__(self):
for key, value in self.__dict__.items():
if value is NoDefault:
raise TypeError(f"__init__ missing 1 required argument: '{key}'")
@dataclass
class Parent(NoDefaultAttributesPostInitMixin):
a: str = ""
@dataclass
class Child(Foo):
b: NoDefaultVar[str] = no_default
下面的方法在使用纯python数据类和没有太多样板代码的情况下处理这个问题。
丑陋的:数据类。InitVar[bool]只是作为一个伪字段来帮助我们进行初始化,一旦创建实例就会丢失。而_ugly: bool = field(init=False)是一个实例成员,它不会通过__init__方法初始化,但也可以使用__post_init__方法初始化(你可以在这里找到更多)。
from dataclasses import dataclass, field, InitVar
@dataclass
class Parent:
name: str
age: int
ugly: InitVar[bool]
_ugly: bool = field(init=False)
def __post_init__(self, ugly: bool):
self._ugly = ugly
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f'The Name is {self.name} and {self.name} is {self.age} year old')
@dataclass
class Child(Parent):
school: str
jack = Parent('jack snr', 32, ugly=True)
jack_son = Child('jack jnr', 12, school='havard', ugly=True)
jack.print_id()
jack_son.print_id()
注意,这使得字段ugly成为强制性的,使其成为可选的。你可以在父类上定义一个类方法,其中包含ugly作为可选参数:
from dataclasses import dataclass, field, InitVar
@dataclass
class Parent:
name: str
age: int
ugly: InitVar[bool]
_ugly: bool = field(init=False)
def __post_init__(self, ugly: bool):
self._ugly = ugly
@classmethod
def create(cls, ugly=True, **kwargs):
return cls(ugly=ugly, **kwargs)
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f'The Name is {self.name} and {self.name} is {self.age} year old')
@dataclass
class Child(Parent):
school: str
jack = Parent.create(name='jack snr', age=32, ugly=False)
jack_son = Child.create(name='jack jnr', age=12, school='harvard')
jack.print_id()
jack_son.print_id()
现在您可以使用create(…)类方法作为创建父/子类的工厂方法,并使用默认值ugly。注意,这种方法必须使用命名参数才能工作。
请注意,在Python 3.10中,现在可以使用数据类原生地进行此操作。
Dataclasses 3.10添加了kw_only属性(类似于attrs)。
它允许您指定哪些字段是keyword_only,因此将在init结束时设置,而不会导致继承问题。
直接从埃里克·史密斯关于这个主题的博客文章中摘录:
人们要求这个功能的原因有两个:
当一个数据类有很多字段时,通过位置指定它们可能变得不可读。为了向后兼容,它还要求将所有新字段添加到数据类的末尾。这并不总是可取的。
当一个数据类从另一个数据类继承,并且基类的字段具有默认值时,派生类中的所有字段也必须具有默认值。
下面是使用这个new参数的最简单的方法,但是有多种方法可以使用它来继承父类中的默认值:
from dataclasses import dataclass
@dataclass(kw_only=True)
class Parent:
name: str
age: int
ugly: bool = False
@dataclass(kw_only=True)
class Child(Parent):
school: str
ch = Child(name="Kevin", age=17, school="42")
print(ch.ugly)
看一下上面链接的博客文章,可以更彻底地解释kw_only。
干杯!
PS:由于它是相当新的,请注意您的IDE仍然可能会引发一个错误,但它在运行时工作
如果将属性从init函数中排除,则可以在父类中使用带有默认值的属性。如果您需要覆盖init的默认值,请使用Praveen Kulkarni的答案扩展代码。
from dataclasses import dataclass, field
@dataclass
class Parent:
name: str
age: int
ugly: bool = field(default=False, init=False)
@dataclass
class Child(Parent):
school: str
jack = Parent('jack snr', 32)
jack_son = Child('jack jnr', 12, school = 'havard')
jack_son.ugly = True
甚至
@dataclass
class Child(Parent):
school: str
ugly = True
# This does not work
# ugly: bool = True
jack_son = Child('jack jnr', 12, school = 'havard')
assert jack_son.ugly