【问题标题】:How to detect invalid kwargs?如何检测无效的 kwargs?
【发布时间】:2022-01-03 22:03:09
【问题描述】:

我正在尝试开发一种机制来检测对继承具有鲁棒性的无效 kwargs。我想创建一个具有检查意外 kwargs 的 init 方法的类,并且我想创建另一个类,该类继承自第一个类并具有相同的 kwarg 检查。这可能比两层更深,但我认为解决两层可以进一步外推。这是我的尝试:

class MyClass(object):
    def __init__(self, **kwargs):
        # possible kwargs and default values
        self._kwargs_def = {
            'a':1, 
            'b':2,
            'c':3,
            }
        self.a = kwargs.pop('a', self._kwargs_def['a'])
        self.b = kwargs.pop('b', self._kwargs_def['b'])
        self.c = kwargs.pop('c', self._kwargs_def['c'])

        assert len(kwargs) == 0, f"Invalid kwargs: {kwargs.keys()}"
        
class MyChildClass(MyClass):
    def __init__(self, **kwargs):
        super.__init__() # run once to generate _kwargs_def
        # repeat to override defaults with any appropriate kwargs
        super.__init__(**{key:val for key, val in kwargs.items() if key in super._kwargs_def})
    
        self._kwargs_def.update({'d':4, 'e':5})
        
        self.d = kwargs.pop('d', self._kwargs_def['d'])
        self.e = kwargs.pop('e', self._kwargs_def['e'])
        
        assert len(kwargs) == 0, f"Invalid kwargs: {kwargs.keys()}"
        
my_instance = MyClass(a=7) # works
# I need a way around this: TypeError: descriptor '__init__' of 'super' object needs an argument
my_child_instance = MyChildClass(e=37) # doesn't work but should???
my_instance = MyClass(z=7) # fails and should fail
my_child_instance = MyChildClass(z=37) # fails but for wrong reason

【问题讨论】:

  • 您发布的代码有什么问题?
  • 调用super().__init__() 两次似乎并不安全。 __init__() 方法通常有副作用,它们不应该被多次调用。
  • 您对super().__init__() 的第二次调用会检查您传递的kwards,但不会将它们从您的kwargs 字典中删除。因此,您的 len(kwargs) 检查失败,因为父关键字仍在字典中。
  • 错字:super.__init__() 应该是super().__init__()

标签: python inheritance keyword-argument


【解决方案1】:

你让这件事变得比需要的更难。请注意,我在派生类中根本没有代码。

class MyClass(object):
    kwargs_def = {
        'a':1, 
        'b':2,
        'c':3,
    }
    def __init__(self, **kwargs):
        # possible kwargs and default values
        assert all(k in self.kwargs_def for k in kwargs), f"Invalid kwargs: {kwargs.keys()}"
        self.__dict__.update( self.kwargs_def )
        self.__dict__.update( kwargs )
        
class MyChildClass(MyClass):
    kwargs_def = MyClass.kwargs_def.copy()
    kwargs_def.update({
        'd':4,
        'e':5
    })
        
my_instance = MyClass(a=7) # works
my_child_instance = MyChildClass(e=37) # doesn't work but should???
my_instance = MyClass(z=7) # fails and should fail
my_child_instance = MyChildClass(z=37) # fails but for wrong reason

【讨论】:

  • 您需要在kwargs_def = MyClass.kwargs_def 中复制一份,否则您将直接改变基类的kwargs_def
  • 好收获。完全错过了。我希望我们有字典添加,就像一个集合。
  • 另外,kwargs_def 应该提供默认值,但您没有使用它们。
  • (从 Python 3.9 开始,dicts do have a union operator.)
  • 编辑队列已满,但我建议将a=42 传递给MyChildClass 的第一个实例,以表明它可以接受MyClass 中使用的参数。我还将两个故意失败放在 try ... except 语句中。否则,这是一个干净的解决方案。
【解决方案2】:

Python 已经检查了无效的关键字参数。只需使用内置机制:

class MyClass:
    def __init__(self, *, a=1, b=2, c=3):
        self.a = a
        self.b = b
        self.c = c

class MyChildClass(MyClass):
    def __init__(self, *, d=4, e=5, **kwargs):
        super().__init__(**kwargs)
        self.d = d
        self.e = e

任何无效的关键字参数都会被MyClass.__init__ 拒绝。 (位置参数将被拒绝,因为这些参数被定义为仅关键字。)

【讨论】:

  • 我尝试将* 替换为/。我不太明白这两个符号的逻辑,尤其是/
  • @Corralien:/ 之前的任何内容都仅限于位置。 * 之后的任何内容都是关键字。
  • 非常感谢!我知道这是题外话,但如果我理解得很好str.join(self, iterable, /) 意味着您可以传递任意数量的位置参数,但不能传递关键字。那么iterable 包含所有位置参数的列表?
  • @Corralien:不。你可以只传递一个位置参数,一个可迭代的要加入的字符串。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-14
  • 1970-01-01
相关资源
最近更新 更多