1

Context: I'm writing a translator from one Python API to another, both in Python 3.5+. I load the file to be translated with a class named FileLoader, described by Fileloader.py. This file loader allows me to transfer the file's content to other classes doing the translation job.

All of the .py files describing each class are in the same folder

I tried two different ways to import my FileLoader module inside the other modules containing the classes doing the translation job. One seems to work, but the other didn't and I don't understand why.

Here are two code examples illustrating both ways:

The working way

import FileLoader

class Parser:
    #
    def __init__(self, fileLoader):
         if isinstance(fileLoader, FileLoader.FileLoader)
             self._fileLoader = fileLoader
         else:
             # raise a nice exception

The crashing way

class Parser:

    import FileLoader
    #
    def __init__(self, fileLoader):
         if isinstance(fileLoader, FileLoader.FileLoader)
             self._fileLoader = fileLoader
         else:
             # raise a nice exception

I thought doing the import inside the class's scope (where it's the only scope FileLoader is used) would be enough, since it would know how to relate to the FileLoader module and its content. I'm obviously wrong since it's the first way which worked.

What am I missing about scopes in Python? Or is it about something different?

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Dmottus
  • 13
  • 6
  • 4
    Does this help: https://stackoverflow.com/questions/128478/should-import-statements-always-be-at-the-top-of-a-module ? – Aru Jun 15 '21 at 14:20
  • 3
    An import (or other assignment) inside a class definition doesn't make the name globally known. You'd have to explicitly write `Parser.FileLoader` to reference that object. – jasonharper Jun 15 '21 at 14:29
  • @Aru it's not really about my question but giving me lots of new information, so thanks for sharing ! – Dmottus Jun 15 '21 at 14:44
  • 1
    Does this answer your question? [Short description of the scoping rules?](https://stackoverflow.com/questions/291978/short-description-of-the-scoping-rules) – MisterMiyagi Jun 15 '21 at 15:31
  • 2
    The immediate effect of an ``import`` statement is just a name binding. An ``import`` inside a class statement has the same effect as any name binding inside a class scope – it creates a class attribute, which is only accessible via the class' scope. – MisterMiyagi Jun 15 '21 at 15:32
  • @MisterMiyagi it does, thank you – Dmottus Jun 16 '21 at 08:08

1 Answers1

1

2 things : this won't work. And there is no benefit to doing it this way.

First, why not?

class Parser:


    #this assigns to the Parser namespace, to refer to it
    #within a method you need to use `self.FileLoader` or 
    #Parser.FileLoader
    import FileLoader

    #`FileLoader` works fine here, under the Parser indentation 
    #(in its namespace, but outside of method)
    copy_of_FileLoader = FileLoader

    #
    def __init__(self, fileLoader):
         # you need to refer to modules under in Parser namespace
         # with that `self`, just like you would with any other
         # class or instance variable 
         if isinstance(fileLoader, self.FileLoader.FileLoader)
             self._fileLoader = fileLoader
         else:
             # raise a nice exception

    #works here again, since we are outside of method,
    #in `Parser` scope/indent.
    copy2_of_FileLoader = FileLoader

Second it's not Pythonic and it doesn't help

Customary for the Python community would be to put import FileLoader at the top of the program. Since it seems to be one of your own modules, it would go after std library imports and after third party module imports. You would not put it under a class declaration.

Unless... you had a good (probably bad actually reason to).

My own code, and this doesn't reflect all that well on me, sometimes has stuff like.

class MainManager(batchhelper.BatchManager):
    ....

    def _load(self, *args, **kwargs):
      from pssystem.models import NotificationConfig

So, after stating this wasn't a good thing, why am I doing this?

Well, there are some specific circumstances to my code going here. This is a batch, command-line, script, usable within a Django context and it uses some Django ORM models. In order for those to be used, Django needs to be imported first and then setup. But that often happens too early in the context of these types of batch programs and I get circular import errors, with Django complaining that it hasn't initialized yet.

The solution? Defer execution until the method is called, when all the other modules have been imported and Django has been setup elsewhere.

NotificationConfig is now available, but only within that method as it is a local variable in it. It works, but... it's really not great practice.

Remember: anything in the global scope gets executed at module load time, anything under classes at module load time, anything withing method/function bodies when the method/function is called.

#happens at module load time, you could have circular import errors
import X1 

class DoImportsLater:
    .

    #happens at module load time, you could have circular import errors
    import X2

    def _load(self, *args, **kwargs):
        #only happens when this method is called, if ever
        #so you shouldn't be seeing circular imports
        import X3

  • import X1 is std practice, Pythonic.

  • import X2, what are doing, is not and doesn't help

  • import X3, what I did, is a hack and is covering up circular import references. But it "fixes" the issue.

JL Peyret
  • 10,917
  • 2
  • 54
  • 73