【问题标题】:How do I elegantly/efficiently write the __init__ function for a Python class that takes lots of instance variables?如何优雅/高效地为需要大量实例变量的 Python 类编写 __init__ 函数?
【发布时间】:2015-01-17 21:10:32
【问题描述】:

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

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

你将如何在__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

...但是这需要大量的打字!我怎样才能让__init__ 自动一些它需要的参数,注意其他参数可能不需要分配为实例变量?

【问题讨论】:

  • 如果你的类需要一大堆位置参数,它可能表明你的类做的太多了,你可能想把它分解成组件。另一方面,一堆关键字参数是另一回事。
  • @jme 让我们假设它们是一堆关键字参数(我会在我的帖子中添加说明),因为当我想要一个类来模拟一些科学的东西时,这种情况真的出现了只是有很多初始条件可以定义
  • 你说的是实例变量,不是类变量,对吧?
  • @Roberto 所以我进一步研究了你所说的,你是对的,我已经做出了改变

标签: python class


【解决方案1】:

几年后,Python 3.7 引入了dataclass,它允许您像这样定义一个类:

from dataclasses import dataclass

@dataclass
class ManyInitVariables:
    a: int = 0
    b: int = 2
    ...
    z: 'typing.Any' = 9

这将自动设置实例属性。

还有make_dataclass:

from dataclasses import make_dataclass, field

attributes = [('a', int, 0), ('b', int, 2), ..., ('z', 'typing.Any', 9)]

ManyInitVariables = make_dataclass(
    'ManyInitVariables', 
    [(attr_name, attr_type, field(default=attr_default)) 
     for attr_name, attr_type, attr_default in attributes])

【讨论】:

  • 我一直在关注数据类库,因为它在 3.7 中首次被引入到 stdlib 时。这是一个很棒的图书馆,似乎非常适合上面的问题。
【解决方案2】:

我确信对于这个非常常见的问题,网络上还有许多其他类似的解决方案,但这是一个,例如:

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

补充:有人要求我进一步解释这一点,但是,如果你不熟悉 Python,这里还有很多事情要做!-)。

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

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

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

顺便说一句,如果你只关心在__init__ 中使用它(正如你在下面的示例中看到的,absorb_attrs 本身没有这样的约束),那么编写一个 class 装饰器来单打取出类的__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)

会打印

a: fee, b: fie, bab: bo, bip: beeeeeep, c: foo, d: feefie, t: 3, u: 23, w: 99, x: 1, y: 2, z: 3

根据需要。

我以这种方式“重新发明轮子”(而不是在网上搜索解决方案)的唯一借口是,那天晚上我的妻子和合著者安娜(弗兰克·威利森纪念奖的唯一女性获奖者) Python 社区,顺便说一句 :-) 向我询问了这个问题(我们正在慢慢地写着“Python in a Nutshell”的第 3 版)——我花了 10 分钟来编写这个代码(抱歉,还没有测试:- ) 而在同样的 10 分钟内,她(尽管她是一个非常熟练的网络搜索者:-) 无法在网络上找到现有的解决方案。而且,这样我就不必担心版权问题,如果我想在这里发布它,将它包含在下一个 Nutshell 中,在 OSCON 或 Pycon 上展示它,等等......:-)

【讨论】:

  • @user2864740 我同意。亚历克斯,我能否对您的解决方案进行更多解释(也许像 cmets 之类的东西),只是为了让您的答案更加“独立”。目前,我想,我必须去谷歌搜索你在做什么(例如,为什么要使用 functools?)但也许这不是一件坏事......我不确定。
  • @user89,不确定——人们可能讨厌在 Python 中制作相当于 Ruby 的 attr_reader 的技术概念,或者他们可能讨厌我添加的个人和轶事信息。
  • @user2864740 ,我当然可以添加进一步的说明 -- 我应该改为编写 Nutshell 的第 3 版,但它更有趣在此处发布:-)。但我认为我会妥协,主要是添加指向相关的优秀在线文档的链接,而不是复制并粘贴它们或解释它们:-)
  • @user2864740,大编辑刚刚完成,添加了措辞和链接,享受!我“围绕编程”的时间比我敢打赌大多数读者“围绕”的时间要长:-),尽管我的职业生涯始于集成电路和硬件系统设计师,而且我几乎完全是自学成才的编程。好的网络搜索引擎,以及后来快速购买和下载电子书的能力,让我很容易找到我难以置信的事情......在我的时代,我们不得不走上山去图书馆,然后上山回家,在雪地里……!-)
  • @AlexMartelli 感谢您花时间进行更新!我错过了雪地和山丘(哇!),但我确实必须在启动时转动 软盘 磁盘;-)
猜你喜欢
  • 1970-01-01
  • 2020-08-05
  • 2017-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多