[英]How do I elegantly/efficiently write the __init__ function for a Python class that takes lots of instance variables?
Let's say you have a class that takes many (keyword) arguments, most of which are meant to be stored as instance variables:假设您有一个接受许多(关键字)参数的类,其中大部分是作为实例变量存储的:
class ManyInitVariables():
def __init__(a=0, b=2, c=1, d=0, e=-1, ... , x=100, y=0, z=9):
How would you initialize them in __init__
?你将如何在
__init__
初始化它们? You could do something like this:你可以这样做:
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
...but it would take a lot of typing! ......但需要大量打字! How could I get
__init__
to automatically some of the arguments it takes, noting that other arguments may not need to be assigned as instance variables?我怎样才能让
__init__
自动获取它需要的一些参数,注意其他参数可能不需要作为实例变量分配?
I'm sure there are many other similar solutions out there on the web for this very common issue, but this is one, for example: 我确信网上有很多其他类似的解决方案可以解决这个非常常见的问题,但这是一个例子:
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
Added: I've been asked to explain this further, but, there's a lot going on here if you're not skilled at Python!-). 补充:我被要求进一步解释这一点,但是,如果你不擅长Python,那么这里有很多东西! - )。
functools.wraps
is a decorator to help make better decorators, see https://docs.python.org/2/library/functools.html#functools.wraps -- not directly germane to the question but useful to support interactive help
and tools based on functions' docstrings. functools.wraps
是一个帮助制作更好的装饰器的装饰器,请参阅https://docs.python.org/2/library/functools.html#functools.wraps - 与问题没有直接关系,但对支持交互式help
和工具很有用基于函数的docstrings。 Get into the habit of always using it when writing a function decorator that (the most common case) wraps the decorated function, and you won't regret it. 养成在编写函数装饰器时总是使用它的习惯(最常见的情况)包装装饰函数,你不会后悔。
The inspect
module is the only right way to do introspection in modern Python. inspect
模块是在现代Python中进行内省的唯一正确方法。 inspect.getargspec
in particular gives you information on what arguments a function accepts, and what the default values for them are, if any (the two bits of info I'm ignoring, by assigning them to _
, are about *a
and **k
special args, which this decorator doesn't support). inspect.getargspec
特别为您提供了函数接受什么参数的信息,以及它们的默认值(如果有的话)(我忽略的两个信息,通过将它们分配给_
,大约是*a
和**k
特殊的args,这个装饰者不支持)。 See https://docs.python.org/2/library/inspect.html?highlight=getargspec#inspect.getargspec for more. 有关详细信息,请参阅https://docs.python.org/2/library/inspect.html?highlight=getargspec#inspect.getargspec 。
self
, by convention, is always the first arg to a method (and this decorator is meant for methods only:-). 按照惯例,
self
始终是方法的第一个arg(而这个装饰器仅用于方法:-)。 So, the first for
loop deals with positional args (whether explicitly given in the call or defaulting to default values); 因此,第一个
for
循环处理位置args(无论是在调用中明确给出还是默认为默认值); then, the second for
loop deals with named args (that one, I hope, is simpler to grasp:-). 然后,第二个
for
循环处理命名的args(我希望,这个更容易掌握:-)。 setattr
of course is the precious built-in function which sets an attribute with a variable name, https://docs.python.org/2/library/functions.html?highlight=setattr#setattr for more. setattr
当然是一个珍贵的内置函数,它设置一个带有变量名的属性, https: setattr
for more。
Incidentally, if you only care to use this in __init__
(as you see in the example below, absorb_attrs
per se has no such constraint), then write a class decorator which singles out the class's __init__
for this treatment, and apply that class decorator to the class itself. 顺便提一下,如果你只关心在
__init__
使用它(正如你在下面的例子中看到的那样, absorb_attrs
本身没有这样的约束),那么写一个类装饰器,为这个处理单挑出类的__init__
,并将该类装饰器应用于班级本身。
Also, if your class's __init__
has no work left to do once args are "absorbed" in this way, you must still define the (decorated) __init__
, but its body can be limited to a docstring explaining the arguments (I personally prefer to always also have a pass
statement in such cases, but that's a personal style issue). 另外,如果你的类的
__init__
没有工作要做,一旦args以这种方式被“吸收”,你仍然必须定义(装饰的) __init__
,但它的主体可以限制为解释参数的文档字符串(我个人更喜欢总是在这种情况下也有pass
声明,但这是个人风格问题)。
And now, back to the original answer, with an example...!-) 现在,回到最初的答案,举个例子......! - )
And then, eg, something like 然后,例如,像
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)
would print 会打印
a: fee, b: fie, bab: bo, bip: beeeeeep, c: foo, d: feefie, t: 3, u: 23, w: 99, x: 1, y: 2, z: 3
as desired. 如预期的。
My only excuse for "reinventing the wheel" this way (rather than scouring the web for a solution) is that the other evening my wife and co-author Anna (only ever woman winner of the Frank Willison Memorial Award for contribution to the Python community, BTW:-) asked me about it (we are, slowly alas!, writing the 3rd edition of "Python in a Nutshell") -- it took me 10 minutes to code this (sorry, no tests yet:-) while in the same 10 minutes she (despite being a very skilled web-searcher:-) could not locate an existing solution on the web. 我唯一能够以这种方式“重新发明轮子”(而不是在网上寻找解决方案)的借口是另一个晚上我的妻子和共同作者安娜(只有弗兰克威利森纪念奖的女性获奖者才能为Python社区做出贡献) ,BTW :-)问我这件事(我们是,慢慢地唉!,写第三版“Python in a Nutshell”) - 我花了10分钟来编写代码(对不起,还没有测试:-)而在同样的10分钟她(尽管是一个非常熟练的网络搜索者:-)无法在网络上找到现有的解决方案。 And, this way I need not worry about copyright issues if I want to post it here, include it in the next Nutshell, present about it at OSCON or Pycon, and so forth...:-)
而且,这样我就不用担心版权问题,如果我想在这里发布,将它包含在下一个Nutshell中,在OSCON或Pycon上提供它,等等...... :-)
A couple of years later, Python 3.7 introduced the dataclass , which allows you to define a class like this:几年后,Python 3.7 引入了dataclass ,它允许您像这样定义一个类:
from dataclasses import dataclass
@dataclass
class ManyInitVariables:
a: int = 0
b: int = 2
...
z: 'typing.Any' = 9
This will automatically take care of setting the instance attributes.这将自动处理设置实例属性。
There's also make_dataclass :还有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])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.