【问题标题】: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 会在我嘴里留下不好的味道,因此是评论而不是答案。

标签: python class default


【解决方案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
      

      【讨论】:

        猜你喜欢
        • 2014-12-26
        • 1970-01-01
        • 2013-06-17
        • 1970-01-01
        • 1970-01-01
        • 2012-11-14
        • 1970-01-01
        • 1970-01-01
        • 2020-01-02
        相关资源
        最近更新 更多