【问题标题】:How to overload __init__ method based on argument type?如何根据参数类型重载 __init__ 方法?
【发布时间】:2010-09-13 13:58:07
【问题描述】:

假设我有一个类,它有一个名为 data 的成员,它是一个列表。

我希望能够使用例如文件名(其中包含用于初始化列表的数据)或实际列表来初始化类。

你的技术是什么?

你只是通过查看__class__来检查类型吗?

我可能会遗漏一些技巧吗?

我习惯了 C++ 中的参数类型重载很容易。

【问题讨论】:

  • @反之亦然? (我的意思是 this 是较老的问题)
  • @Wolf 我不会说两者之间哪个主题更好,但是当较新的问题质量更好/有更好的答案/涵盖该主题时,较旧的问题通常会因为新问题的欺骗而被关闭以更广泛适用的方式。

标签: python constructor operator-overloading


【解决方案1】:

更好的方法是使用 isinstance 和类型转换。如果我的理解是正确的,你想要这个:

def __init__ (self, filename):
    if isinstance (filename, basestring):
        # filename is a string
    else:
        # try to convert to a list
        self.path = list (filename)

【讨论】:

    【解决方案2】:

    您可能想要isinstance 内置函数:

    self.data = data if isinstance(data, list) else self.parse(data)
    

    【讨论】:

    • if else 表达式仅适用于 python 2.5(及更高版本)
    【解决方案3】:

    你应该使用 isinstance

    isinstance(...)
        isinstance(object, class-or-type-or-tuple) -> bool
    
        Return whether an object is an instance of a class or of a subclass thereof.
        With a type as second argument, return whether that is the object's type.
        The form using a tuple, isinstance(x, (A, B, ...)), is a shortcut for
        isinstance(x, A) or isinstance(x, B) or ... (etc.).
    

    【讨论】:

      【解决方案4】:

      好的,太好了。我只是把这个例子和一个元组而不是文件名放在一起,但这很容易。谢谢大家。

      class MyData:
          def __init__(self, data):
              self.myList = []
              if isinstance(data, tuple):
                  for i in data:
                      self.myList.append(i)
              else:
                  self.myList = data
      
          def GetData(self):
              print self.myList
      

      a = [1,2]

      b = (2,3)

      c = MyData(a)

      d = MyData(b)

      c.GetData()

      d.GetData()

      [1, 2]

      [2, 3]

      【讨论】:

      • init 中不需要所有的代码——我将它缩短为只是一个类型转换,它做同样的事情并且更灵活。
      • 在 Python 中,getter 也大多是不必要的。只需使用直接属性访问。如果您需要做更多事情,可以使用 property() 将 getter/setter 隐藏在普通属性访问之后。
      • 我知道,但这违背了示例的目的;我只是想展示如何使用两种不同的输入类型。元组/列表可能没有必要,但如果那是文件名,那就是必要的。不过,我想这只是与其他人所说的相呼应。我的例子对我很有启发性
      【解决方案5】:

      获得“替代构造函数”的一种更简洁的方法是使用类方法。例如:

      >>> class MyData:
      ...     def __init__(self, data):
      ...         "Initialize MyData from a sequence"
      ...         self.data = data
      ...     
      ...     @classmethod
      ...     def fromfilename(cls, filename):
      ...         "Initialize MyData from a file"
      ...         data = open(filename).readlines()
      ...         return cls(data)
      ...     
      ...     @classmethod
      ...     def fromdict(cls, datadict):
      ...         "Initialize MyData from a dict's items"
      ...         return cls(datadict.items())
      ... 
      >>> MyData([1, 2, 3]).data
      [1, 2, 3]
      >>> MyData.fromfilename("/tmp/foobar").data
      ['foo\n', 'bar\n', 'baz\n']
      >>> MyData.fromdict({"spam": "ham"}).data
      [('spam', 'ham')]
      

      它更简洁的原因是毫无疑问期望什么类型,并且您不必猜测调用者打算如何处理它给您的数据类型。 isinstance(x, basestring) 的问题是调用者无法告诉你,例如,即使类型不是基本字符串,你也应该将其视为字符串(而不是另一个序列)。也许调用者想将相同的类型用于不同的目的,有时作为单个项目,有时作为项目序列。明确化可以消除所有疑虑,并带来更健壮、更清晰的代码。

      【讨论】:

      • 酷!我在哪里可以了解 @classmethod 到底做了什么?
      • 你在哪里定义了 cls() 的行为?
      • @Ajay 请参阅this 澄清问题
      • 当目标是使用例如fromfilename 第一?
      • 我为此奋斗了一段时间,最后我创建了一个基类和两个子类,每个子类都有不同的 init 参数列表。这对我来说更具可读性。感谢您的启发!
      【解决方案6】:

      很好的问题。我也解决了这个问题,虽然我同意“工厂”(类方法构造函数)是一个很好的方法,但我想建议另一种方法,我也发现它非常有用:

      这是一个示例(这是一个read 方法而不是构造函数,但思路是一样的):

      def read(self, str=None, filename=None, addr=0):
          """ Read binary data and return a store object. The data
              store is also saved in the interal 'data' attribute.
      
              The data can either be taken from a string (str 
              argument) or a file (provide a filename, which will 
              be read in binary mode). If both are provided, the str 
              will be used. If neither is provided, an ArgumentError 
              is raised.
          """
          if str is None:
              if filename is None:
                  raise ArgumentError('Please supply a string or a filename')
      
              file = open(filename, 'rb')
              str = file.read()
              file.close()
          ...
          ... # rest of code
      

      这里的关键思想是使用 Python 对命名参数的出色支持来实现这一点。现在,如果我想从文件中读取数据,我会说:

      obj.read(filename="blob.txt")
      

      要从字符串中读取它,我说:

      obj.read(str="\x34\x55")
      

      这样,用户只需调用一个方法。如您所见,在内部处理它并不太复杂

      【讨论】:

      • obj.read(str="\x34\x55")是如何处理的;当 str 不是 None 时,您没有可以处理的代码
      • @brainstorm 我认为处理非无字符串的代码位于“#其余代码”中。 :-)
      • 当您想要重载许多版本的构造函数时,可能会导致此解决方案不那么优雅的事情发生,例如您想要从整数、文件或字符串构造对象, OR... OR... OR... OR... OR... 然后你会得到一个很长的 init 参数列表。
      • 另一个问题是作为调用者,除非我阅读文档,否则我不知道应该使用哪些参数来构造对象。在上面的示例中,调用者可以同时提供 str 和文件名,但只考虑 str,因为它在 if 语句层次结构中更高。文档可以提供帮助,但最好是我们可以设计没有歧义的界面。
      • 我个人更喜欢更明确的解决方案,即每种类型都有一个构造函数。这使您的代码更易于阅读、维护和更改。
      【解决方案7】:

      快速而肮脏的修复

      class MyData:
          def __init__(string=None,list=None):
              if string is not None:
                  #do stuff
              elif list is not None:
                  #do other stuff
              else:
                  #make data empty
      

      然后你可以调用它

      MyData(astring)
      MyData(None, alist)
      MyData()
      

      【讨论】:

      • 第二个最好写成MyData(list = alist)
      • 这是我认为最好的解决方案。如果您愿意看一下,我已经详细说明了它:stackoverflow.com/a/26018762/385025
      • 你不会在__init__ 中错过self 吗?您可能不想使用 list 作为输入名称,因为它会影响内置类型 list
      • 这更像是一种变通方法,不能正确回答问题
      【解决方案8】:

      你为什么不更pythonic?

      class AutoList:
      def __init__(self, inp):
          try:                        ## Assume an opened-file...
              self.data = inp.read()
          except AttributeError:
              try:                    ## Assume an existent filename...
                  with open(inp, 'r') as fd:
                      self.data = fd.read()
              except:
                  self.data = inp     ## Who cares what that might be?
      

      【讨论】:

      • 永远不要通过使用 try catch 强制错误来控制执行流程。这是所有编程语言的标准规则。
      • 不,在 Python 中经常(但并非总是)相反:stackoverflow.com/questions/12265451/… 在这种情况下,这样做确实便宜得多。
      • 我认为您误解了 try/except 的基础。它的基本工作方式与 if 语句非常不同,与其他流控制方法相比,处理的每个错误都具有非常高的 CPU 开销。您提供的链接表明应该在可能发生各种错误的地方使用 try/except - 我同意。但是,这种情况与使用 try/except 根据您肯定会经常或有意发生的异常来更改程序流程完全不同。
      • 要考虑的不仅仅是 CPU 时间(我非常了解stackoverflow.com/questions/2522005/…);这也是开发人员的时间,审阅者可以快速理解代码的简洁性,以及其他重要的编码风格问题。在上述第一种情况下,替代方案是:if inp.hasattr('read') and callable(inp.read): self.data = inp.read()。第二种情况会更加复杂。最后,所有这些可能会花费更多的 CPU。毫不奇怪,python 手册支持 EAFP:docs.python.org/3.6/glossary.html#term-eafp
      【解决方案9】:

      我的首选解决方案是:

      class MyClass:
          _data = []
          __init__(self,data=None):
              # do init stuff
              if not data: return
              self._data = list(data) # list() copies the list, instead of pointing to it.
      

      然后使用MyClass()MyClass([1,2,3]) 调用它。

      希望对您有所帮助。快乐编码!

      【讨论】:

      • 我猜是因为 _dataself._data 我们都不清楚。
      • _data 类变量在这个例子中没有意义。也许你对“_data”的含义有一些误解。
      • 本例中的构造函数返回的实例要么有自己的_data列表,要么引用类变量_data中的公共列表。一旦构建完成,就没有简单的方法让代码知道特定实例的行为。由于这两种行为完全不同,这似乎是一个糟糕的主意。
      【解决方案10】:

      使用 python3,您可以使用Implementing Multiple Dispatch with Function Annotations,正如 Python Cookbook 所写:

      import time
      
      
      class Date(metaclass=MultipleMeta):
          def __init__(self, year:int, month:int, day:int):
              self.year = year
              self.month = month
              self.day = day
      
          def __init__(self):
              t = time.localtime()
              self.__init__(t.tm_year, t.tm_mon, t.tm_mday)
      

      它的工作原理是:

      >>> d = Date(2012, 12, 21)
      >>> d.year
      2012
      >>> e = Date()
      >>> e.year
      2018
      

      【讨论】:

      • 这个使用元类构造多个__init__函数的想法很有意思,能解释一下这背后的原理吗?
      • MultipleMeta 中的@GoingMyWay __prepare__ 方法返回一个MultiDict 实例来替换__new__ 方法中clsdict 传递的Date 类默认__dict__ 属性。因此,它可以容纳多个同名'__init__'的函数,其值为MultiMethod实例,在其_method属性中存储了不同的函数注解。您应该查看Python Cookbook 了解更多详情。
      • @carton.swing ,哪个版本的python支持'委托' init ?我用3.6.8 尝试过,它抱怨TypeError: __init__() takes 2 positional arguments but 3 were given。在我的代码中,它是init(self, x)init(self, a,b),后者将从前者调用。
      • @YuriyPozniak Python 不支持“委托”,它只支持函数注释,您可以通过函数注释来实现元类。您是否使用 Python Cookbook 中所写的上述元类“MultipleMeta”?
      • @carton.swing,感谢您的回复。不,我没有使用 MultipleMeta。
      猜你喜欢
      • 2021-12-25
      • 1970-01-01
      • 2019-05-13
      • 1970-01-01
      • 1970-01-01
      • 2023-03-31
      • 2012-12-05
      相关资源
      最近更新 更多