请原谅Python新手的风格无能。
我有一个类,它使用一个参数来建立初始数据。初始数据有两种方式可以进入:字符串列表,或带字符串键和整数值的字典。
现在我只实现了构造函数的一个版本,即带参数字典的版本,默认值为{}。 list参数init实现为方法,即
myClass = MyClass()
myClass.initList(listVar)
我当然可以忍受这个,但这当然不是完美的。所以,我决定转向一些Pythonic的智慧:应该如何实现这样的多态构造函数?我是否应该尝试无法读取initData.keys()以便嗅探这是否是字典?或者也许嗅探参数类型以实现糟糕的多态性,其中设计不受欢迎被认为是非pythonic?
答案 0 :(得分:3)
在一个理想的世界中,你会编写一个构造函数,可以在不知道差异的情况下使用list
或dict
(即鸭子类型)。当然,这不太现实,因为这些是非常不同的鸭子。
可以理解的是,你对检查实际实例类型的想法有些不满,因为它打破了鸭子打字的想法。但是,在python 2.6中引入了一个名为abc
的有趣模块,它允许定义“抽象基类”。要被视为抽象基类的实例,实际上不必从它继承,而只需实现其所有抽象方法。
collections
模块包含一些在这里感兴趣的抽象基类,即collections.Sequence
和collections.Mapping
。因此,您可以编写__init__
函数,如:
def __init__(self, somedata):
if isinstance(somedata, collections.Sequence):
# somedata is a list or some other Sequence
elif isinstance(somedata, collections.Mapping):
# somedata is a dict or some other Mapping
http://docs.python.org/2/library/collections.html#collections-abstract-base-classes包含每个ABC提供哪些方法的细节。如果您坚持这些,那么您的代码现在可以接受任何适合这些抽象基类之一的对象。而且,就采用内置dict
和list
类型而言,您可以看到:
>>> isinstance([], collections.Sequence)
True
>>> isinstance([], collections.Mapping)
False
>>> isinstance({}, collections.Sequence)
False
>>> isinstance({}, collections.Mapping)
True
而且,几乎是偶然的,你只是让它适用于tuple
。你可能不在乎它是否真的是list
,只是你可以读出它的元素。但是,如果您选中了isinstance(somedata, list)
,那么您将排除tuple
。这就是使用ABC买你的东西。
答案 1 :(得分:2)
Python中没有函数overloading。在if isinstance(...)
方法中使用__init__()
可以非常简单地阅读和理解:
class Foo(object):
def __init__(self, arg):
if isinstance(arg, dict):
...
elif isinstance(arg, list):
...
else:
raise Exception("big bang")
答案 2 :(得分:2)
正如@ Jan-PhilipGehrcke所说,pythonic
很难量化。对我而言,这意味着:
Zen of Python
获取完整列表,您可以通过在解释器中键入import this
获得)因此,最pythonic解决方案取决于您必须为每个支持的初始化程序做什么,以及您拥有多少个。我会说,如果你只有少数几个,只需几行代码即可处理,然后使用isinstance
和__init__
:
class MyClass(object):
def __init__(self, initializer):
"""
initialize internal data structures with 'initializer'
"""
if isinstance(initializer, dict):
for k, v in itit_dict.items():
# do something with k & v
setattr(self, k, v)
elif isinstance(initializer, (list, tuple)):
for item in initializer:
setattr(self, item, None)
另一方面,如果你有许多可能的初始化器,或者如果其中任何一个需要大量代码来处理,那么你将希望为每个可能的init类型都有一个classmethod
构造函数,最常见的用法是__init__
:
class MyClass(object):
def __init__(self, init_dict={}):
"""
initialize internal data structures with 'init_dict'
"""
for k, v in itit_dict.items():
# do something with k & v
setattr(self, k, v)
@classmethod
def from_sequence(cls, init_list):
"""
initialize internal data structures with 'init_list'
"""
result = cls()
for item in init_list:
setattr(result, item, None)
return result
这使每个可能的构造函数变得简单,干净且易于理解。
作为旁注:使用可变对象作为默认值(就像我在上面的__init__
中所做的那样)需要谨慎处理;原因是默认值只被评估一次,然后结果将用于每次后续调用。当您在函数中修改该对象时,这只是一个问题,因为每次后续调用都会看到这些修改 - 除非您想创建一个可能不是您正在寻找的行为的缓存。这不是我的例子的问题,因为我没有修改init_dict
,只是迭代它(如果调用者没有替换它,这是一个无操作,因为它是空的)。
答案 3 :(得分:-1)
您可以使用*args
和**kwargs
来执行此操作。
但是如果你想知道什么类型的参数,你应该使用type()
或isinstance()
。