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.)

  • 1
    Which specific errors are you seeing? – kingkupps Sep 09 '21 at 01: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. – NotAName Sep 09 '21 at 01:20
  • Here is a duplicate: https://stackoverflow.com/q/51117397/2988730. Unfortunately I've already used up my close vote – Mad Physicist Sep 09 '21 at 01: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 – Mad Physicist Sep 09 '21 at 01:31

2 Answers2

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))
newtothis
  • 35
  • 10
  • This doesn't really address the why aspect – Mad Physicist Sep 09 '21 at 01:27
  • 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. – newtothis Sep 09 '21 at 01:35
  • 1
    `self` is more flexible than `foo` in this case – Mad Physicist Sep 09 '21 at 01:36
  • 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](https://stackoverflow.com/questions/69111582/apparently-inconsistent-errors-when-calling-functions-defined-in-other-modules-f). – jomurmiranda Sep 09 '21 at 13:40
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.

BatWannaBe
  • 4,330
  • 1
  • 14
  • 23
  • 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 ` – jomurmiranda Sep 09 '21 at 13:47
  • 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. – BatWannaBe Sep 09 '21 at 18:39