0

Backround

When I create a python class and in the init method I just write:

self.name1 = name1
self.name2 = name2
....

for all arguments.

Question

SO, how to elegently write a __init__ for different *args and **kwargs?

I want write a metaclass to automatically set attributes for *args and **kwargs:

#the metaclass to be used
class LazyInit():
     # code here

And then when I create child classes of it, child classes will auto __init__ for their own *args and **kwargs.

class Hospital(metaclass = LazyInit):
  def __init__(self, *args,**kwargs): pass

class Basketball(metaclass = LazyInit):
  def __init__(self, *args, **kwargs): pass

a = 1
b = 2
c = 3
a_hospital = Hospital(a, b, kw1 =1, kw2 =2)
a_circle = Basketball(a, c, kw3 = 10, kw4 = 20)

My attempt

I only know that we can use setattr to do it only for **kwargs:

class LazyInit():
    def __init__(self,**kwargs):
        for k,v in kwargs.items():
            setattr(self,k,v)
LazyInit(a=1).a

1

However, this is not a meta class which can be used in child classes. I should write this code snippet in each of my child classes...

Travis
  • 1,152
  • 9
  • 25
  • 2
    That's not a metaclass, it's just a class that you're inheriting from. – ChrisGPT was on strike Feb 17 '20 at 05:16
  • 1
    Why don't you want the classes to have specific attribute names and parameters? And write that code in their `__init__`? – wwii Feb 17 '20 at 05:19
  • Suppose I want to write 10 classes, I need to write the same `__init__` for them 10 times, maybe we can save some time by using metaclass. Also, I only know how to setattributes for `**kwargs` but have no idea about `*args`. – Travis Feb 17 '20 at 05:23
  • `*args` are positional, you won't be able to derive a name from what is passed. It will be a tuple that you can iterate over but you will have to supply the names for the attributes. – wwii Feb 17 '20 at 05:26
  • 1
    related: [Use of *args and **kwargs](https://stackoverflow.com/questions/3394835/use-of-args-and-kwargs) ... [dataclasses](https://docs.python.org/3/library/dataclasses.html) are supposed to help alleviate the tedium (?) of writing all those `__init__'s`. Or you can just inherit from a class like you had before your edit. – wwii Feb 17 '20 at 05:30
  • https://docs.python.org/3/tutorial/classes.html – wwii Feb 17 '20 at 05:34

1 Answers1

0

First, make a decorator to do the binding.

from functools import wraps
from inspect import signature

def binds(f):
    @wraps(f)
    def __init__(self, *args, **kwargs):
        # Update self attributes with inspected binding of arg names to values.
        vars(self).update(signature(f).bind(None, *args, **kwargs).arguments)
        # `self` is also an arg, but we didn't really want to save this one.
        del self.self
    return __init__

Here's how you could use it.

class Foo:
    @binds
    def __init__(self, foo, bar, *args, baz, **kwargs):
        pass

And a demonstration:

>>> vars(Foo(1,2,3,4,baz=5,quux=10))
{'foo': 1, 'bar': 2, 'args': (3, 4), 'baz': 5, 'kwargs': {'quux': 10}}

Now all that's left is to make a metaclass apply this decorator automatically.

class AutoInit(type):
    def __new__(cls, name, bases, namespace):
        namespace['__init__'] = store_args(namespace['__init__'])
        return type.__new__(cls, name, bases, namespace)

AutoInit is a subclass of the default metaclass type. On construction it finds the init method and replaces it with the wrapped version. type handles the rest.

Now you can use it like any other metaclass:

class Bar(metaclass=AutoInit):
    def __init__(self, spam, eggs, *, bacon):
        pass

Demonstration:

>>> vars(Bar(1,2,bacon=3))
{'spam': 1, 'eggs': 2, 'bacon': 3}

And this behavior will be inherited as well.

class Baz(Bar):
    def __init__(self, spam, eggs, sausage, *, bacon):
        super().__init__(spam, eggs, bacon=bacon)

Demonstration (note the 'sausage'):

>>> vars(Baz(1,2,3,bacon=4))
{'spam': 1, 'eggs': 2, 'sausage': 3, 'bacon': 4}

Metaclasses are deep magic. Powerful, but obscure. You almost never need them (if you're not sure, assume that you don't). Code that uses metaclasses too much can become very hard to understand and maintain.

In this case, you should probably stop at the decorator. It does almost everything you want, without being so confusing. Explicit is better than implicit. Code is read more than it is written. Readability counts.

gilch
  • 10,813
  • 1
  • 23
  • 28