Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Import all functions and classes inside a module into a class python

I am trying to import all the objects from a subfolder into a class in python 3.8, and am strugguling to find a way to do so. I don't want to manually have to import all the objects, as there are far too many files to list:

class Foo:
    from bar import {
        one,
        two,
        three,
        # ...
    }

And when I use the star symbol to import all functions and classes (e.g. from bar import *) I get the following error:

SyntaxError: import * only allowed at module level

Also, I would not like to put everything under a sub-scope (e.g. import bar.package and put all the files in the package subpackage of bar), because the functions in bar rely on being passed self when run and would mean would have to change all references of self to self.package I am only creating another folder with all the methods in the class so that there is not a single extremely long file.

So I guess I have three questions: Why is importing all in a class not allowed, how can I get around this, and is there a better way to split multiple methods up into different files?

EDIT: I've seen this post, and is what I currently have, but only instead of importing everything manually I want to use the * instead.

EDIT 2: Perhaps creating the submethods of a class as a package and importing it as self would work (e.g. import bar.package as self), but this may override the default self. For example say I have the two files foo.py and bar.py:

# foo.py
def print_bar(self):
    print("bar")
# bar.py
class Test:
    import foo as self
    def __init__(self):
        print("hi")
        self.one = 1

    def print_one(self):
        print(self.one)

if __name__ == "__main__":
    test_class = Test()
    test_class.print_one()
    test_class.self.print_bar(test_class)

Notice the ugly call in bar.py that runs test_class.self.print_bar(test_class). In this case, python does not automatically pass the class as a method. If I get rid of the test_class argument passed in the function it does not run and gives a TypeError. I want to avoid passing self to the method at all costs.

like image 505
Monolith Avatar asked Dec 07 '22 11:12

Monolith


1 Answers

First of all: Don't do this.

There are probably better ways to solve your specific problem. If you have to define a large number of related methods for a class, and want to use modules to organize these methods, then give each module a base class, define the methods on those classes, then combine the classes from the modules into one by using sub-classing. See below.

Why is importing all in a class not allowed

Because a class statement is a scoped namespace where Python, at compile time, needs to know what names are referenced as globals and distinguish those from local names. This is especially important in functions, where the local namespace is highly optimised by knowing what names are used at compile time. For a class statement, the 'local' names become the class attributes. A class statement is more flexible when it comes to dynamic local names than functions are, but importing an arbitrary, dynamic list of names into a class definition was never seen as a use-case worth supporting.

how can I get around this

You can introspect the module and get all the same names that Python would import, and set these on the class dynamically using setattr() or by assigning to the locals() dictionary (a class statement is the only place the latter actually works). The import statement documentation lists what is imported:

  • If the module defines __all__, then it is used as the list of names to import
  • Otherwise all public names are imported (all globals with a name that doesn't start with _).

So the following would achieve the exact same effect as using from bar import * inside the class statement for Foo:

import bar

def _import_all(module, class):
    namespace = vars(module)
    public = (name for name in namespace if name[:1] != "_")
    for name in getattr(module, "__all__", public):
        setattr(class, name, namespace[name])

class Foo:
    pass

_import_all(bar, Foo)

or, and this is definitely more 'hack-y' and depending on internal Python implementation details:

import bar

class Foo
    locals().update(
        (n, getattr(bar, n))
        for n in getattr(
            bar, "__all__",
            (n for n in dir(bar) if n[:1] != "_")
        )
    )

is there a better way to split multiple methods up into different files?

Yes, use classes. Put all those methods in separate modules, each in a class, then simply import those classes from each module and use it as a base class:

import bar, spam

class Foo(bar.Base, spam.Base):
    pass

where bar.py would define a Base class:

class Base:
    def print_bar(self):
        print("bar")

and so would spam, etc. You can add and remove methods to these base mixin classes as needed, and the import statements to combine the base classes will not change.

like image 68
Martijn Pieters Avatar answered Dec 28 '22 05:12

Martijn Pieters