Let’s say I have the following structure:
my_module/
__init__.py
utilities.py
and __init__.py
contains
from .utilities import SomeUtilityFunction
Is there a way to prevent or alert developers when they do
from my_module.utilities import SomeUtilityFunction
instead of
from my_module import SomeUtilityFunction
The problem arose when a few modules started using a function that was imported inside a module in which it wasn’t used, while also being available on the module’s __init__.py
, so after linting the file and removing the unused import my tests started failing.
any other advice for situations like this?
You could guard it.
__init__.py
:_GUARD_SOME_UTILITY_FUNCTION = True from .utilities import SomeUtilityFunction
utilities.py
:def SomeUtilityFunction(): if not _GUARD_SOME_UTILITY_FUNCTION: raise SomeException("Helpful error message")
Take this with a grain of salt, as I'm typing this on my phone and haven't actually tried it.
Alternatively there's the
import-guard
package on PyPI. No idea if it's any good, though. Just something a quick search brought up.Edit:
Ok, I tried my suggestion and it doesn't work.This approach seems quite overkomplex. Instead of having these errors on runtime, stuff like this should sit in linter rules of any kind.
It's only useful during development there.
@fixmycode mypy type checking can report this error in your code:
iox3.py:3: error: Module “iox2” does not explicitly export attribute “y” [attr-defined]
which I think is roughly the problem you are encountering: an attribute in an imported module that wasn’t explicitly defined in that module, but instead came from somewhere else.
I think this is the more sensitive approach, I’ll take a look at putting mypy in my pipeline
I am normally define the interface to models while defining
__all__
.Defined in the
__init__
it allows to define a whitelist what can be Imported from the outside.https://docs.python.org/3/tutorial/modules.html#importing-from-a-package
That's not correct.
__all__
is not a whitelist. It is only the list used forfrom module import *
If you have a module with submodules
foo
,bar
andbaz
and__all__ = ["foo", "bar"]
it will not prevent you from importingbaz
manually. It just won't do it automatically.It works exactly like one. You get a warning if you try to import something not defined in it. The docs are just very confusing here ;)
Bullshit!
module/__init__.py
:__all__ = ["foo", "bar"]
module/foo.py
:def foo(): print("foo")
module/bar.py
:def bar(): print("bar")
module/baz.py
:def baz(): print("baz")
main.py
:from module import * from module import baz if __name__ == "__main__": print("main") foo.foo() bar.bar() baz.baz()
Output:
$ python main.py main foo bar baz
No errors, warnings or anything.
You're running python without linters? Interesting approach.
You can't expect the user to have one.
@Chais
from module import \*
should almost never be used anyway, so…Renders correctly for me