5

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Monolith
  • 1,067
  • 1
  • 13
  • 29
  • 3
    **Why** do you want to do this? Why not just put those names in a mix-in class and import that class and use it as a base class in the importing module? – Martijn Pieters Nov 02 '19 at 12:52
  • That's not really a reason to not use classes; nothing in recursive descent parser design requires that you eschew using classes and then try to re-assemble the methods into a class after the fact and so go against normal Python patterns. – Martijn Pieters Nov 05 '19 at 14:11
  • 1
    That doesn't contradict anything I said, though. You then define multiple classes in modules, not a pile of functions that you want to attach to a class later as methods. :-) – Martijn Pieters Nov 05 '19 at 14:21
  • Ah... I see what you mean. Yes I have done that instead of the "ugly" way of importing all. – Monolith Nov 05 '19 at 14:26
  • 1
    On a side note, you may be interested in reading how [Guido van Rossum is re-visiting the Python language parser design](https://medium.com/@gvanrossum_83706/peg-parsing-series-de5d41b2ed60). Different parser design, but still. – Martijn Pieters Nov 05 '19 at 14:32

2 Answers2

17

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Yes I realised you can just use inheritence, but it doesn't really make sense for my use case. The reason I am doing this is because I am writing a recursive descent parser (meaning the methods are interdependant of each other), in which the rules in the parser have no particular order. Perhaps I could make the whole `Parser` class and have it inherit from the smaller classes. – Monolith Nov 03 '19 at 00:39
  • @MonLiH that’s what I’m saying you should do, yes. To create your Parser class by subclassing a series of “component” classes. – Martijn Pieters Nov 03 '19 at 08:10
1

A for-loop over bar's attributes with getattr and setattr can mimic the import-all result and a call to the function's __get__ method will return that same function bound to an instantiated instance (see this answer):

# bar.py
def frog(self):
    print(f"frog: {self}")

def horse(self):
    print(f"horse: {self}")

# foo.py
class Foo:
    def __init__(self):
        import bar
        for object_name in dir(bar):
            if not object_name.startswith("__"):
                bound_method = getattr(bar, object_name).__get__(
                    self, 
                    self.__class__
                )
                setattr(self, object_name, bound_method)

>>> from foo import Foo
>>> f = Foo()
>>> f.frog
<bound method frog of <foo.Foo object at 0x10c961a50>>
>>> f.horse
<bound method horse of <foo.Foo object at 0x10c961a50>>
>>> f.frog()
frog: <foo.Foo object at 0x10c961a50>
>>> f.horse()
horse: <foo.Foo object at 0x10c961a50>
>>>

Remove the if statement to include the extra, default, Python objects (__package__, __spec__, etc.) or expand upon it if you have other filters in mind.

Note that this will require the class to be instantiated before accessing those objects through it.

Cole
  • 1,715
  • 13
  • 23
  • This doesn't do what I want because when I run my method, the class does not automatically pass `self` as the first argument. Doing so gives the following error: `TypeError: print_bar() missing 1 required positional argument: 'self'`. I do not want to have to manually pass `self` (`test_class.print_bar(test_class)`) because the function is called alot and seems messey. – Monolith Nov 01 '19 at 15:39
  • Also, most methods are called by other methods in the class, meaning I would haave to pass self as a first argument everytime (e.g. in the class `self.print_bar(self)`), leading to more edits. – Monolith Nov 01 '19 at 15:47
  • My mistake, I somehow overlooked that part of your question. I've updated my answer to account for binding the methods – Cole Nov 01 '19 at 16:03
  • Thanks for the answer! This looks promising although a bit hacky. I will accept this answer I don't find a better one in a few days. – Monolith Nov 01 '19 at 17:13
  • 2
    This is way more hacky than it needs to be. You can still add attributes to a class after the `class` statement, giving each instance a static copy of the bound method *doesn't scale* either, and now the OP can't use properties. – Martijn Pieters Nov 02 '19 at 13:28