我有一个特殊的问题,但我将使示例更一般。 我有一个 Parent 类,其中包含一个强制的构造函数参数和一些可选的参数,每个参数都有一个默认值。然后,我从中继承 Child 并添加一个必需参数,并从 Child 继承 GrandChild 并向构造函数添加另一个必需参数。结果类似于:
class Parent():
def __init__(self, arg1, opt_arg1='opt_arg1_default_val', opt_arg2='opt_arg2_default_val',
opt_arg3='opt_arg3_default_val', opt_arg4='opt_arg4_default_val'):
self.arg1 = arg1
self.opt_arg1 = opt_arg1
self.opt_arg2 = opt_arg2
self.opt_arg3 = opt_arg3
self.opt_arg4 = opt_arg4
class Child(Parent):
def __init__(self, arg1, arg2, opt_arg1, opt_arg2, opt_arg3, opt_arg4):
super().__init__(arg1, opt_arg1, opt_arg2, opt_arg3, opt_arg4)
self.arg2 = arg2
class GrandChild(Child):
def __init__(self, arg1, arg2, arg3, opt_arg1, opt_arg2, opt_arg3, opt_arg4):
super().__init__(arg1, arg2, opt_arg1, opt_arg2, opt_arg3, opt_arg4)
self.arg3 = arg3
问题是,这看起来很难看,特别是如果我想从Child继承更多的类,则必须将所有参数复制/粘贴到该新类的构造函数中。
在寻找解决方案时,我发现here可以使用** kwargs来解决此问题,如下所示:
class Parent():
def __init__(self, arg1, opt_arg1='opt_arg1_default_val', opt_arg2='opt_arg2_default_val',
opt_arg3='opt_arg3_default_val', opt_arg4='opt_arg4_default_val'):
self.arg1 = arg1
self.opt_arg1 = opt_arg1
self.opt_arg2 = opt_arg2
self.opt_arg3 = opt_arg3
self.opt_arg4 = opt_arg4
class Child(Parent):
def __init__(self, arg1, arg2, **kwargs):
super().__init__(arg1, **kwargs)
self.arg2 = arg2
class GrandChild(Child):
def __init__(self, arg1, arg2, arg3,**kwargs):
super().__init__(arg1, arg2,**kwargs)
self.arg3 = arg3
但是,我不确定这是否正确。
在创建这些类的对象时也有一些不便之处。我正在使用PyCharm进行开发,在这种情况下,IDE具有一种显示函数/类构造函数参数的有用方法。例如,在第一个示例中,
这使开发变得更加容易,并且可以帮助将来的开发人员,因为他们可以看到该函数还有哪些其他参数。但是,在第二个示例中,不再显示可选参数:
在这种情况下,我不认为使用** kwargs是一个好习惯,因为必须深入研究代码,直到Parent类,以检查其具有哪些可选参数。
我也研究过使用Builder模式,但是我要做的就是将参数列表从我的类移到Builder类,而且我遇到了同样的问题,带有很多参数的构建器在继承时会创建更多的参数。在已经存在的参数之上。同样在Python中,就我所知,考虑到所有类成员都是公共的,并且无需使用setter和getter即可访问,Builder并没有多大意义。
关于如何解决此构造函数问题的任何想法?
答案 0 :(得分:3)
基本思想是编写代码,为您生成__init__
方法,并使用所有参数显式指定,而不用通过*args
和/或**kwargs
进行指定,甚至不需要重复所有self.arg1 = arg1
行。
理想情况下,它可以轻松添加PyCharm可用于弹出提示和/或静态类型检查的类型注释。 1
而且,当您使用它时,为什么不构建一个显示相同值的__repr__
呢?甚至还有__eq__
和__hash__
,也许还有字典比较运算符,以及往返于dict
的键的匹配与每个JSON持久性的属性相匹配的dataclasses
,以及……
或者,甚至更好的是,使用可以为您解决这些问题的库。
Python 3.7带有这样的库dataclasses
。或者,您可以使用attrs
之类的第三方库,该库可与Python 3.4和2.7(但有一定限制)一起使用。或者,在简单的情况下(对象是不可变的,并且您希望它们按照指定的顺序像其属性的元组一样工作),可以使用namedtuple
,它可以同时使用3.0和2.6。
不幸的是,from dataclasses import dataclass
@dataclass
class Parent:
arg1: str
opt_arg1: str = 'opt_arg1_default_val'
opt_arg2: str = 'opt_arg2_default_val'
opt_arg3: str = 'opt_arg3_default_val'
opt_arg4: str = 'opt_arg4_default_val'
@dataclass
class Child(Parent):
arg2: str
不适用于您的用例。如果您只是这样写:
arg2
…您会收到错误消息,因为它试图将强制参数opt_arg1
放在默认值参数opt_arg4
至dataclasses
之后。
Child(arg1, arg2, opt_arg1=…
不能对参数(Child(*, arg1, opt_arg1=…, arg2)
)进行重新排序,也不能强制它们成为仅关键字的参数(attrs
)。 __init__
并没有开箱即用的功能,但是您可以添加它。
因此,它并不像您希望的那么琐碎,但它是可行的。
但是,如果您想自己编写此代码,您将如何动态创建exec
函数?
最简单的选项是exec
。
您可能已经听说inspect
很危险。但这只是危险,如果您要传递来自用户的值。在这里,您只传递了来自您自己的源代码的值。
它仍然很丑陋,但有时还是最好的答案。 The standard library's namedtuple
used to be one giant exec
template.,even the current version uses exec
for most of the methods和so does dataclasses
。
还要注意,所有这些模块都将字段集存储在私有类属性中的某个位置,因此子类可以轻松读取父类的字段。如果您不这样做,则可以使用Signature
模块为基类(或用于多重继承的基类)的初始化程序获取base._fields
并从那里开始。但是,仅使用attrs
显然要简单得多(并且可以存储通常不包含在签名中的额外元数据)。
这是一个简单的实现,无法处理dataclasses
或def makeinit(cls):
fields = ()
optfields = {}
for base in cls.mro():
fields = getattr(base, '_fields', ()) + fields
optfields = {**getattr(base, '_optfields', {}), **optfields}
optparams = [f"{name} = {val!r}" for name, val in optfields.items()]
paramstr = ', '.join(['self', *fields, *optparams])
assignstr = "\n ".join(f"self.{name} = {name}" for name in [*fields, *optfields])
exec(f'def __init__({paramstr}):\n {assignstr}\ncls.__init__ = __init__')
return cls
@makeinit
class Parent:
_fields = ('arg1',)
_optfields = {'opt_arg1': 'opt_arg1_default_val',
'opt_arg2': 'opt_arg2_default_val',
'opt_arg3': 'opt_arg3_default_val',
'opt_arg4': 'opt_arg4_default_val'}
@makeinit
class Child(Parent):
_fields = ('arg2',)
的大多数功能,但是会在所有可选参数之前对所有必需参数进行排序。
__init__
现在,您已经在Parent
和Child
上有了所需的help
方法,完全可以检查 2 (包括@dataclass
) ,而不必重复自己。
1。我没有使用PyCharm,但是我知道在3.7发行之前,他们的开发人员已经参与了@dataclass
的讨论,并且已经在为IDE添加对它的显式支持,因此它甚至没有必须评估类定义以获得所有这些信息。我不知道它是否在当前版本中可用,但是如果没有,我认为它将可用。同时,$customer = Mage::getSingleton('customer/session')->getCustomer();
$mail = $customer->getEmail();
已经对我有用,它具有IPython自动完成功能,emacs flycheck等功能,对我来说已经足够了。 :)
2。 …至少在运行时。 PyCharm可能无法完全静态地解决问题,无法完成弹出窗口。