将函数参数赋值给 `self`

29 投票
10 回答
9325 浏览
提问于 2025-04-17 09:16

我发现我常用的一种写法是把SomeClass.__init__()里的参数直接赋值给同名的self属性。举个例子:

class SomeClass():
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

其实这应该是很多人都会做的事情,连PyDev都有这个快捷方式——如果你把光标放在参数列表上,然后按Ctrl+1,就会出现一个选项,可以选择Assign parameters to attributes,这样就能自动生成那种模板代码

有没有其他更简短、更优雅的方法来完成这个赋值呢?

10 个回答

9

我理解你觉得模板代码不好这个想法。但是在这种情况下,我不太确定有没有更好的替代方案。我们来看看各种可能性。

如果你只是在处理几个变量,那么一系列的 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个“块”)。所以在这种情况下,你的代码已经有点难读了,这样做只会让它更难读。

10

你可以这样做,这种方法简单明了:

>>>  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 对象都有 abc 这些属性,这种方法就不太适用了。

(顺便提一下,这种模式是由 Guido 自己提出的,作为在 Python 中定义枚举的一种通用解决方案。你可以创建一个像上面那样的类,叫做 Enum,然后你可以写出像 Colors = Enum(Red=0, Green=1, Blue=2) 这样的代码,从此以后就可以使用 Colors.RedColors.GreenColors.Blue。)

如果你把 self.__dict__ 设置为 kwargs 而不是 dict(kwargs),你可能会遇到什么样的问题,这是一个值得思考的练习。

2

装饰器的魔力!!

>>> 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__

撰写回答