如何优雅/高效地为需要大量实例变量的Python类编写__init__函数?

use*_*r89 3 python class

假设你有一个带有许多(关键字)参数的类,其中大多数都是作为实例变量存储的:

class ManyInitVariables():
    def __init__(a=0, b=2, c=1, d=0, e=-1, ... , x=100, y=0, z=9):
Run Code Online (Sandbox Code Playgroud)

你会如何初始化它们__init__?你可以这样做:

class ManyInitVariables():
    def __init__(a=0, b=2, c=1, d=0, e=-1, ... , x=100, y=0, z=9):
        self.a = a
        self.b = b
        self.c = c
        ...
        self.z = z
Run Code Online (Sandbox Code Playgroud)

...但需要打字很多!我怎样才能__init__自动获得一些参数,注意其他参数可能不需要作为实例变量分配?

Ale*_*lli 11

我确信网上有很多其他类似的解决方案可以解决这个非常常见的问题,但这是一个例子:

import functools
import inspect

def absorb_args(f):
    args, _, _, defs = inspect.getargspec(f)
    args = args[1:]  # ignore the leading `self`
    @functools.wraps(f)
    def do_absorb(self, *a, **k):
        ndefs = len(args) - len(a) + 2
        for name, value in zip(args, a + defs[-ndefs:]):
            setattr(self, name, value)
        for name, value in k.items():
            setattr(self, name, value)
        return f(self, *a, **k)
    return do_absorb
Run Code Online (Sandbox Code Playgroud)

补充:我被要求进一步解释这一点,但是,如果你不擅长Python,那么这里有很多东西! - ).

functools.wraps是一个帮助制作更好的装饰器的装饰器,请参阅https://docs.python.org/2/library/functools.html#functools.wraps - 与问题没有直接关系,但对于支持help基于功能的交互式和工具非常有用'文档字符串.养成在编写函数装饰器时总是使用它的习惯(最常见的情况)包装装饰函数,你不会后悔.

inspect模块是在现代Python中进行内省的唯一正确方法. inspect.getargspec特别是为你提供了一个函数接受什么参数的信息,以及它们的默认值,如果有的话(我忽略的两个信息,通过分配它们_,是关于*a**k特殊的args,这个装饰器没有'支持).有关详细信息,请参阅https://docs.python.org/2/library/inspect.html?highlight=getargspec#inspect.getargspec.

self按惯例,它始终是方法的第一个arg(而这个装饰器仅用于方法:-).因此,第一个for循环处理位置args(无论是在调用中明确给出还是默认为默认值); 然后,第二个for循环处理命名args(我希望,这个更容易掌握:-). setattr当然是宝贵的内置函数,它设置一个带有变量名的属性,https://docs.python.org/2/library/functions.html?highlight = setattr #setattr for more.

顺便说一下,如果你只关心使用它__init__(正如你在下面的例子中看到的那样,absorb_attrs本身没有这样的约束),那么写一个装饰器,__init__为这个处理选择单独的类,并将该类装饰器应用于类本身.

另外,如果你的类__init__没有工作要做,一旦args以这种方式被"吸收",你仍然必须定义(装饰)__init__,但它的身体可以限制为解释参数的文档字符串(我个人更喜欢总是也有pass在这种情况下的声明,但这是一个个人风格的问题).

现在,回到最初的答案,举个例子......! - )

然后,例如,像

class Struggle(object):

    @absorb_args
    def __init__(self, a, b, c, bab='bo', bip='bop'):
        self.d = a + b

    @absorb_args
    def setit(self, x, y, z, u=23, w=45):
        self.t = x + y

    def __str__(self):
        attrs = sorted(self.__dict__)
        r = ['%s: %s' % (a, getattr(self, a)) for a in attrs]
        return ', '.join(r)

s = Struggle('fee', 'fie', 'foo', bip='beeeeeep')
s.setit(1, 2, 3, w=99)
print(s)
Run Code Online (Sandbox Code Playgroud)

会打印

a: fee, b: fie, bab: bo, bip: beeeeeep, c: foo, d: feefie, t: 3, u: 23, w: 99, x: 1, y: 2, z: 3
Run Code Online (Sandbox Code Playgroud)

如预期的.

我唯一能够以这种方式"重新发明轮子"(而不是在网上寻找解决方案)的借口是另一个晚上我的妻子和共同作者安娜(只有弗兰克威利森纪念奖的女性获奖者才能为Python社区做出贡献) ,BTW :-)问我这件事(我们是,慢慢地唉!,写第三版"Python in a Nutshell") - 我花了10分钟来编写代码(对不起,还没有测试:-)而在同样的10分钟她(尽管是一个非常熟练的网络搜索者:-)无法在网络上找到现有的解决方案.而且,这样我就不用担心版权问题,如果我想在这里发布,将它包含在下一个Nutshell中,在OSCON或Pycon上提供它,等等...... :-)

  • @ user2864740,我肯定可以添加进一步的指令 - 我应该**正在编写Nutshell的第3版,但****在这里发布会更有趣:-).但我认为我会妥协并且大多只是添加相关的优秀在线文档的链接,而不是复制和过去它们或者解释它们:-) (2认同)