将函数参数赋值给 `self`
10 个回答
我理解你觉得模板代码不好这个想法。但是在这种情况下,我不太确定有没有更好的替代方案。我们来看看各种可能性。
如果你只是在处理几个变量,那么一系列的 self.x = x
代码行其实很容易读懂。实际上,我觉得这种明确的写法在可读性上更好。虽然打这些代码可能有点麻烦,但这并不足以让我们去创造一种新的语言结构,反而可能会让事情变得更复杂。用 vars(self).update()
这种方式在这里反而会让人更困惑。
另一方面,如果你要传递九个、十个或者更多的参数给 __init__
,那么你可能需要重新考虑代码结构了。所以这个问题其实只适用于传递大约5到8个参数的情况。我能理解八行 self.x = x
会让人觉得打起来和读起来都很烦;但我不确定5到8个参数的情况是否足够常见或者麻烦到值得使用其他方法。因此,我觉得虽然你提出的担忧在理论上是有道理的,但在实际中,还有其他限制因素让这个问题变得不那么重要。
为了让这个观点更具体,我们可以考虑一个函数,它接收一个对象、一个字典和一个名字列表,然后把字典里的值赋给列表里的名字。这样可以确保你在给self赋值时依然很明确。(我绝对不会建议一种不明确列出要赋值变量的解决方案;那简直是个bug的温床):
>>> def assign_attributes(obj, localdict, names):
... for name in names:
... setattr(obj, name, localdict[name])
...
>>> class SomeClass():
... def __init__(self, a, b, c):
... assign_attributes(self, vars(), ['a', 'b', 'c'])
现在,虽然这个方法看起来并不是特别糟糕,但它仍然比简单的 self.x = x
一系列代码要难理解。而且根据具体情况,这种写法也比一、两行甚至三四行要长,打起来更麻烦。所以你只有在处理五个参数的情况下才会有一些好处。但这正好是你开始接近人类短期记忆极限的时刻(大约是7个“块”)。所以在这种情况下,你的代码已经有点难读了,这样做只会让它更难读。
你可以这样做,这种方法简单明了:
>>> class C(object):
def __init__(self, **kwargs):
self.__dict__ = dict(kwargs)
这样做的好处是,创建 C
类实例的代码可以决定这个实例在构造后有哪些属性,比如:
>>> c = C(a='a', b='b', c='c')
>>> c.a, c.b, c.c
('a', 'b', 'c')
如果你希望所有的 C
对象都有 a
、b
和 c
这些属性,这种方法就不太适用了。
(顺便提一下,这种模式是由 Guido 自己提出的,作为在 Python 中定义枚举的一种通用解决方案。你可以创建一个像上面那样的类,叫做 Enum
,然后你可以写出像 Colors = Enum(Red=0, Green=1, Blue=2)
这样的代码,从此以后就可以使用 Colors.Red
、Colors.Green
和 Colors.Blue
。)
如果你把 self.__dict__
设置为 kwargs
而不是 dict(kwargs)
,你可能会遇到什么样的问题,这是一个值得思考的练习。
装饰器的魔力!!
>>> class SomeClass():
@ArgsToSelf
def __init__(a, b=1, c=2, d=4, e=5):
pass
>>> s=SomeClass(6,b=7,d=8)
>>> print s.a,s.b,s.c,s.d,s.e
6 7 2 8 5
在定义的时候:
>>> import inspect
>>> def ArgsToSelf(f):
def act(self, *args, **kwargs):
arg_names,_,_,defaults = inspect.getargspec(f)
defaults=list(defaults)
for arg in args:
setattr(self, arg_names.pop(0),arg)
for arg_name,arg in kwargs.iteritems():
setattr(self, arg_name,arg)
defaults.pop(arg_names.index(arg_name))
arg_names.remove(arg_name)
for arg_name,arg in zip(arg_names,defaults):
setattr(self, arg_name,arg)
return f(*args, **kwargs)
return act
当然,你可以只定义一次这个装饰器,然后在整个项目中使用它。
而且,这个装饰器可以作用于任何对象的方法,不仅仅是 __init__
。