Nihl
Nihl

Reputation: 587

Set class attributes from default arguments

I would like to automatically set default argument as class attributes. For example, I have (with more arguments, like a dozen or two) :

class Foo:
    def __init__(self,a=1,b=2,c=3,...):
        self.a = a
        self.b = b
        self.c = c
        ...

And I want define the attributes automatically, without having to rewrite self.x = x all the time in __init__ body.

I could use something like :

class Foo:
    def __init__(self, **kwargs):
        for attr, value in kwargs.items():
            setattr(self,attr,value)

But now I cannot give them default values. What I would like is some function that gives me a dictionary of the arguments with default values :

class Foo:
    def __init__(self,a=1,b=2,c=3,...,**kwargs):
        defargs = somefunction()

With defargs being {'a':1,'b':2,'c':3,...} (but not containing kwargs).

Closest thing I managed to do is :

class Foo:
    def __init__(self,a=1,b=2,c=3,...,**kwargs):
        defargs=locals()
        defargs.pop('self')
        defargs.pop('kwargs')
        for attr, value in defargs.items():
            setattr(self,attr,value)

But I don't know if there is not some potentially unsafe behavior in this code.

Upvotes: 5

Views: 796

Answers (3)

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 250881

In Python 3.3+ this can be done easily using inspect.signature:

import inspect


def auto_assign(func):
    signature = inspect.signature(func)

    def wrapper(*args, **kwargs):
        instance = args[0]
        bind = signature.bind(*args, **kwargs)
        for param in signature.parameters.values():
            if param.name != 'self':
                if param.name in bind.arguments:
                    setattr(instance, param.name, bind.arguments[param.name])
                if param.name not in bind.arguments and param.default is not param.empty:
                    setattr(instance, param.name, param.default)
        return func(*args, **kwargs)

    wrapper.__signature__ = signature # Restore the signature

    return wrapper


class Foo:
    @auto_assign
    def __init__(self, foo, bar, a=1, b=2, c=3):
        pass

f = Foo(10, 20)
g = Foo(100, 200, a=999)

print(f.__dict__)
print(g.__dict__)
print(inspect.getargspec(Foo.__init__))

Output:

{'bar': 20, 'b': 2, 'foo': 10, 'c': 3, 'a': 1}
{'bar': 200, 'b': 2, 'foo': 100, 'c': 3, 'a': 999}
ArgSpec(args=['self', 'foo', 'bar', 'a', 'b', 'c'], varargs=None, keywords=None, defaults=(1, 2, 3))

Upvotes: 2

Daniel Roseman
Daniel Roseman

Reputation: 599480

You could define the defaults manually as a dict:

def __init__(self, **kwargs):
    defaults = {'a':1,'b':2,'c':3,...}
    defaults.update(kwargs)
    for attr, value in defaults.items():
        setattr(self,attr,value)

Upvotes: 2

jonrsharpe
jonrsharpe

Reputation: 121974

You could always set actual class attributes (you're referring to instance attributes) to hold the default values, and either get them explicitly:

class Foo:

    # default attribute values
    a = 1 
    ...

    def __init__(self, **kwargs):
        setattr(self, 'a', kwargs.get('a', getattr(self, 'a')))
        ...

Or just leave them to be accessed normally (not recommended for mutable attributes):

class Foo:

    # default attribute values
    a = 1 
    ...

    def __init__(self, **kwargs):
        if 'a' in kwargs:
            setattr(self, 'a', kwargs['a'])
        ...

Either way, Foo().a will get you 1 and Foo(a=2).a will get you 2, and you can now easily refactor to a loop over ('a', ...) for the names of relevant attributes.

Upvotes: 1

Related Questions