【问题标题】:How can I inherit defaultdict and use its copy method in subclass method?如何继承 defaultdict 并在子类方法中使用其复制方法?
【发布时间】:2017-07-31 08:17:42
【问题描述】:

有代码。

from collections import defaultdict
class A(defaultdict):
  def __init__(self):
    super(A, self).__init__(lambda :0)
    self.x = 1

  def my_copy(self):
    return self.copy()

if __name__ == '__main__':
  a = defaultdict(lambda :0)
  b = a.copy() # no error when using the base class directly
  a = A()
  b = a.my_copy()

有错误:

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1591, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1018, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
   exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/liu/project/scir/pytorch_test/t.py", line 14, in <module>
    b = a.my_copy()
  File "/Users/liu/project/scir/pytorch_test/t.py", line 8, in my_copy
    return self.copy()
TypeError: __init__() takes 1 positional argument but 3 were given

我不知道如何继承复制方法,也不知道我为什么要给出3个参数。

【问题讨论】:

    标签: python inheritance collections copy defaultdict


    【解决方案1】:

    在调用copy 时,defaultdict 调用带有参数的构造函数,以传递default_factory 函数和数据。

    您的构造函数不接受任何参数,因此它只能使用固定工厂构建空字典。

    像这样修复你的构造函数:

    def __init__(self,*args):
    

    但是您必须将args 传递给母类,否则您复制的字典将为空(不是您想要的)。

    由于您专门研究默认工厂,因此如果args 为空,则必须进行特殊处理:

    class A(defaultdict):
      def __init__(self,*args):
        if args:
            super(A, self).__init__(*args)
        else:
            super(A, self).__init__(int)  # better than lambda : 0
    

    或者使用三元组更简单:

    class A(defaultdict):
      def __init__(self,*args):
        super(A, self).__init__(*(args or (int,)))
    
    • args 不为空(从copy 调用)时,副本将采用原始属性(函数和数据)。
    • args 为空时,表示您正在创建一个新 dict,因此它只是修复了默认的工厂参数。

    除此之外:您可以将 (lambda :0) 替换为 (int)

    编辑:一种更复杂但确保用户无法更改默认值的方法是忽略第一个参数并强制int(如果第一个参数不是int,可能会发出警告):

    super(A, self).__init__(*([int]+list(args[1:])))
    

    这可行,但我不太喜欢忽略争论的想法。

    作为结论,继承内置类型通常很棘手,应谨慎使用(请参阅另一个尝试使用 pandas 数据框进行此操作的示例:building a class from an existing one)。有时创建一个以defaultdict 为参数的类,并且它只模仿/中继您计划使用的方法,这样会减少副作用。

    【讨论】:

    • 这让用户可以指定一个不同的工厂,这可能不是 OP 想要的。
    • @brunodesthuilliers 看到我的编辑。但我的结论是:安全地从内置类型继承是很棘手的。
    【解决方案2】:

    我决定将一个小评论扩展到一个答案。虽然在已经给出的答案中给出了完美的分析,但我不喜欢提议的论点修改。 defaultdict 和底层 dict 都有一个重要的签名(参数的使用)。下面的代码不涉及参数,并将它们原封不动地传递给原始实现:

    def __init__(self, *args, **kwargs):
        super(A, self).__init__(*args, **kwargs)
        self.default_factory = int
    

    kwargs 也被保留了,例如A(a=1,b=2) 有效。

    【讨论】:

      【解决方案3】:

      defaultdict.__init__() 接受三个参数:

      1. self(当然),
      2. 一个可选的工厂调用缺失键,和
      3. 一组可选的键值:值(可以是dict(key, value) 对的序列)。

      defaultdict.copy() 将创建一个新的 defaultdict 实例并将其传递给它的 factory 可调用对象及其当前键值集的浅表副本。

      您的子类的__init__ 只接受self 作为参数,但最终被三个调用。

      这里的解决方法是重写A.__init__,这样它就可以处理这两种情况:

      class A(defaultdict):
          def __init__(self, *args):
              # make sure we force the factory
              args = (int,) + args[1:]
              super(A, self).__init__(*args)
      

      【讨论】:

        猜你喜欢
        • 2019-01-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-06
        • 1970-01-01
        相关资源
        最近更新 更多