121

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Adam Matan
  • 128,757
  • 147
  • 397
  • 562
  • 1
    See also discussion of the `autoassign` activestate recipe, and an alternate `autoargs` implementation at: [What is the best way to do automatic attribute assignment in Python, and is it a good idea? - Stack Overflow](http://stackoverflow.com/questions/3652851/what-is-the-best-way-to-do-automatic-attribute-assignment-in-python-and-is-it-a) – nealmcb Jan 14 '17 at 17:26

17 Answers17

125

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'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Nadia Alramli
  • 111,714
  • 37
  • 173
  • 152
  • 1
    minor nitpick - you forgot to import inspect – John Montgomery Sep 07 '09 at 12:46
  • 7
    This works and answer the question so I voted up. But I kept Ferdidand Beyer answer: "Explicit is better than implicit" – Lucas Gabriel Sánchez Sep 07 '09 at 12:50
  • 19
    +1 For great answer that solved my problem. But shouldn't it be a core functionality of the language? Do you think it's worth writing a PEP? – Adam Matan Sep 07 '09 at 12:56
  • 1
    @Nadia nice, but inside the wrapper fun should also be called, in case its body is more than just 'pass' -- no downsides to that! So pls edit this good answer to make it complete. – Alex Martelli Sep 07 '09 at 16:16
  • @Udi, a PEP can't hurt, but you'll get lots of push-back (see e.g. Ferdinand Beyer's answer below). Then again, these days you ALWAYS get lots of push-back for ANY pep at all;-). – Alex Martelli Sep 07 '09 at 16:18
  • 3
    This is a really good answer - this has gone straight into my toolbox. – Michael van der Westhuizen Sep 07 '09 at 18:40
  • 1
    Thanks for this, it was pretty much exactly what I was looking for. Note that it won't work if you use keyword arguments or use default values, though you can pretty easily modify it to handle that case. – Paul D. Jan 28 '10 at 16:47
  • 3
    @NadiaAlramli I think there is a small bug in the code. Look here https://gist.github.com/pmav99/137dbf4681be9a58de74 (original.py) – pmav99 Nov 14 '13 at 10:06
  • 3
    The current example has a bug, and will not work if the signature doesn't include default arguments. You need to include a check to make sure names and defaults are not None. Ex: if names and defaults: –  Dec 11 '14 at 13:42
  • getargspec is going away, any port to python 3.5+? – nbecker Nov 17 '16 at 19:33
  • @NadiaAlramli Nice implementation : ), just added an answer based on your code that provides a few more use cases. `getfullargspec` returns a bit more info that is useful for some of the less common parameter structures. – Enteleform May 13 '17 at 01:59
  • @nbecker Check out my answer. It uses `getfullargspec`, which is the suggested replacement for `getargspec` via [**3.6 documentation**](https://docs.python.org/3/library/inspect.html#inspect.getfullargspec). – Enteleform May 13 '17 at 02:05
  • In IDE like pycharm, such trick-way will make object.xxx of this class lossed its reference-origin(declaration) – kkkobelief24 Apr 30 '21 at 06:25
67

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.

EDIT: Reasons to avoid using NamedTuples

Some suggest the use of namedtuple for this case, but namedtuples have some behaviours that differs from Python classes, which are not really evident at first and should be well known:

1. NamedTuples are immutable

Immutability can be useful, but maybe it is not what you want for your instances. DataClasses can also be somehow immutable if you use the argument frozen=True on the @dataclass decorator.

2. NamedTuples __eq__ behaves like Tuple's

In Python, SomeNamedTuple(a=1, b=2) == AnotherNamedTuple(c=1, d=2) is True! The __eq__ function of NamedTuple, used in comparisons, only considers the values and the positions of those values on the compared instances, not their class or fields' names.

Jundiaius
  • 6,214
  • 3
  • 30
  • 43
37

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.

Kiv
  • 31,940
  • 6
  • 44
  • 59
  • 2
    "This is appropriate especially when your class is really just a big bag of values." In such a scenario, you could also do this: [https://docs.python.org/3.3/tutorial/classes.html#odds-and-ends](https://docs.python.org/3.3/tutorial/classes.html#odds-and-ends) – Big Sharpie Jul 31 '14 at 15:57
30

Quoting the Zen of Python,

Explicit is better than implicit.

Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145
  • 14
    Wouldn't an initialization list declaration be explicit enough? – Adam Matan Sep 07 '09 at 13:20
  • I guess. But I don't see a reason for adding something like that to the language. I clearly prefer multiple assignment statements over some kind of decorator-magic behind the scenes. – Ferdinand Beyer Sep 07 '09 at 13:51
  • 34
    @Ferdinand, I agree it would be silly to have in the language something that can perfectly well be in the stdlib, but, it SHOULD be in the stdlib, because "beautiful is better than ugly" takes precedence, and many repetitive assignments are ugly (as is any form of repetition). – Alex Martelli Sep 07 '09 at 16:20
  • Well, to counter: DWIM > DWIS – Terrence Brannon Feb 18 '13 at 11:43
  • I would agree decorator is more beautiful than a list of assignments but PyCharm makes it uglier by not understanding it :-( – user110954 Apr 28 '17 at 22:18
  • I agree. Decorating the function also makes _complex_ worse than _simple_, since you cannot make inplace modifications to self.attributes inside init. A solution would need to be done at some other level before `__init__` is called, probably using a metaclass. –  Sep 04 '18 at 09:34
30

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

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
15

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.

SilentGhost
  • 307,395
  • 66
  • 306
  • 293
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"
Andrew Magee
  • 6,506
  • 4
  • 35
  • 58
5

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.

Enteleform
  • 3,753
  • 1
  • 15
  • 30
3

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

  • It's a nice and original approach, but `d['b']` is written in Python's *lingua franca* while `d.params['b']` will cause confusion for code readers. – Adam Matan Jan 21 '14 at 09:06
3

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")
Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
2

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'
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
2

at the end of the init function:

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

For more on setattr() please refer here

Pratibha
  • 1,730
  • 7
  • 27
  • 46
LuWil
  • 46
  • 3
1

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'
benregn
  • 205
  • 4
  • 13
1

There is a helper function to do this in the fastcore lib https://fastcore.fast.ai/utils.html#store_attr.

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.
Alex
  • 355
  • 5
  • 11
0

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
delca85
  • 1,176
  • 2
  • 10
  • 17
0

The attrs library does something like this.

offby1
  • 6,767
  • 30
  • 45
0

Simple solution here if you have fixed sets of inputs, you can use this:

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

This should do it, if you don't specify *kwargs

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

Dazz W
  • 113
  • 8