How can I automatically assign __init__ arguments to attributes?
Asked Answered
I

5

18

Is there a way to automatically bind to self (some of) the arguments of the __init__ method?

For example, suppose we have:

class Person:
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address
        ...

could a lazy_init decorator be created that allows doing it this way instead?

class Person:
    @lazy_init
    def __init__(self, name, age, address):
        ...

Does any similar technique exist? Or is there a good reason not to try?

Imprest answered 19/2, 2011 at 1:49 Comment(1)
Dataclasses in Python 3.7 will do exactly this. python.org/dev/peps/pep-0557Acred
K
9

For future reference for all others who encounter this question in the future (Python 3.7 or above): There is the dataclass decorator that does exactly this.

Example

from dataclasses import dataclass

@dataclass
class Person:
    name
    age
    address

# other methods...
Katabolism answered 18/11, 2019 at 12:20 Comment(0)
B
7

It's definitely possible, here's a somewhat naive implementation:

from functools import wraps

def lazy_init(init):
    import inspect
    arg_names = inspect.getargspec(init)[0]

    @wraps(init)
    def new_init(self, *args):
        for name, value in zip(arg_names[1:], args):
            setattr(self, name, value)
        init(self, *args)

    return new_init

class Person:
    @lazy_init
    def __init__(self, name, age):
        pass

p = Person("Derp", 13)
print p.name, p.age

Once you start having something besides attributes that map to properties, you're going to run into trouble though. You'll need at least some way to specify which args to initialize as properties... at which point it'll just become more hassle than it's worth.

Bina answered 19/2, 2011 at 2:0 Comment(2)
Thanks. I realize that if the __init__ method is sophisticated it wouldn't be worth it, but the simple ones are so widespread that I think it would be nice to have something like that at hand.Sheeting
requires changes for keyword argumentsDecompose
F
4

You can either use data classes as of Python 3.7 like this:

from dataclasses import dataclass

@dataclass
class MyClass:
    var_1: str
    var_2: float

Or you can make use of the store_attr() method from the fastcore lib like this:

from fastcore.utils import store_attr

class MyClass:
    def __init__(self, var_1, var_2):
        store_attr()

Both result in the equivalent init method:

def __init__(self, var_1, var_2):
    self.var_1 = var_1
    self.var_2 = var_2
Felske answered 24/11, 2021 at 9:12 Comment(1)
The dataclass approach seems sufficient enough for most cases :-)Halophyte
D
-1

Here it goes, in simplest form:

def lazy_init(*param_names):
  def ret(old_init):
    def __init__(self, *args, **kwargs):
      if len(args) > len(param_names):
        raise TypeError("Too many arguments")
      for k in kwargs:
        if k not in param_names:
          raise TypeError("Arg %r unexpected" % k)
      for par, arg in zip(param_names, args):
        setattr(self, par, arg)
      for par, arg in kwargs.items():
        setattr(self, par, arg)
      old_init(*args, **kwargs)
    return __init__
    #
  return ret


class Q(object):
  @lazy_init("a", "b")
  def __init__(self, *args, **kwargs):
    print "Original init"

>>> q = Q(1, 2)
Original init
>>> q.a, q.b
(1, 2)

Consider making a class decorator to cover __str__ too.

Dumpish answered 19/2, 2011 at 2:31 Comment(0)
D
-1

initify.py created by me :) it does exactly what you'd expect.

class Animal:
   @init_args(exclude=["name"])
   def __init__(self, name):
       pass

or with default values:

class Animal:
   @init_args
   def __init__(self, name="Default name"):
       pass

or even excluding inheritance:

class Dog(Animal):
   @init_args(exclude=["name"])
   def __init__(self, species):
       pass

https://github.com/prankymat/initify.py

Dinette answered 25/5, 2015 at 3:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.