问题
I have a python class that looks like this:
class Process:
def __init__(self, PID, PPID, cmd, FDs, reachable, user):
followed by:
self.PID=PID
self.PPID=PPID
self.cmd=cmd
...
Is there any way to autoinitialize these instance variables, like C++\'s initialization list? It would spare lots of redundant code.
回答1:
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
Use it to decorate the __init__
method:
class process:
@initializer
def __init__(self, PID, PPID, cmd, FDs, reachable, user):
pass
Output:
>>> c = process(1, 2, 3, 4, 5, 6)
>>> c.PID
1
>>> dir(c)
['FDs', 'PID', 'PPID', '__doc__', '__init__', '__module__', 'cmd', 'reachable', 'user'
回答2:
If you're using Python 2.6 or higher, you can use 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.
回答3:
For Python 3.7+ you can use a Data Class, which is a very pythonic and maintainable way to do what you want.
It allows you to define fields for your class, which are your automatically initialized instance variables.
It would look something like that:
@dataclass
class Process:
PID: int
PPID: int
cmd: str
...
The __init__
method will already be in your class.
Note that here type hinting is required, that is why I have used int
and str
in the example. If you don't know the type of your field, you can use Any from the typing module.
The Data Class has many advantages compared to the proposed solutions:
- It is explicit: all fields are visible, which respects the Zen of Python and makes it readable and maintainable. Compare it to the use of
**kwargs
. - It can have methods. Just like any other class.
- It allows you to go beyond the automatic
__init__
using the __post_init__ method.
回答4:
Quoting the Zen of Python,
Explicit is better than implicit.
回答5:
Another thing you can do:
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
回答6:
You could do it easily with the keyword arguments, e.g. like this:
>>> class D:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
>>> D(test='d').test
'd'
similar implementation for the positional arguments would be:
>> class C:
def __init__(self, *args):
self.t, self.d = args
>>> C('abc', 'def').t
'abc'
>>> C('abc', 'def').d
'def'
which to me doesn't seem to solve your problem.
回答7:
Nadia's solution is better and more powerful, but I think this is also interesting:
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"
回答8:
I needed something for the same purpose, but none of the existing answers covered all of the cases I tested. Nadia's answer was the closest to what I was looking for, so I started with her code as a base.
The decorator below works with all valid combinations of arguments:
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 )
It also implements the standard _
-prefix convention to allow for __init__
-private variables that won't be assigned to class instances.
### 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 not(hasattr(t, "_c"))
Note:
I included tests, but collapsed them into the last line (58) for brevity. You can expand the tests, which detail all of the potential use cases, by find/replace
-ing all $
characters with a newline.
回答9:
There may not be a need to initialize variables, as locals() already contains the values!
class Dummy(object):
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
Of course, within a class one could use self.params
回答10:
As soon as getargspec
is deprecated since Python 3.5, here's solution using 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
Check if works:
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")
回答11:
nu11ptr has made a small module, PyInstanceVars, which includes this functionality as a function decorator. In the module's README is states that the "[...] performance is now only 30-40% worse than explicit initialization under CPython".
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'
回答12:
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
Demo:
>>> 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)
A non-decorator approach for both Python 2 and 3 using frames:
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)
Demo:
>>> p = Point('foo', 'bar')
>>> p.x
'foo'
>>> p.y
'bar'
回答13:
Maybe this is a closed question, but I would like to propose my solution in order to know what you think about it. I have used a metaclass which applies a decorator to init method
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
回答14:
The attrs library does something like this.
来源:https://stackoverflow.com/questions/1389180/automatically-initialize-instance-variables