如何根据参数类型重载 __init__ 方法?
假设我有一个类,这个类里面有一个叫做data的成员,它是一个列表。
我想要能够用一个文件名来初始化这个类(这个文件里包含了用来初始化列表的数据),或者直接用一个实际的列表来初始化。
你们通常是怎么做到这一点的呢?
你们是通过查看 __class__
来检查类型吗?
有没有什么技巧我可能还没想到的?
我习惯于C++,在那里面根据参数类型重载函数是很简单的。
10 个回答
41
这是个很好的问题。我也遇到过这个问题,虽然我同意“工厂”(类方法构造函数)是一种不错的方法,但我想推荐另一种我觉得也很有用的方法:
这里有个示例(这是一个 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")
这样用户只需要调用一个方法。正如你看到的,内部处理并不复杂。
49
在Python 3中,你可以使用通过函数注解实现多重分发,就像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
565
有一种更简洁的方法来获取“备用构造函数”,那就是使用类方法。例如:
>>> 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)
的问题在于,调用者无法告诉你,尽管类型不是 basestring,但你应该把它当作字符串来处理(而不是其他序列)。而且,调用者可能希望在不同的情况下使用相同的类型,有时作为单个项目,有时作为多个项目的序列。明确地说明这一点可以消除所有疑虑,使代码更加健壮和清晰。