在类中设置默认值

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

【讨论】:

以上是关于在类中设置默认值的主要内容,如果未能解决你的问题,请参考以下文章

在easygui multienterbox中设置默认值

在 Twig 中设置 Symfony 2 表单字段的默认值

MySQL使用默认值与在查询中设置值是不是有任何性能差异?

如何在 AssociationField EasyAdmin 3 中设置默认值

Rails:如何在 ActiveRecord 中设置默认值?

如何在javascript中设置选择2框的默认值