Zepphit
Zepphit

Reputation: 61

When given the choice of a method, should the user set it when instantiating or later on?

I'll jump to the example, since I think it will become more clear. Should I prefer doing this:

class Substance():

    def __init__(subst,name,density_method,**kwargs):
         
        self.name   = name
        self.kwargs = kwargs
        self.density_method = density_method

    def density_method_1(self):
        #Something Here

    def density_method_2(self):
        #Something Else Here

class Air(Substance):
    Subst.__init__(self,name,gas_density_method=gas_density_method_1,**kwargs)

    # More stuff here

Air_1 = Air('Air_1',T=700,P=101325)
Air_2 = Air('Air_2',gas_density_method=gas_density_method_2,T=700,P=101325)

Or this:

class Substance():

    def __init__(subst,name,**kwargs):
         
        self.name   = name
        self.kwargs = kwargs
        self.density_method = density_method_1 #Default is now here

    def density_method_1(self):
        #Something Here

    def density_method_2(self):
        #Something Else Here

class Air(Substance):
    Subst.__init__(self,name,**kwargs)

    # More stuff here

Air_1 = Air('Air_1',T=700,P=101325)
Air_2 = Air('Air_2',T=700,P=101325)
Air_2.density_method = density_method_2

This is a basic example, but I have over 10 methods per subst (density, viscosity, etc) and each one can have 1 or more different possibilities. Is the answer here purely design-based, is there any good "practice", and is one of these better in terms of performance (important in my case)? Also, what happens if density_method_2 has extra arguments than method_1? (Since the user actively requests method_2, it is their duty to know there are extra arguments, and so I'm currently making them be passed through **kwargs to be later collected in the relevant method) Thanks in advance and have a great day!

Upvotes: 0

Views: 37

Answers (2)

As for performance. The difference really boils down to using a variable vs using a dictionary. Which at that point you are talking milliseconds, that's micro optimization that has very specialized use cases.

There's not really a good 'best practice' for this scenario, so it's going to depend more on how you are going to use the methods.

As for the other methods having different variables, that all comes down to when you actually call those methods. In this code you are only setting them to a variable to standardize how they are called, but you are not actually calling them. To add uniformity, I would make the extra variables optional. Though again, it kind of depends on what you want them to do and what you need out of them.

Edit: I misread some of your question so my answer wasn't completely understandable. As per your comment I would look at it like this.

First lets make some changes to the Substance class:

class Substance():
    # Notice here I changed 'subst' to self. That was just a syntax error
    def __init__(self, name, density_method, **kwargs):
        self.name = name
        self.kwargs = kwargs
    
        # This is where we get into the substance of the method.
        # The density method is going to be what you call as a method later on.
        # For now we just have to initialize it.
        self.density_method = None

        # This will be the setter function to make it so we can use the 
        # different density methods
        # Since there is a call to it in the init function, we can always call it again later
        self.set_density_method(density_method)

Now we have to make the setter function so that you can pass different values into the method and have it change which density method you are using. You can use getattr(object, string) as a relatively safe way to look for a method signature. The docs are here for getattr()

    def set_density_method(new_method):
        self.density_method = getattr(self, new_method)

Now change the Air class to reflect the changes you made to the parent class:

class Air(Substance):
    # The method variable here is the default density class.
    # If you want there to be no default, remove everything to the right of =
    def __init__(self, name, method='density_method_1', **kwargs):
        Substance.__init__(self, name, density_method=method, **kwargs

So with these changes, the implementation changes, but only slightly.

# Initialized with the default density method.
Air_1 = Air('Air_1',T=700,P=101325)
# Calling the designated density method.
Air_1.density_method()

# Again, initialized with the default method.
Air_2 = Air('Air_2',T=700,P=101325)
# Changing the default method after it's initialized.
Air_2.set_density_method('density_method_2')
Air_2.density_method()

# Calling it when setting the method in the class construction
Air_3 = Air('Air_3', method='density_method_3', T=700, P=101325)
Air_3.density_method()

Doing it this way means you need to know what the density functions are when you are writing your code, but there are ways to use dictionaries to map stuff like that, but it's outside the scope of this question.

Final Code:

class Substance():
    def __init__(self, name, density_method, **kwargs):
        self.name = name
        self.kwargs = kwargs
        self.density_method = None
        self.set_density_method(density_method)


    def set_density_method(self, new_method):
        self.density_method = getattr(self, new_method)

    def density_method_1(self):
        # Something Here
        print('density 1')

    def density_method_2(self):
        # Something Else Here
        print('density_2')

    def density_method_3(self):
        # Something Else Here
        print('density_3')


class Air(Substance):
    def __init__(self, name, method='density_method_1', **kwargs):
        Substance.__init__(self, name, density_method=method, **kwargs)


Air_1 = Air('Air_1',T=700,P=101325)
Air_1.density_method()
Air_2 = Air('Air_2',T=700,P=101325)
Air_2.set_density_method('density_method_2')
Air_2.density_method()

Air_3 = Air('Air_3', method='density_method_3', T=700, P=101325)
Air_3.density_method()

print('wait')

Upvotes: 1

pacuna
pacuna

Reputation: 2089

It's always best to inject the dependencies. In this case, your first option is better. It makes your code more testable and easier to mock. Try to write tests for both cases and you will realize why. Read more about dependency injection and duck typing with Python.

Upvotes: 1

Related Questions