Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use **kwargs in Python 3 class __init__ function?

I am writing a class in Python 3 that I want to be able to take various keyword arguments from the user and to store these values for later use in class methods. An example code would be something like this:

class MathematicalModel:
    def __init__(self, var1, var2, var3, **kwargs):
        self.var1 = var1
        self.var2 = var2
        self.var3 = var3
        self.var4 = kwarg1
        self.var5 = kwarg2
        self.var6 = kwarg6

    def calculation1(self):
        x = self.var1 + self.var2 + self.var3
        return x

    def calculation2(self):
        y = self.var1 * self.var2 * var3
        return y

class MathematicalModelExtended(MathematicalModel):
    def __init__(self, var1, var2, var3, **kwargs):
        super.__init__(self, var1, var2, var3, **kwargs)

    def calculation1(self):
        '''Overrides calculation1 method from parent DoThis'''
        x = (self.var1 + self.var2 + self.var3) / self.kwarg1
        return x

    def calculation2(self):
        '''Overrides calculation2 method from parent DoThis'''
        y = (self.var1 * self.var2 * self.var3) / (self.kwarg1 + self.kwarg2)
        return y

a = MathematicalModel(1, 2, 3)
b = MathematicalModelExtended(1, 2, 3, var4 = 4, var5 = 5, var6 = 6)

However I am not sure how this works, for a few reasons: a) What if the user doesn't put an argument for b or c, or even a for that matter? Then the code will throw an error, so I am not sure how to initialize these attributes in that case. b) How do I access the values associated with the keywords, when I don't know what keyword argument the user passed beforehand?

I plan to use the variables in mathematical formulas. Some variables (not included in kwargs) will be used in every formula, whereas others (the ones in kwargs) will only be used in other formulas. I plan to wrap MathematicalModel in another class like-so MathematicalModelExtended(MathematicalModel) in order to achieve that.

Thank you!

like image 657
psychedelic-snail Avatar asked Dec 08 '22 10:12

psychedelic-snail


1 Answers

General kwargs ideas

When you load variables with self.var = value, it adds it to an internal dictionary that can be accessed with self.__dict__.

class Foo1:

    def __init__(self, **kwargs):
        self.a = kwargs['a']
        self.b = kwargs['b']

foo1 = Foo1(a=1, b=2)
print(foo1.a)  # 1
print(foo1.b)  # 2
print(foo1.__dict__)  # {'a': 1, 'b': 2}

If you want to allow for arbitrary arguments, you can leverage the fact that kwargs is also a dictionary and use the update() function.

class Foo2:

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

foo2 = Foo2(some_random_variable=1, whatever_the_user_supplies=2)
print(foo2.some_random_variable)  # 1
print(foo2.whatever_the_user_supplies)  # 2
print(foo2.__dict__)  # {'some_random_variable': 1, 'whatever_the_user_supplies': 2}

This will prevent you from getting an error when you try to store a value that isn't there

class Foo3:

    def __init__(self, **kwargs):
        self.a = kwargs['a']
        self.b = kwargs['b']

foo3 = Foo3(a=1)  # KeyError: 'b'

If you wanted to ensure that variables a or b were set in the class regardless of what the user supplied, you could create class attributes or use kwargs.get()

class Foo4:

    def __init__(self, **kwargs):
        self.a = kwargs.get('a', None)
        self.b = kwargs.get('b', None)

foo4 = Foo4(a=1)
print(foo4.a)  # 1
print(foo4.b)  # None
print(foo4.__dict__)  # {'a': 1, 'b': None}

However, with this method, the variables belong to the class rather than the instance. This is why you see foo5.b return a string, but it's not in foo5.__dict__.

class Foo5:

    a = 'Initial Value for A'
    b = 'Initial Value for B'

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

foo5 = Foo5(a=1)
print(foo5.a)  # 1
print(foo5.b)  # Initial Value for B
print(foo5.__dict__)  # {'a': 1}

If you are giving the users the freedom to specify any kwargs they want, you can iterate through the __dict__ in a function.

class Foo6:

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def do_something(self):
        for k, v in self.__dict__.items():
            print(f"{k} -> {v}")

foo6 = Foo6(some_random_variable=1, whatever_the_user_supplies=2)
foo6.do_something()
# some_random_variable -> 1
# whatever_the_user_supplies -> 2

However, depending on whatever else you have going on in your class, you might end up with a lot more instance attributes than the user supplied. Therefore, it might be good to have the user supply a dictionary as an argument.

class Foo7:

    def __init__(self, user_vars):
        self.user_vars  = user_vars

    def do_something(self):
        for k, v in self.user_vars.items():
            print(f"{k} -> {v}")

foo7 = Foo7({'some_random_variable': 1, 'whatever_the_user_supplies': 2})
foo7.do_something()
# some_random_variable -> 1
# whatever_the_user_supplies -> 2

Addressing your code

With your updated code, I would suggest using the self.__dict__.update(kwargs) method. Then you can either raise an error when you don't encounter variable you're relying on (option1 method) or you can have a default value for the variable incase it's not defined (option2 method)

class MathematicalModel:
    def __init__(self, var1, var2, var3, **kwargs):
        self.var1 = var1
        self.var2 = var2
        self.var3 = var3
        self.__dict__.update(kwargs)  # Store all the extra variables


class MathematicalModelExtended(MathematicalModel):
    def __init__(self, var1, var2, var3, **kwargs):
        super().__init__(var1, var2, var3, **kwargs)

    def option1(self):
        # Trap error if you need var4 to be specified
        if 'var4' not in self.__dict__:
            raise ValueError("Please provide value for var4")

        x = (self.var1 + self.var2 + self.var3) / self.var4
        return x

    def option2(self):
        # Use .get() to provide a default value when the user does not provide it.
        _var4 = self.__dict__.get('var4', 1)

        x = (self.var1 + self.var2 + self.var3) / self.var4
        return x


a = MathematicalModel(1, 2, 3)
b = MathematicalModelExtended(1, 2, 3, var4=4, var5=5, var6=6)
print(b.option1())  # 1.5
print(b.option2())  # 1.5

Granted, if MathematicalModel will never use anything other than var1, var2, and var3, there's no point in passing the kwargs.

class MathematicalModel:
    def __init__(self, var1, var2, var3, **kwargs):
        self.var1 = var1
        self.var2 = var2
        self.var3 = var3

class MathematicalModelExtended(MathematicalModel):
    def __init__(self, var1, var2, var3, **kwargs):
        super().__init__(var1, var2, var3)
        self.__dict__.update(kwargs)

    def option1(self):
        # Trap error if you need var4 to be specified
        if 'var4' not in self.__dict__:
            raise ValueError("Please provide value for var4")

        x = (self.var1 + self.var2 + self.var3) / self.var4
        return x

    def option2(self):
        # Use .get() to provide a default value when the user does not provide it.
        _var4 = self.__dict__.get('var4', 1)

        x = (self.var1 + self.var2 + self.var3) / self.var4
        return x


a = MathematicalModel(1, 2, 3)
b = MathematicalModelExtended(1, 2, 3, var4=4, var5=5, var6=6)
print(b.option1())  # 1.5
print(b.option2())  # 1.5
like image 126
Cohan Avatar answered Dec 09 '22 23:12

Cohan