我如何优雅/高效地为需要许多实例变量的Python类编写__init__函数? [英] How do I elegantly/efficiently write the __init__ function for a Python class that takes lots of instance variables?

查看:249
本文介绍了我如何优雅/高效地为需要许多实例变量的Python类编写__init__函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设你有一个类,它需要很多(关键字)参数,其中大部分是作为实例变量存储的:

  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 __ 自动所需的一些参数,注意其他参数可能不需要分配为实例变量? p>

解决方案

我确定有很多其他类似的解决方案在网络上这个非常常见的问题,但这是一个,示例:

  import functools 
import inspect

def absorb_args(f):
args,_,_,defs = inspect.getargspec(f)
args = args [1:]#忽略前导'self'
@ functools.wraps(f)
def do_absorb(self,* a,** k):
ndefs = len(args) - len(a)+ 2
名称,zip中的值(args,a + defs [-ndefs:] ):
setattr(self,name,value)
name,k.items()中的值:
setattr(self,name,value)
return f * a,** k)
return do_absorb

进一步解释这一点,但如果你不熟悉Python,还有很多事情。)



functools.wraps 是一个装饰师,可以帮助更好的装饰,请参阅 https: //docs.python.org/2/library/functools.html#functools.wraps - 不直接与问题密切相关,但有助于支持交互式帮助和基于函数docstrings的工具。在编写函数装饰器(最常见的情况)包装装饰函数时,应该使用总是的习惯,你不会后悔。



inspect 模块是在现代Python中进行内省的唯一正确方法。 inspect.getargspec 特别给你提供了函数接受什么参数的信息,以及它们的默认值,如果有的话(我忽略的两个信息位,通过将它们分配给 _ ,约 * a ** k 特殊args,这个装饰器不支持)。请参见 https://docs.python.org/2/



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



顺便提一句,如果你只关心在 __ init __ 中使用它(如下例所示, absorb_attrs 本身没有这样的约束),然后写一个装饰器,它为这个类的 __ init __



此外,如果你的类的 __ init __ 没有剩余的工作一旦args以这种方式被吸收,你必须仍然定义(decorated) __ init __ ,但它的主体可以被限制为docstring解释参数(我个人更喜欢在这种情况下总是有一个 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
return','.join(r)

s =斗争('费用','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 
pre>



我这样做的唯一借口是重新发明轮子 web的解决方案)是,另一个晚上我的妻子和合着者安娜(只有弗兰克Willison纪念奖的贡献的Python社区,BTW :-)问我的问题(我们,慢慢唉! ,编写第三版Python in a Nutshell) - 我花了我10分钟来编码(对不起,还没有测试:-),而在同一10分钟她(尽管是一个非常熟练的网络搜索: )无法在Web上找到现有解决方案。而且,这种方式我不需要担心版权问题,如果我想在这里发布,包括它在下一个Nutshell,介绍它在OSCON或Pycon,等等...: - )


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

解决方案

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!-).

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. 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.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). See https://docs.python.org/2/library/inspect.html?highlight=getargspec#inspect.getargspec for more.

self, by convention, is always the first arg to a method (and this decorator is meant for methods only:-). So, the first for loop deals with positional args (whether explicitly given in the call or defaulting to default values); then, the second for loop deals with named args (that one, I hope, is simpler to grasp:-). 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.

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.

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).

And now, back to the original answer, with an example...!-)

And then, e.g, 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. 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...:-)

这篇关于我如何优雅/高效地为需要许多实例变量的Python类编写__init__函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆