3

I have been trying to understand how to properly call a function from inside a subpackage in python. I wanted to be able to call the function the way I call, for example, function isfile from os package, with os.path.isfile(). I made a test package with a structure like this:

sandbox/
    -- __init__.py
    -- acid.py
    -- pack1/
        -- __init__.py
        -- fly/
            -- __init__.py
            -- plane.py
        -- by/
    -- pack2/

There are only two modules there, acid.py and plane.py. Both of them contain just a function, e.g. plane.py is

"""plane module"""
def plane(x):
    x=x+4
    return x

To use the function in my test.py code, I put

import pack1

in sandbox/__init__.py

import fly 

in sandbox/pack1/__init__.py, and

from plane import plane

in sandbox/pack1/fly/__init__.py

The test code was then:

import sandbox
print sandbox.pack1.fly.plane(3)

Is this the right way to import a function from a subpackage, or I'm misunderstanding things?

user3653831
  • 235
  • 6
  • 19
  • See your sandbox is now not only a directory its a python package (because you created __init__.py file) so you don't have to worry about calling a function from a package just use a '.' operator. – Archu Sm Sep 20 '17 at 13:31
  • So what i am saying is you don't have to import sub packages inside the __init__.py file in every directory. Your test code will work even without it. Empty your all __init__.py files and run the test.py. It will work I think. The __init__.py file is working as an initializer in the package – Archu Sm Sep 20 '17 at 13:33
  • If I empty the init files I get `AttributeError: 'module' object has no attribute 'pack1'`. This is why I arrived at this weird solution. – user3653831 Sep 20 '17 at 15:49
  • Empty the init files means, You need init.py in the directory, just clear the imports, don't delete the __init__.py files. – Archu Sm Sep 21 '17 at 07:24
  • @ArchuSm I did empty the __init__ files. I also tried importing only in `sandbox/__init__.py`. I put there `import pack1.fly.plane` and ran the test file with `print sandbox.pack1.fly.plane.plane(3)` and that worked. – user3653831 Sep 21 '17 at 08:25

2 Answers2

4

What you did certainly works, although there are several worthwhile changes to make.

First, a note about importing from packages: importing a module is semantically distinct from accessing something in a module, even though from xml import sax and from datetime import date are syntactically equivalent. It's impossible to import only part of a module, so that

import datetime
datetime.date.today()  # OK: date is a class

is guaranteed to work. However, it is possible to import a package but not the modules it contains. This is a good thing for efficiency, but it does mean that

import xml
xml.sax.parse(...)     # AttributeError: 'module' object has no attribute 'sax'

is an error. Unfortunately, such errors often go uncaught because some other code has already imported sax, making it available to any other code that imports xml. (The word "from" in from xml import sax is referring to the complete package on disk, not the module object xml — on which it stores the new module as an attribute!)


As an aside, note that your example of os.path is an abberation: writing

    import os
    os.path.isfile(...)

works, but only because os.path is not actually a module but an alias for one of posixpath, ntpath, etc. (It then gets installed in sys.modules to allow import os.path as if it were a normal module.)


As such, for a package there is a set of public modules that the user must be aware of (because they must be imported by name to be available); the rest are internal modules that the package loads itself when necessary. If a package contains no public modules, it is irrelevant to the user that it is a package (for example, importlib with its one public function is actually implemented as a package for forward compatibility with Python 3).

Now for the suggestions:

  1. Implicit relative imports are deprecated: write from . import pack1 instead of just import pack1 in sandbox/__init__.py, for instance.
  2. The from plane import plane (or from .plane import plane, following the above point) is problematic because it overwrites the reference to the module plane.py with a reference to the function. Instead:
    1. Define the user-visible entry points (like plane()) directly in their package's __init__.py, importing internal functions from private modules as needed, or
    2. Rename the module (to plane_module.py or so) to avoid the collision.
  3. However, it's not generally a good idea to have a package automatically import its public modules anyway: it forces the client to pay for loading unused parts of the package, and it blurs the distinction between public modules and simple nested names. Instead, write client code like

    import sandbox.pack1.fly
    print sandbox.pack1.fly.plane(3)    # the same line you had
    

    or

    from sandbox.pack1 import fly
    print fly.plane(3)
    

    if you want to avoid repeating sandbox.pack1.

  4. It is often suggested that __init__.py be entirely empty, and in Python 3.3 it became possible to define packages without any __init__.py at all (which by necessity "makes it empty"). This policy does more than the "no automatic import" suggestion in that it precludes loading things from private modules (like plane).

    There are sometimes good reasons to have a non-empty __init__.py; for example, it allows reorganzing an existing module into a package without breaking its clients. I personally see no reason to especially restrict its contents; for further discussion see What is __init__.py for?.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
1

The init.py file makes a folder as a package so that you can import it to python prompt. If you just want to call the function "plane" from your file plane.py, add the absolute path of plane.py file to your PYTHONPATH and call the funtion as shown below.

>>> import sys
>>> sys.path.append("D:\\sandbox\\pack1\\fly")
>>> import plane
>>> print plane.__doc__
plane module
>>> print plane.plane(3)
7

If you want all the packages under "sandbox" folder to be used in your script just add the absolute path of "sandbox" folder to your PYTHONPATH and call the funtion as shown below.

>>> import sys
>>> sys.path.append("D:\\")
>>> from sandbox.pack1.fly import plane
>>> print plane.plane(3)
7

You can also import the "plane.py" module and call the function "plane" as shown below:

>>> import sys
>>> sys.path.append("D:\\")
>>> import sandbox.pack1.fly.plane
>>> print sandbox.pack1.fly.plane.plane(3)
7
Raju Pitta
  • 606
  • 4
  • 5