[英]Can you modify an object's field every time another field is modified?
I have a dataclass that looks like this我有一个看起来像这样的数据类
from dataclasses import dataclass, field
@dataclass
class Data:
name: str | None = None
file_friendly_name: str | None = field(default=None, init=False)
def __post_init__(self):
# If name, automatically create the file_friendly_name
if self.name:
self.file_friendly_name = "".join(
i for i in self.name if i not in "/:*?<>|"
)
If user passes name
on instantiation, file_friendly_name
is automatically created.如果用户在实例化时传递name
,则会自动创建file_friendly_name
。
Is there a way to do it so that every time name
is updated/changed, file_friendly_name
also changes?有没有办法做到每次更新/更改name
时, file_friendly_name
也会更改?
eg例如
data = Data()
data.name = 'foo/bar'
print(data.file_friendly_name) # want: 'foobar'
data = Data(name='foo/bar')
data.name = 'new?name'
print(data.file_friendly_name) # want: 'newname'
Update based on answers:根据答案更新:
_name: str
and creating name
using getters/setters.我试过设置_name: str
并使用 getters/setters 创建name
。 But I don't like how when you do print(Data())
it shows _name
as an attribute.但我不喜欢当你执行print(Data())
时它如何将_name
显示为属性。 I'd like that not to happen.我不希望这种情况发生。file_friendly_name
as a property.我喜欢将file_friendly_name
设置为属性。 But then you can't see that as an attribute when you do print(Data())
.但是当您执行print(Data())
时,您无法将其视为属性。 This is less of an issue but still not ideal.这不是什么大问题,但仍然不理想。 Can it just show name
and file_friendly_name
as attributes when doing print(Data())
?它可以在执行print(Data())
时仅将name
和file_friendly_name
显示为属性吗?
I'd suggest defining file_friendly_name
as @property
instead.我建议改为将file_friendly_name
定义为@property
。
from dataclasses import dataclass, fields
@dataclass
class Data:
name: str | None = None
@property
def file_friendly_name(self) -> str | None:
if self.name is not None:
return "".join(
i for i in self.name if i not in "\/:*?<>|"
)
else:
return None
def __repr__(self):
fields_str = [f'{field.name}={getattr(self, field.name)!r}'
for field in fields(self)]
fields_str.append(f'file_friendly_name={self.file_friendly_name}')
fields_res = ', '.join(fields_str)
return f'{type(self).__name__}({fields_res})'
Indeed, there is a way!确实,有办法!
from dataclasses import dataclass, field
@dataclass
class Data:
_name: str | None = None
file_friendly_name: str | None = field(default=None, init=False)
def __post_init__(self):
# If _name is not None, automatically create the file_friendly_name
if self._name is not None:
self.file_friendly_name = "".join(
i for i in self._name if i not in "/:*?<>|"
)
@property
def name(self) -> str | None:
return self._name
@name.setter
def name(self, new_val: str | None) -> None:
if self._name == new_val:
return
self._name = new_val
if self._name is None:
self.file_friendly_name = None
else:
self.file_friendly_name = "".join(
i for i in self._name if i not in "/:*?<>|"
)
Since you asked for a way to actually update the file_friendly_name
field whenever name
changes, I've changed the name
field into a property which reads from private attribute _name
.由于您要求一种在name
更改时实际更新file_friendly_name
字段的方法,因此我已将name
字段更改为从私有属性_name
读取的属性。 Now it's _name
which is assessed in __post_init__
.现在是在__post_init__
中评估的_name
。
Then I've created a "setter" for the name
property.然后我为name
属性创建了一个“setter”。 This setter will be called every time name
is updated.每次更新name
时都会调用此设置器。 Note that there's no sort of Data.name.setattr(...)
boilerplate-y nonsense, given that we're in Python-land.请注意,这里没有Data.name.setattr(...)
样板式的废话,因为我们在 Python 领域。 When I say "updated", I mean whenever you do当我说“更新”时,我的意思是每当你这样做
>>> d = Data("Zev")
>>> d.name = "**Zev**"
that setter will be invoked and the name
and file_friendly_name
fields will be updated accordingly:该设置器将被调用,并且name
和file_friendly_name
字段将相应更新:
>>> d.file_friendly_name
'Zev'
>>> d.name
'**Zev**'
>>> data = Data()
>>> data.name = 'foo/bar'
>>> print(data.file_friendly_name)
'foobar'
>>> data = Data('foo/bar')
>>> data.name = 'new?name'
>>> print(data.file_friendly_name)
'newname'
One small drawback of this is that printing data
shows our private field:这样做的一个小缺点是打印data
会显示我们的私有字段:
>>> print(data)
Data(_name='new?name', file_friendly_name='newname')
However, you can work around this by defining your own __repr__
method:但是,您可以通过定义自己的__repr__
方法来解决此问题:
def __repr__(self) -> str:
return f"Data(name='{self._name}', file_friendly_name='{self.file_friendly_name}')"
>>> print(data)
Data(name='new?name', file_friendly_name='newname')
name
work in the constructor使name
在构造函数中起作用Finally, if you'd like your name
keyword argument back for constructing Data
instances, you can add your own constructor to it.最后,如果您希望您的name
关键字参数返回用于构造Data
实例,您可以向其添加自己的构造函数。 We'll DRY up the code this requires while we're at it:我们将干掉这需要的代码:
def __init__(self, name: str | None = None):
self._name = name
if self._name is not None:
self.file_friendly_name = self.make_file_friendly_name(self._name)
def __post_init__(self):
# If _name is not None, automatically create the file_friendly_name
if self._name is not None:
self.file_friendly_name = self.make_file_friendly_name(self._name)
@name.setter
def name(self, new_val: str | None) -> None:
if self._name == new_val:
return
self._name = new_val
if self._name is None:
self.file_friendly_name = None
else:
self.file_friendly_name = self.make_file_friendly_name(self._name) # 👈 revised
@staticmethod
def make_file_friendly_name(name: str) -> str:
return "".join(
i for i in name if i not in "\\/:*?<>|"
)
After this, the sample code works as expected:在此之后,示例代码按预期工作:
>>> data = Data()
>>> data.name = 'foo/bar'
>>> print(data.file_friendly_name)
'foobar'
>>> data = Data(name='foo/bar')
>>> data.name = 'new?name'
>>> print(data.file_friendly_name)
'newname'
Similiarly to @Yevhen's suggestion, but using setter on property you can trigger a specific function when setting to an attribute.与@Yevhen 的建议类似,但在属性上使用 setter 可以在设置属性时触发特定的 function。 You can then check if class has related private attribute to tell if you are definining it right now or it already exists.然后你可以检查 class 是否有相关的私有属性来判断你是现在定义它还是已经存在。
from dataclasses import dataclass, field
def methodToTrigger():
print("Triggered method")
@dataclass
class Data:
name: str = None
def __post_init__(self):
# If name, automatically create the file_friendly_name
if self.name:
self.file_friendly_name = "".join(
i for i in self.name if i not in "\/:*?<>|"
)
@property
def file_friendly_name(self):
return self._file_friendly_name
@file_friendly_name.setter
def file_friendly_name(self, value):
if not hasattr(self, "_file_friendly_name"):
methodToTrigger()
self._file_friendly_name = value
d = Data(name = "asdf")
print(d.file_friendly_name)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.