Python Forum

Full Version: How do I instantiate a class with **kwargs?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Consider the following:

class TestClass():

    def __init__(self, param_1 , **kwargs):
        self.param_1 = param_1
        #?? how do I initialise the **kwargs??
        #self.**kwargs = **kwargs gives a SyntaxError
    
    def do_something(self):
        pass

x = TestClass(param_1 = 'a', param_2 = 'b', param_3 = 'c')
print(x.param_1) #a
print(x.param_2) #AttributeError : TestClass object has no attribute 'param_2'
How do I get the final line to work?

Thanks in advance
Consider not doing that. What is your use case for a class that creates different instance attributes based on the named arguments passed to __init__()?

But if you want to follow this path that likely leads to disaster:
class BadIdea:
    def __init__(self, param_1, **kwargs):
        self.param_1 = param_1
        for key, value in kwargs.items():
            setattr(self, key, value)


class AlsoBadIdea:
    def __init__(self, param_1, **kwargs):
        self.param_1 = param_1
        for key, value in kwargs.items():
            self.__dict__[key] = value


x = BadIdea(param_1="a", param_2="b", param_3="c")
print(x.__dict__, x.param_2)

y = AlsoBadIdea(param_1="a", param_2="b", param_3="c")
print(y.__dict__, y.param_3)
Output:
{'param_1': 'a', 'param_2': 'b', 'param_3': 'c'} b {'param_1': 'a', 'param_2': 'b', 'param_3': 'c'} c
If you want an object that holds arbitrary values that can be referenced by name, us a dictionary. if you want a dictionary that supports attribute-like syntax, look at AttrDict.

https://pypi.org/project/attrdict/

Or do you just want optional arguments?
class TestClass:
    def __init__(self, param_1, param_2=None, param_3=None):
        self.param_1 = param_1
        self.param_2 = param_2
        self.param_3 = param_3

    def __str__(self):
        
        return f"<TestClass param_1={self.param_1} param_2={self.param_2} param_3={self.param_3}>"


print(TestClass(1))
print(TestClass(1, 2))
print(TestClass(1, 2, 3))
Output:
<TestClass param_1=1 param_2=None param_3=None> <TestClass param_1=1 param_2=2 param_3=None> <TestClass param_1=1 param_2=2 param_3=3>
(Feb-15-2023, 04:21 PM)deanhystad Wrote: [ -> ]Consider not doing that. What is your use case for a class that creates different instance attributes based on the named arguments passed to __init__()?

Actually this (using *args and/or **kwargs) is quite common pattern. Of course one does not simply take whatever you throw in. But it is quite useful, e.g. in inheritance.

https://stackoverflow.com/q/1098549/4046632
(Feb-15-2023, 05:22 PM)buran Wrote: [ -> ]
(Feb-15-2023, 04:21 PM)deanhystad Wrote: [ -> ]Consider not doing that. What is your use case for a class that creates different instance attributes based on the named arguments passed to __init__()?

Actually this (using *args and/or **kwargs) is quite common pattern. Of course one does not simply take whatever you throw in. But it is quite useful, e.g. in inheritance.

https://stackoverflow.com/q/1098549/4046632
For inheritance, sure:
class A:
    def __init__(self, a, b, c=None):
        self.a = a
        self.b = b
        self.c = c


class B(A):
    def __init__(self, a, b, d=None, **kwargs):
        super().__init__(a, b, **kwargs)
        self.d = d

    def __str__(self):
        return f"<B a={self.a} b={self.b} c={self.c} d={self.d}>"


x = B(1, 2, c=3, d=4)
print(x)
Output:
<B a=1 b=2 c=3 d=4>
Notice that B doesn't really do anything with **kwargs other than pass them along to the __init__() method for A. Also be aware that B() only allows 2 positional arguments. I should write it like this:
class B(A):
    def __init__(self, a, b, *, d=None, **kwargs):d
This lets the user know only the first two arguments are positional.

But that is not what is what the OP is asking about. At least that is not my interpretation. I think the OP wants named arguments in the __init__() method to become attributes in the new instance. That is not very useful because the class creates instances that don't have the same attributes. It does not define an interface. How can you use instances of this class if you don't know what they do or know.
You can do
class TestClass():
 
    def __init__(self, param_1 , **kwargs):
        self.param_1 = param_1
        vars(self).update(kwargs)
     
    def do_something(self):
        pass
 
(Feb-15-2023, 04:21 PM)deanhystad Wrote: [ -> ]Consider not doing that. What is your use case for a class that creates different instance attributes based on the named arguments passed to __init__()?

But if you want to follow this path that likely leads to disaster:
class BadIdea:
    def __init__(self, param_1, **kwargs):
        self.param_1 = param_1
        for key, value in kwargs.items():
            setattr(self, key, value)


class AlsoBadIdea:
    def __init__(self, param_1, **kwargs):
        self.param_1 = param_1
        for key, value in kwargs.items():
            self.__dict__[key] = value


x = BadIdea(param_1="a", param_2="b", param_3="c")
print(x.__dict__, x.param_2)

y = AlsoBadIdea(param_1="a", param_2="b", param_3="c")
print(y.__dict__, y.param_3)
Output:
{'param_1': 'a', 'param_2': 'b', 'param_3': 'c'} b {'param_1': 'a', 'param_2': 'b', 'param_3': 'c'} c
If you want an object that holds arbitrary values that can be referenced by name, us a dictionary. if you want a dictionary that supports attribute-like syntax, look at AttrDict.

https://pypi.org/project/attrdict/

Or do you just want optional arguments?
class TestClass:
    def __init__(self, param_1, param_2=None, param_3=None):
        self.param_1 = param_1
        self.param_2 = param_2
        self.param_3 = param_3

    def __str__(self):
        
        return f"<TestClass param_1={self.param_1} param_2={self.param_2} param_3={self.param_3}>"


print(TestClass(1))
print(TestClass(1, 2))
print(TestClass(1, 2, 3))
Output:
<TestClass param_1=1 param_2=None param_3=None> <TestClass param_1=1 param_2=2 param_3=None> <TestClass param_1=1 param_2=2 param_3=3>

Thank you for your feedback and guidance.

I am actually trying to extend the dateutil.rrule class. Specifically I am trying to generate the last Monday and Friday (for example) of each month, alternately. So for example, last Monday of Jan 2023 ( 30 Jan) followed by last Friday of Feb 2023 ( Feb 24), then last Monday of March ( 27 March) etc. To the best of my knowledge neither rrule nor rruleset provides a convenient method to do this (please tell me if I am wrong!) , so I have to try and create a custom class that will allow me to do this using rrule in some way (I am thinking composition, but I am still experimenting so I don't have a concrete implementation yet).

Unfortunately rrule has so many parameters, not all needs to be supplied and not all can be supplied in one call, so I figured the easiest way is to use **kwargs on top of whatever other arguments I need. Like I said I am still experimenting around (so I don't have a concrete implementation yet), and during the experiment I thought this class may need to inspect the parameters supplied to rrule, hence the question.

If you have any broad tips (not asking for actual code) on how to approach this problem, it would be much appreciated.

Thanks to all others that helped.
Buran was right, you wanted to use kwargs to pass along arguments to a superclass. I totally misread your intentions. Sorry.

Using **kwargs for subclassing works great. Just declare you new named arguments and pass **kwargs to the superclass.
class MyRrule(dateutil.rrule):
    def __init__(self, new_arg=None, **kwargs):
        super().__init__(**kwargs)
        self.new_arg = new_arg