我如何 elegantly/efficiently 为需要大量实例变量的 Python class 编写 __init__ 函数?
How do I elegantly/efficiently write the __init__ function for a Python class that takes lots of instance variables?
假设您有一个 class 接受许多(关键字)参数,其中大部分要存储为实例变量:
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__
自动 一些 它需要的参数,注意其他参数可能不需要分配为实例变量?
我敢肯定,对于这个非常常见的问题,网上还有许多其他类似的解决方案,但这是一个,例如:
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 装饰器选择 class 的 __init__
进行此处理,并将该 class 装饰器应用于 class 本身。
此外,如果您的 class 的 __init__
在 args 以这种方式 "absorbed" 后没有工作要做,您仍然必须定义(修饰的)__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
随心所欲。
我以这种方式 "reinventing the wheel" 唯一的借口(而不是在网上搜索解决方案)是那天晚上我的妻子和合著者安娜(唯一一位获得弗兰克·威利森纪念奖的女性)对 Python 社区的贡献,顺便说一句:-) 问我这件事(我们正在慢慢写 "Python in a Nutshell" 的第 3 版)——我花了 10 分钟来编写这个代码(抱歉,还没有测试:-) 而在同样的 10 分钟内,她(尽管她是一个非常熟练的网络搜索者:-) 无法在网络上找到现有的解决方案。而且,如果我想在这里 post 它,将它包含在下一个 Nutshell 中,在 OSCON 或 Pycon 上展示它等等,这样我就不必担心版权问题...:-)
几年后,Python 3.7 引入了 dataclass,它允许您像这样定义 class:
from dataclasses import dataclass
@dataclass
class ManyInitVariables:
a: int = 0
b: int = 2
...
z: 'typing.Any' = 9
这将自动负责设置实例属性。
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])
假设您有一个 class 接受许多(关键字)参数,其中大部分要存储为实例变量:
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__
自动 一些 它需要的参数,注意其他参数可能不需要分配为实例变量?
我敢肯定,对于这个非常常见的问题,网上还有许多其他类似的解决方案,但这是一个,例如:
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 装饰器选择 class 的 __init__
进行此处理,并将该 class 装饰器应用于 class 本身。
此外,如果您的 class 的 __init__
在 args 以这种方式 "absorbed" 后没有工作要做,您仍然必须定义(修饰的)__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
随心所欲。
我以这种方式 "reinventing the wheel" 唯一的借口(而不是在网上搜索解决方案)是那天晚上我的妻子和合著者安娜(唯一一位获得弗兰克·威利森纪念奖的女性)对 Python 社区的贡献,顺便说一句:-) 问我这件事(我们正在慢慢写 "Python in a Nutshell" 的第 3 版)——我花了 10 分钟来编写这个代码(抱歉,还没有测试:-) 而在同样的 10 分钟内,她(尽管她是一个非常熟练的网络搜索者:-) 无法在网络上找到现有的解决方案。而且,如果我想在这里 post 它,将它包含在下一个 Nutshell 中,在 OSCON 或 Pycon 上展示它等等,这样我就不必担心版权问题...:-)
几年后,Python 3.7 引入了 dataclass,它允许您像这样定义 class:
from dataclasses import dataclass
@dataclass
class ManyInitVariables:
a: int = 0
b: int = 2
...
z: 'typing.Any' = 9
这将自动负责设置实例属性。
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])