在类中设置默认值
Posted
技术标签:
【中文标题】在类中设置默认值【英文标题】:Setting default values in a class 【发布时间】:2019-07-03 22:30:03 【问题描述】:我正在 Python 中创建一个类,但我不确定如何正确设置默认值。我的目标是为所有类实例设置默认值,也可以通过类方法进行修改。但是,我希望在调用方法后恢复初始默认值。
我已经能够使用下面显示的代码使其工作。它不是很“漂亮”,所以我怀疑这是解决这个问题的更好方法。
class plots:
def __init__(self, **kwargs):
self.default_attr = 'a': 1, 'b': 2, 'c': 3
self.default_attr.update(kwargs)
self.__dict__.update((k, v) for k, v in self.default_attr.items())
def method1(self, **kwargs):
self.__dict__.update((k, v) for k, v in kwargs.items())
#### Code for this method goes here
# Then restore initial default values
self.__dict__.update((k, v) for k, v in self.default_attr.items())
当我使用这个类时,我会执行my_instance = plots()
和my_instance.method1()
、my_instance.method1(b = 5)
和my_instance.method1()
之类的操作。第三次调用method1
时,如果我不在方法定义末尾重置默认值,b
将为 5,但我希望它再次为 2。
注意:上面的代码只是一个例子。真正的类有几十个默认值,将它们全部用作输入参数将被视为反模式。
关于如何正确解决此问题的任何建议?
【问题讨论】:
def __init__(self, a = 1, b = 2, c = 3, **kwargs):
?至于method1
,听起来它的kwargs
有一个本地范围,那么为什么还要设置全局变量然后在最后重置它们呢?只需将kwargs
用作method1
中的本地人
@Dan 我认为期望是该方法的代码会调用其他方法,他希望他们看到临时修改的属性。
mock
库提供了patch.dict
,您可以将其用作上下文管理器。
好吧,如果 混乱 是问题所在,那么self.__dict__.update((k, v) for k, v in kwargs.items())
-> self.__dict__.update(kwargs)
@ReblochonMasque 这是一个棘手的问题。 patch.dict
,因为它更新 data 而不是修补名称,所以在 mock
库中感觉至少有点格格不入。但是在生产代码中使用mock
会在我嘴里留下不好的味道,因此是评论而不是答案。
【参考方案1】:
您可以使用类变量和属性来实现为所有类实例设置默认值的目标。可以直接修改实例值,调用方法后恢复初始默认值。
鉴于“真正的类有几十个默认值”的上下文,您可以考虑的另一种方法是设置一个包含默认值的配置文件,并使用该文件初始化或重置默认值。
这是使用一个类变量的第一种方法的简短示例:
class Plots:
_a = 1
def __init__(self):
self._a = None
self.reset_default_values()
def reset_default_values(self):
self._a = Plots._a
@property
def a(self):
return self._a
@a.setter
def a(self, value):
self._a = value
plot = Plots()
print(plot.a)
plot.a = 42
print(plot.a)
plot.reset_default_values()
print(plot.a)
输出:
1
42
1
【讨论】:
【参考方案2】:有很多方法可以解决这个问题,但如果您安装了 python 3.7(或安装了 3.6 并安装了the backport),dataclasses 可能是一个不错的解决方案。
首先,它可以让你以一种可读和紧凑的方式定义默认值,并且还允许你需要的所有变异操作:
>>> from dataclasses import dataclass
>>> @dataclass
... class Plots:
... a: int = 1
... b: int = 2
... c: int = 3
...
>>> p = Plots() # create a Plot with only default values
>>> p
Plots(a=1, b=2, c=3)
>>> p.a = -1 # update something in this Plot instance
>>> p
Plots(a=-1, b=2, c=3)
您还可以使用dataclass field definition 免费定义默认工厂而不是默认值。这可能还不是问题,但它避免了mutable default value gotcha,每个python程序员迟早都会遇到。
最后但并非最不重要的一点是,考虑到现有数据类,编写 reset
函数非常容易,因为它会跟踪其 __dataclass_fields__
属性中已经存在的所有默认值:
>>> from dataclasses import dataclass, MISSING
>>> @dataclass
... class Plots:
... a: int = 1
... b: int = 2
... c: int = 3
...
... def reset(self):
... for name, field in self.__dataclass_fields__.items():
... if field.default != MISSING:
... setattr(self, name, field.default)
... else:
... setattr(self, name, field.default_factory())
...
>>> p = Plots(a=-1) # create a Plot with some non-default values
>>> p
Plots(a=-1, b=2, c=3)
>>> p.reset() # calling reset on it restores the pre-defined defaults
>>> p
Plots(a=1, b=2, c=3)
所以现在您可以编写一些函数 do_stuff(...)
来更新 Plot 实例中的字段,并且只要您执行 reset()
更改就不会持续存在。
【讨论】:
【参考方案3】:您可以使用上下文管理器或装饰器来应用和重置值,而无需在每个方法上键入相同的代码。
与其拥有self.default_attr
,不如回到之前的状态。
使用装饰器可以获得:
def with_kwargs(fn):
def inner(self, **kwargs):
prev = self.__dict__.copy()
try:
self.__dict__.update(kwargs)
ret = fn(self)
finally:
self.__dict__ = prev
return ret
return inner
class plots:
a = 1
b = 2
c = 3
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
@with_kwargs
def method1(self):
# Code goes here
恕我直言,这是个坏主意,至少建议不要改变 plots
。为此,您可以创建一个新对象并将其作为self
传递给method1
。
class Transparent:
pass
def with_kwargs(fn):
def inner(self, **kwargs):
new_self = Transparent()
new_self.__dict__ = **self.__dict__, **kwargs
return fn(new_self)
return inner
【讨论】:
以上是关于在类中设置默认值的主要内容,如果未能解决你的问题,请参考以下文章
如何在 AssociationField EasyAdmin 3 中设置默认值