ChatGPT解决这个技术问题 Extra ChatGPT

如何根据参数类型重载 __init__ 方法?

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

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

你这样做的技术是什么?

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

我可能会错过一些技巧吗?

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

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

T
Thomas Wouters

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

>>> 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 问题以进行澄清
为什么不使用 @staticmethod,因为此示例中的 __init__ 几乎没有什么用处,而目标是首先使用例如 fromfilename
我为此奋斗了一段时间,最后我创建了一个基类和两个子类,每个子类都有不同的 init 参数列表。这对我来说更具可读性。谢谢你的灵感!
F
FMc

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

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

使用 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。
B
Ben

快速而肮脏的修复

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
这更像是一种解决方法,不能正确回答问题
J
Justin

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

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

M
Moe

你应该使用 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.).

E
Eli Courtwright

您可能需要 isinstance 内置函数:

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

if else 表达式仅适用于 python 2.5(及更高版本)
B
Baltimark

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

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 = 我的数据(一)

d = 我的数据(b)

c.GetData()

d.GetData()

[1, 2]

[2, 3]


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

你为什么不去更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
F
Fydo

我首选的解决方案是:

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 中的公共列表的实例。一旦构建完成,就没有简单的方法让代码知道特定实例的行为。由于这两种行为完全不同,这似乎是一个糟糕的主意。