自动初始化实例变量?

我有一个 Python 类,看起来像这样:

class Process:
def __init__(self, PID, PPID, cmd, FDs, reachable, user):

其次是:

        self.PID=PID
self.PPID=PPID
self.cmd=cmd
...

有没有什么方法可以自动初始化这些实例变量,比如 C + + 的初始化列表。

50077 次浏览

你可以很容易地使用关键字参数,例如:

>>> class D:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)


>>> D(test='d').test
'd'

位置参数的类似实现如下:

>> class C:
def __init__(self, *args):
self.t, self.d = args




>>> C('abc', 'def').t
'abc'
>>> C('abc', 'def').d
'def'

在我看来,这并不能解决你的问题。

You can use a decorator:

from functools import wraps
import inspect


def initializer(func):
"""
Automatically assigns the parameters.


>>> class process:
...     @initializer
...     def __init__(self, cmd, reachable=False, user='root'):
...         pass
>>> p = process('halt', True)
>>> p.cmd, p.reachable, p.user
('halt', True, 'root')
"""
names, varargs, keywords, defaults = inspect.getargspec(func)


@wraps(func)
def wrapper(self, *args, **kargs):
for name, arg in list(zip(names[1:], args)) + list(kargs.items()):
setattr(self, name, arg)


for name, default in zip(reversed(names), reversed(defaults)):
if not hasattr(self, name):
setattr(self, name, default)


func(self, *args, **kargs)


return wrapper

用它来装饰 __init__方法:

class process:
@initializer
def __init__(self, PID, PPID, cmd, FDs, reachable, user):
pass

产出:

>>> c = process(1, 2, 3, 4, 5, 6)
>>> c.PID
1
>>> dir(c)
['FDs', 'PID', 'PPID', '__doc__', '__init__', '__module__', 'cmd', 'reachable', 'user'

引用 Zen of Python的话,

显式比隐式好。

如果使用 Python 2.6或更高版本,可以使用 Collections.namedtuple:

>>> from collections import namedtuple
>>> Process = namedtuple('Process', 'PID PPID cmd')
>>> proc = Process(1, 2, 3)
>>> proc.PID
1
>>> proc.PPID
2

This is appropriate especially when your class is really just a big bag of values.

还有一件事你可以做:

class X(object):
def __init__(self, a,b,c,d):
vars = locals() # dict of local names
self.__dict__.update(vars) # __dict__ holds and object's attributes
del self.__dict__["self"] # don't need `self`

But the only solution I would recommend, besides just spelling it out, is "make a macro in your editor" ;-p

娜迪亚的解决方案更好、更有效,但我认为这也很有趣:

def constructor(*arg_names):
def __init__(self, *args):
for name, val in zip(arg_names, args):
self.__setattr__(name, val)
return __init__




class MyClass(object):
__init__ = constructor("var1", "var2", "var3")




>>> c = MyClass("fish", "cheese", "beans")
>>> c.var2
"cheese"

Nu11ptr 已经制作了一个小模块 PyInstanceVars,它包含了这个作为函数修饰符的功能。在模块的 README 中指出“ [ ... ]性能现在只比 CPython 下的显式初始化差30-40% ”。

Usage example, lifted straight from the module's documentation:

>>> from instancevars import *
>>> class TestMe(object):
...     @instancevars(omit=['arg2_'])
...     def __init__(self, _arg1, arg2_, arg3='test'):
...             self.arg2 = arg2_ + 1
...
>>> testme = TestMe(1, 2)
>>> testme._arg1
1
>>> testme.arg2_
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'TestMe' object has no attribute 'arg2_'
>>> testme.arg2
3
>>> testme.arg3
'test'

可能不需要初始化变量,因为局部变量()已经包含了这些值!

类虚拟(对象) :

def __init__(self, a, b, default='Fred'):
self.params = {k:v for k,v in locals().items() if k != 'self'}

d = Dummy(2, 3)

d.params

{‘ a’: 2,‘ b’: 3,‘ default’: ‘ Fred’}

D.params [‘ b’]

3

当然,在类中可以使用 self. params

也许这是一个封闭的问题,但我想提出我的解决方案,以便了解你的想法。我使用了一个元类,它将一个装饰器应用于 init方法

import inspect


class AutoInit(type):
def __new__(meta, classname, supers, classdict):
classdict['__init__'] = wrapper(classdict['__init__'])
return type.__new__(meta, classname, supers, classdict)


def wrapper(old_init):
def autoinit(*args):
formals = inspect.getfullargspec(old_init).args
for name, value in zip(formals[1:], args[1:]):
setattr(args[0], name, value)
return autoinit

自 Python 3.5以来,一旦 getargspec被弃用,下面是使用 inspect.signature的解决方案:

from inspect import signature, Parameter
import functools




def auto_assign(func):
# Signature:
sig = signature(func)
for name, param in sig.parameters.items():
if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
raise RuntimeError('Unable to auto assign if *args or **kwargs in signature.')
# Wrapper:
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
for i, (name, param) in enumerate(sig.parameters.items()):
# Skip 'self' param:
if i == 0: continue
# Search value in args, kwargs or defaults:
if i - 1 < len(args):
val = args[i - 1]
elif name in kwargs:
val = kwargs[name]
else:
val = param.default
setattr(self, name, val)
func(self, *args, **kwargs)
return wrapper

检查是否有效:

class Foo(object):
@auto_assign
def __init__(self, a, b, c=None, d=None, e=3):
pass


f = Foo(1, 2, d="a")
assert f.a == 1
assert f.b == 2
assert f.c is None
assert f.d == "a"
assert f.e == 3


print("Ok")

我需要一些东西来达到同样的目的,但是没有一个现有的答案涵盖了我测试的所有案例。Nadia 的答案最接近我要找的,所以我以她的代码为基础。

下面的修饰符可以处理所有有效的参数组合:

Positional                                          __init__(self, a, b                )
Keyword                                             __init__(self, a=None, b=None      )
Positional + Keyword                                __init__(self, a, b, c=None, d=None)
Variable Positional                                 __init__(self, *a                  )
Variable Positional + Keyword                       __init__(self, *a, b=None          )
Variable Positional + Variable Keyword              __init__(self, *a, **kwargs        )
Positional + Variable Positional + Keyword          __init__(self, a, *b, c=None       )
Positional + Variable Positional + Variable Keyword __init__(self, a, *b, **kwargs     )
Keyword Only                                        __init__(self, *, a=None           )
Positional + Keyword Only                           __init__(self, a, *, b=None        )

它还实现了标准的 _-前缀约定,以便允许不分配给类实例的 __init__-私有变量。


###  StdLib  ###
from functools import wraps
import inspect




###########################################################################################################################
#//////|   Decorator   |//////////////////////////////////////////////////////////////////////////////////////////////////#
###########################################################################################################################


def auto_assign_arguments(function):


@wraps(function)
def wrapped(self, *args, **kwargs):
_assign_args(self, list(args), kwargs, function)
function(self, *args, **kwargs)


return wrapped




###########################################################################################################################
#//////|   Utils   |//////////////////////////////////////////////////////////////////////////////////////////////////////#
###########################################################################################################################


def _assign_args(instance, args, kwargs, function):


def set_attribute(instance, parameter, default_arg):
if not(parameter.startswith("_")):
setattr(instance, parameter, default_arg)


def assign_keyword_defaults(parameters, defaults):
for parameter, default_arg in zip(reversed(parameters), reversed(defaults)):
set_attribute(instance, parameter, default_arg)


def assign_positional_args(parameters, args):
for parameter, arg in zip(parameters, args.copy()):
set_attribute(instance, parameter, arg)
args.remove(arg)


def assign_keyword_args(kwargs):
for parameter, arg in kwargs.items():
set_attribute(instance, parameter, arg)
def assign_keyword_only_defaults(defaults):
return assign_keyword_args(defaults)


def assign_variable_args(parameter, args):
set_attribute(instance, parameter, args)


POSITIONAL_PARAMS, VARIABLE_PARAM, _, KEYWORD_DEFAULTS, _, KEYWORD_ONLY_DEFAULTS, _ = inspect.getfullargspec(function)
POSITIONAL_PARAMS = POSITIONAL_PARAMS[1:] # remove 'self'


if(KEYWORD_DEFAULTS     ): assign_keyword_defaults     (parameters=POSITIONAL_PARAMS,  defaults=KEYWORD_DEFAULTS)
if(KEYWORD_ONLY_DEFAULTS): assign_keyword_only_defaults(defaults=KEYWORD_ONLY_DEFAULTS                          )
if(args                 ): assign_positional_args      (parameters=POSITIONAL_PARAMS,  args=args                )
if(kwargs               ): assign_keyword_args         (kwargs=kwargs                                           )
if(VARIABLE_PARAM       ): assign_variable_args        (parameter=VARIABLE_PARAM,      args=args                )




###########################################################################################################################$#//////|   Tests   |//////////////////////////////////////////////////////////////////////////////////////////////////////#$###########################################################################################################################$$if __name__ == "__main__":$$#######|   Positional   |##################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b):$      pass$$  t = T(1, 2)$  assert (t.a == 1) and (t.b == 2)$$#######|   Keyword   |#####################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a="KW_DEFAULT_1", b="KW_DEFAULT_2"):$      pass$$  t = T(a="kw_arg_1", b="kw_arg_2")$  assert (t.a == "kw_arg_1") and (t.b == "kw_arg_2")$$#######|   Positional + Keyword   |########################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b, c="KW_DEFAULT_1", d="KW_DEFAULT_2"):$      pass$$  t = T(1, 2)$  assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "KW_DEFAULT_2")$$  t = T(1, 2, c="kw_arg_1")$  assert (t.a == 1) and (t.b == 2) and (t.c == "kw_arg_1") and (t.d == "KW_DEFAULT_2")$$  t = T(1, 2, d="kw_arg_2")$  assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "kw_arg_2")$$#######|   Variable Positional   |#########################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a):$      pass$$  t = T(1, 2, 3)$  assert (t.a == [1, 2, 3])$$#######|   Variable Positional + Keyword   |###############################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a, b="KW_DEFAULT_1"):$      pass$$  t = T(1, 2, 3)$  assert (t.a == [1, 2, 3]) and (t.b == "KW_DEFAULT_1")$$  t = T(1, 2, 3, b="kw_arg_1")$  assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1")$$#######|   Variable Positional + Variable Keyword   |######################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a, **kwargs):$      pass$$  t = T(1, 2, 3, b="kw_arg_1", c="kw_arg_2")$  assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1") and (t.c == "kw_arg_2")$$#######|   Positional + Variable Positional + Keyword   |##################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *b, c="KW_DEFAULT_1"):$      pass$$  t = T(1, 2, 3, c="kw_arg_1")$  assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1")$$#######|   Positional + Variable Positional + Variable Keyword   |#########################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *b, **kwargs):$      pass$$  t = T(1, 2, 3, c="kw_arg_1", d="kw_arg_2")$  assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1") and (t.d == "kw_arg_2")$$#######|   Keyword Only   |################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *, a="KW_DEFAULT_1"):$      pass$$  t = T(a="kw_arg_1")$  assert (t.a == "kw_arg_1")$$#######|   Positional + Keyword Only   |###################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *, b="KW_DEFAULT_1"):$      pass$$  t = T(1)$  assert (t.a == 1) and (t.b == "KW_DEFAULT_1")$$  t = T(1, b="kw_arg_1")$  assert (t.a == 1) and (t.b == "kw_arg_1")$$#######|   Private __init__ Variables (underscored)   |####################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b, _c):$      pass$$  t = T(1, 2, 3)$  assert hasattr(t, "a") and hasattr(t, "b") and no
t(hasattr(t, "_c"))

注:

我包含了测试,但为了简短起见,将它们折叠到最后一行(58)中。您可以通过使用换行符对所有 $字符进行 find/replace处理来扩展测试,这些测试详细说明了所有潜在的用例。

吸引库执行类似的操作。

For Python 3.3+:

from functools import wraps
from inspect import Parameter, signature




def instance_variables(f):
sig = signature(f)
@wraps(f)
def wrapper(self, *args, **kwargs):
values = sig.bind(self, *args, **kwargs)
for k, p in sig.parameters.items():
if k != 'self':
if k in values.arguments:
val = values.arguments[k]
if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
setattr(self, k, val)
elif p.kind == Parameter.VAR_KEYWORD:
for k, v in values.arguments[k].items():
setattr(self, k, v)
else:
setattr(self, k, p.default)
return wrapper


class Point(object):
@instance_variables
def __init__(self, x, y, z=1, *, m='meh', **kwargs):
pass

演示:

>>> p = Point('foo', 'bar', r=100, u=200)
>>> p.x, p.y, p.z, p.m, p.r, p.u
('foo', 'bar', 1, 'meh', 100, 200)

Python 2和 Python 3使用框架的非装饰方法:

import inspect




def populate_self(self):
frame = inspect.getouterframes(inspect.currentframe())[1][0]
for k, v in frame.f_locals.items():
if k != 'self':
setattr(self, k, v)




class Point(object):
def __init__(self, x, y):
populate_self(self)

演示:

>>> p = Point('foo', 'bar')
>>> p.x
'foo'
>>> p.y
'bar'

对于 Python 3.7 + ,您可以使用 数据类,这是一种非常 Python 化和可维护的方式来完成您想要的任务。

It allows you to define 田野 for your class, which are your automatically initialized instance variables.

看起来是这样的:

@dataclass
class Process:
PID: int
PPID: int
cmd: str
...

__init__方法将已经在您的类中。

注意这里的 需要类型提示,这就是我在示例中使用 intstr的原因。如果不知道字段的类型,可以使用 任何来自 typing模块的

与拟议的解决方案相比,数据类有许多优点:

  • 它是 露骨: 所有字段都是可见的,这尊重了 Python 的禅意,使其具有可读性和可维护性。将其与 **kwargs的使用进行比较。
  • 它可以有 方法。就像任何其他类一样。
  • 它允许您使用 __post_init__方法超越自动 __init__

编辑: 避免使用 NamedTuple 的原因

有人建议在这种情况下使用 namedtuple,但是 namedtuple 有一些不同于 Python 类的行为,这些行为一开始并不明显,应该是众所周知的:

1. NamedTuples are immutable

不可变性可能是有用的,但是它可能不是您想要的实例。如果在 @dataclass装饰器上使用参数 frozen=True,DataClass 在某种程度上也是不可变的。

2. NamedTuples __eq__ behaves like Tuple's

在 Python 中,SomeNamedTuple(a=1, b=2) == AnotherNamedTuple(c=1, d=2)就是 True!比较中使用的 NamedTuple 的 __eq__函数只考虑被比较实例上这些值的值和位置,而不考虑它们的类或字段的名称。

Init函数结束时:

for var in list(locals().keys()):
setattr(self,var,locals()[var])

有关 setattr()的详情,请参阅 给你

在 fastcore 库 https://fastcore.fast.ai/utils.html#store_attr中有一个 helper 函数来完成这项工作。

from fastcore.utils import store_attr


class Process:
def __init__(self, PID, PPID, cmd, FDs, reachable, user):
store_attr() # this will do the same as self.PID = PID etc.

这里有一个简单的解决方案,如果你有固定的输入集,你可以这样做:

from inspect import getargvalues, stack


def arguments():
args = getargvalues(stack()[1][0])[-1]
del args['self']
if 'kwargs' in args:
args.update(args['kwargs'])
del args['kwargs']
return args


class Process():
def __init__(self, PID, PPID, cmd, FDs, reachable, user):
# Auto update all arguments into object dictionary
self.__dict__.update(arguments())

如果没有指定 * kwargs,那么应该可以做到这一点

object = Process(1,2,3,'foo','random'...)
# all the right instances will be created
# object.PID =1
# object.PPID = 2