2

I understand this is the correct way to include a function inside a class, but feels overly complicated and awkward to have to rename existing functions:

from numpy import sin, cos, tan

class Foo1:
    def my_sin(self, x):
        return sin(x)

    def my_cos(self, x):
        return cos(x)

    def my_tan(self, x):
        return tan(x)

a1 = Foo1()
print(a1.my_sin(2))
print(a1.my_cos(2))
print(a1.my_tan(2))

In contrast, this works just as well, and feels neater.

class Foo2:
    from numpy import sin, cos, tan


a2 = Foo2()
print(a2.sin(2))
print(a2.cos(2))
print(a2.tan(2))

Why is this wrong, assuming I will only use those functions inside this class? Why does it even work?

(I've redacted my example heavily from my original text to keep it focused.)

4
  • 1
    Which specific errors are you seeing? Commented Sep 9, 2021 at 1:08
  • It's a bad practice to do imports like this. First of all, all imports should go at the top of the file except some very special cases. Secondly, inside seasonal_decompose method as soon as the import statement runs, the seasonal decompose within the namespace gets overwritten by the function from statsmodels. Commented Sep 9, 2021 at 1:20
  • Here is a duplicate: stackoverflow.com/q/51117397/2988730. Unfortunately I've already used up my close vote Commented Sep 9, 2021 at 1:29
  • Use self.seasonal_decompose if you want to do that. Imports have global side effects anyway, so the onto only real reason to do them anywhere but the global namespace is lazy evaluation. Circular imports are another one, but that's not a real reason, just bad design Commented Sep 9, 2021 at 1:31

2 Answers 2

1

It's important to put all your imports at the top of the file so someone else or future-you can immediately see your file's dependencies. Your import statement in Foo2 basically adds a module to sys.modules and assigns a few functions to class attributes. We can make class attributes like this:

import numpy as np

## other code that can bury Foo3 deeper into file

class Foo3:
    sin, cos, tan = np.sin, np.cos, np.tan

Has a repetition compared to the import, but it's still more concise than making forwarding methods like in Foo1. If you planned on renaming the functions, then the repetition would be necessary anyway: from numpy import sin as s, cos as c, tan as t vs s, c, t = np.sin, np.cos, np.tan.

P.S. The other posts linked in comments and answers say that modules are almost always better than classes for putting static method-like functions in namespaces (and they are right), but maybe you want this class to have this class attribute so you can override in subclasses and play with duck-typing.

Sign up to request clarification or add additional context in comments.

2 Comments

Something else clicked finally, which I will repeat here to make sure I understood. If I want to make a collection of functions easily accessible, this is the correct way: Create a module, myusefulfunctions.py: ` from numpy import sin, cos, tan, pi from statsmodels.tsa.seasonal import seasonal_decompose class MyUsefulClass: # fun stuff pass def my_useful_function(): # more fun stuff pass `
Modules are just the go-to for making a namespace to hold anything. You don't even have to put your useful functions in a useful class in your module, you can just put a useful function in the module. It's pretty flexible, do what you need. It's just that flexible things let you write anti-patterns, and you managed to recognize one and now know an easy way to get around it.
1

Generally, you should avoid nesting import statements into your functions/classes. See here and here for more on why.

If you are insisting on having import statements in your class, then you are attributing that library/module to the class's namespace - so I think you would have to reflect that in the function.

In example, (but bad practice):

class foo:
    from numpy import sin
    from statsmodels.tsa.seasonal import seasonal_decompose
    
    def seasonal_decompose(self, *args, **kwargs):
        return foo.seasonal_decompose(*args, **kwargs)


a = foo()
print(a.sin(3))
print(a.seasonal_decompose([1, 2], period=1))

Also, it's not advisable to name a function after a library/module...

I would suggest the following adjustments:

from numpy import sin
from statsmodels.tsa.seasonal import seasonal_decompose

class foo:
    def my_new_function_name(self, *args, **kwargs):
        return seasonal_decompose(*args, **kwargs)


a = foo()
print(a.sin(3))
print(a.my_new_function_name([1, 2], period=1))

4 Comments

This doesn't really address the why aspect
The bigger question was on the "appropriate way to make the function accessible" in the class. Hence title of the post. This answer directly addresses that. Also provided two links on why it doesn't work.
self is more flexible than foo in this case
Both of you are correct. I realized I had asked two separate questions together, so I focused this one on @newtothis's answer and created a separate question regarding why seasonal_decompose and sin behave differently here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.