我正试图创建一个数据类,将所有相关数据存储在一个对象中。如何初始化一个数据类实例,其中的值是从数据类中的函数计算的,这些函数接受参数?
这就是我目前的处境:
@dataclass
class Person:
def Name(self):
return f'My name is {self.name[0]} {self.name[1]}.'
def Age(self):
return f'I am {self.age} years old.'
name: field(default_factory=Name(self), init=True)
age: field(default_factory=Age(self), init=True)
person = Person(('John', 'Smith'), '100')
print(person)
电流输出:
Person(name=('John', 'Smith'), age='100')
这就是我试图实现的输出:
Person(name='My name is John Smith', age='I am 100 years old')
我试图在dataclass';中使用How引用"self";字段?以供本主题参考。
首先-这是相当微妙的-我注意到将dataclasses.field()
作为类型注释是不起作用的。即name: field(...)
无效。我可以假设你的意思是做name: str = field(...)
。这里str
是名称的类型注释。
但是,即使使用这种方法,您也会根据传递default_factory
参数的方式遇到TypeError
——您需要一个可调用的无参数,尽管我注意到这在本用例中似乎没有帮助。
我的印象是,单独使用dataclasses.field(...)
是不可能实现您想要实现的,因为我相信文档表明default_factory
需要是可调用的零参数。
例如,default_factory=list
的工作原理是list()
提供了一个无参数构造函数。
但是,请注意以下情况是不可能的:
field(default_factory = lambda world: f'hello {world}!')
dataclasses
不会将world
的值传递给default_factory
函数,因此使用这种方法会遇到错误。
好消息是,在你的情况下,有一些不同的替代方案或选择可以考虑,我将在下面概述。
仅初始化变量
为了解决这个问题,一种选择是使用InitVar
和field(init=False)
:的组合
from dataclasses import field, dataclass, InitVar
@dataclass
class Person:
in_name: InitVar[tuple[str, str]]
in_age: InitVar[str]
name: str = field(init=False)
age: str = field(init=False)
def __post_init__(self, in_name: tuple[str, str], in_age: str):
self.name = f'My name is {in_name[0]} {in_name[1]}.'
self.age = f'I am {in_age} years old.'
person = Person(('John', 'Smith'), '100')
print(person)
打印:
Person(name='My name is John Smith.', age='I am 100 years old.')
属性
另一种用法可能是使用数据类中的字段属性。在这种情况下,如所示,将值传递给构造函数方法(即tuple
和str
(,并且用于每个字段属性的@setter
方法生成格式化的字符串,该字符串存储在私有属性中,例如self._name
。
请注意,由于dataclasses
当前处理(或静默忽略(属性的方式,在构造函数中没有传递字段属性的默认值时,会出现未定义的行为。
为了解决这个问题,您可以使用元类,比如我在本文中概述的元类。
from dataclasses import field, dataclass
@dataclass
class Person:
name: tuple[str, str]
age: str
# added to silence any IDE warnings
_age: str = field(init=False, repr=False)
_name: str = field(init=False, repr=False)
@property
def name(self):
return self._name
@name.setter
def name(self, name: tuple[str, str]):
self._name = f'My name is {name[0]} {name[1]}.'
@property
def age(self):
return self._age
@age.setter
def age(self, age: str):
self._age = f'I am {age} years old.'
person = Person(('John', 'Smith'), '100')
print(person)
person.name = ('Betty', 'Johnson')
person.age = 150
print(person)
# note that a strange error is returned when no default value is passed for
# properties; you can use my gist to work around that.
# person = Person()
打印:
Person(name='My name is John Smith.', age='I am 100 years old.')
Person(name='My name is Betty Johnson.', age='I am 150 years old.')
描述符
最后一个选项是在Python中使用描述符,我很可能会推荐这个选项,因为它比属性更容易设置。
据我所知,与声明大量属性相比,描述符本质上是一种更容易的方法,尤其是在所述属性的目的或用法非常相似的情况下。
下面是一个名为FormatValue
:的自定义描述符类的示例
from typing import Callable, Any
class FormatValue:
__slots__ = ('fmt', 'private_name', )
def __init__(self, fmt: Callable[[Any], str]):
self.fmt = fmt
def __set_name__(self, owner, name):
self.private_name = '_' + name
def __get__(self, obj, objtype=None):
value = getattr(obj, self.private_name)
return value
def __set__(self, obj, value):
setattr(obj, self.private_name, self.fmt(value))
它可以按如下方式使用,其工作原理与上面的示例相同,具有属性:
from dataclasses import dataclass
@dataclass
class Person:
name: 'tuple[str, str] | str' = FormatValue(lambda name: f'My name is {name[0]} {name[1]}.')
age: 'str | int' = FormatValue(lambda age: f'I am {age} years old.')
person = Person(('John', 'Smith'), '100')
print(person)
person.name = ('Betty', 'Johnson')
person.age = 150
print(person)
打印:
Person(name='My name is John Smith.', age='I am 100 years old.')
Person(name='My name is Betty Johnson.', age='I am 150 years old.')