【问题标题】:Store init parameters of a class and its subclasses存储类及其子类的初始化参数
【发布时间】:2020-03-16 09:00:54
【问题描述】:

我有一个类,我希望能够存储在创建该类或任何子类的新实例时给出的参数的名称和值(子类 init 具有比超类更多的参数)。

def init_store(init):
    @wraps(init)
    def wrapper(*args, **kwargs):
        self = args[0]
        fields = args[1:]
        field_dict = {arg_name: arg_val for arg_name, arg_val in zip(init.__code__.co_varnames[1:], fields)}
        self.init_values = dict(**field_dict, **kwargs)
        self.init_values['Problem'] = type(self).__name__
        init(*args, **kwargs)
    return wrapper

目前我用这个函数装饰我所有子类的初始化,这就是工作。 但是有没有办法在不必装饰每个 init 的情况下获得相同的效果? 环顾四周,我发现元类做了一些类似的事情,但我从未使用过它们,也找不到在此处应用它们的方法。

提前致谢!

【问题讨论】:

  • 如果您将类设计为仅使用关键字参数,这似乎会简单得多;那么您只需要子类将所有参数传递给super().__init__,而您的基类__init__ 可以简单地保存**kwargs,而无需装饰任何方法。
  • 是的,这种方式效果很好,但我更喜欢不必改变我的课程而只学习 kwargs
  • 为什么?如果您不必考虑将参数传递给各种方法的顺序,super 更容易正确使用。
  • 主类只有一个,但每个子类可能会引入冲突的位置参数。关键字参数标识自己,不依赖于它们的传递顺序。
  • @vjjhgj 查看我的编辑。

标签: python python-3.x class python-decorators


【解决方案1】:

此解决方案不起作用,因为我还想存储 args 名称 当前版本效果的 ex: 声明:

class A:
    def __init__(self, x):
        self.x = x

class B(A):
    @init_store
    def __init__(self, a1, a2):
        self.a1, self.a2 = a1, a2
        super(B, self).__init__(True)

调用:

b = B(1, 2)
print(b.init_values)

输出:

'{'a1': 1, 'a2': 2, 'Problem': 'B'}'

【讨论】:

  • 如果要存储 arg 名称,就是说需要在实例创建调用中使用关键字 args。
【解决方案2】:

您无需更改为仅使用关键字 args,您也可以捕获位置。或多或少像这样,您将适应任何您喜欢存储和访问参数的方式:

class C:
    def __init__(self, *args, **kwargs):
        self.called_with = dict(
            name = self.__class__,
            positionals = args,
            keywords = kwargs
            )
        print(args, kwargs)

class D(C):
    pass

c1 = C(1,2,3,a='A',b='B')
print(c1.called_with['name'])
print(c1.called_with['positionals'])
print(c1.called_with['keywords'])

print()

d1 = D(1,2,3,4,d='D')
print(d1.called_with['name'])
print(d1.called_with['positionals'])
print(d1.called_with['keywords'])

输出:

(1, 2, 3) {'a': 'A', 'b': 'B'}
<class '__main__.C'>
(1, 2, 3)
{'a': 'A', 'b': 'B'}

(1, 2, 3, 4) {'d': 'D'}
<class '__main__.D'>
(1, 2, 3, 4)
{'d': 'D'}

当然,您可以在需要时使用子类 init 中的super().__init__(args, kwargs)

但是,根据此处的各种 cmets 以及 @chepner 的见解,很明显 OP 想要使用关键字 args(根据要求并且因为它们很方便),并且混合位置只会使代码混乱,因为与它们一起,类需要了解很多关于它们的层次关系,以区分它们的位置参数与其他类参数的顺序)。类将与该信息高度耦合,这是错误的。

所以只需使用关键字 args。

至于默认值,这样就可以了:

if not 'somekey' in kwargs:
    kwargs['somekey'] = default_value

【讨论】:

  • 如果您开始让子类引入自己的位置参数,那么使用位置参数会变得很混乱。假设A 有一个参数,BC 都继承自A 并添加自己的第二个参数,然后D 继承自BC。你如何解释D(1, 2, 3)
  • 我真的没有这个问题,因为 super 的唯一参数取决于我所在的子类,而不是创建 sublass 实例时使用的参数 sublcas init 看起来像:``` def __init__(self, *args): # 使用 args super().__init__(True) ```
  • @chepner,你是对的,你的评论“主要课程只有一个,但是......”让你的观点非常清楚。我只是没有在这一切中得到 OP 的意图。我很简单地看到你的例子。具有这些位置的 D 实例。对他们做任何你想做的事。将它们传递给超级时也是如此。是的,这很愚蠢。这意味着一个类知道它在层次结构中的位置以选择正确的 arg。这将类与彼此的太多知识结合在一起。我从来不需要写这样的代码(但这只是我),这就是为什么我没有考虑太多。我正在编辑我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多