python: import vs import as
some python devs may think import
and import as
statements behave exactly
the same, except that import as
binds the imported module to a different name;
but this is not very true (in python 3.6);
assuming package layout:
sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
here is the test case:
import sound.effects
del sound.effects
#import sound.effects.echo # pass
#import sound.effects.echo as echo2 # fail
#from sound.effects import echo # pass
#from sound.effects import echo as echo2 # pass
you will find import
will pass but import as
will fail; this is an
implementation detail: import as
generates additional bytecodes: LOAD_ATTR
;
lets dis
these statements in python 3.6:
>>> dis.dis('import sound.effects.echo')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (sound.effects.echo)
6 STORE_NAME 1 (sound)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
>>> dis.dis('import sound.effects.echo as echo2')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (sound.effects.echo)
6 LOAD_ATTR 1 (effects)
8 LOAD_ATTR 2 (echo)
10 STORE_NAME 3 (echo2)
12 LOAD_CONST 1 (None)
14 RETURN_VALUE
>>> dis.dis('from sound.effects import echo')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('echo',))
4 IMPORT_NAME 0 (sound.effects)
6 IMPORT_FROM 1 (echo)
8 STORE_NAME 1 (echo)
10 POP_TOP
12 LOAD_CONST 2 (None)
14 RETURN_VALUE
>>> dis.dis('from sound.effects import echo as echo2')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('echo',))
4 IMPORT_NAME 0 (sound.effects)
6 IMPORT_FROM 1 (echo)
8 STORE_NAME 2 (echo2)
10 POP_TOP
12 LOAD_CONST 2 (None)
14 RETURN_VALUE
of these 4 import statements, only import as
generates the LOAD_ATTR
bytecode; because we have deleted the attribute from the parent module, this
bytecode will fail;
this bug creates a discrepancy between import
and import as
, which is bad
and misleading; so it has been fixed in python 3.7:
>>> dis.dis('import sound.effects.echo')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (sound.effects.echo)
6 STORE_NAME 1 (sound)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
>>> dis.dis('import sound.effects.echo as echo2')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (sound.effects.echo)
6 IMPORT_FROM 1 (effects)
8 ROT_TWO
10 POP_TOP
12 IMPORT_FROM 2 (echo)
14 STORE_NAME 3 (echo2)
16 POP_TOP
18 LOAD_CONST 1 (None)
20 RETURN_VALUE
>>> dis.dis('from sound.effects import echo')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('echo',))
4 IMPORT_NAME 0 (sound.effects)
6 IMPORT_FROM 1 (echo)
8 STORE_NAME 1 (echo)
10 POP_TOP
12 LOAD_CONST 2 (None)
14 RETURN_VALUE
>>> dis.dis('from sound.effects import echo as echo2')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('echo',))
4 IMPORT_NAME 0 (sound.effects)
6 IMPORT_FROM 1 (echo)
8 STORE_NAME 2 (echo2)
10 POP_TOP
12 LOAD_CONST 2 (None)
14 RETURN_VALUE
bytecode LOAD_ATTR
has been replaced with IMPORT_FROM
, which basically does
the same thing, but doesnt try to read from the attribute; so if you run the
above test case in python 3.7, all 4 statements will pass;
note that there is a minor difference with IMPORT_FROM
: it leaves the parent
module on the stack; so this needs to be manually popped off with additional
bytecodes ROT_TWO
and POP_TOP
; these 2 bytecodes interchange the top 2 items
and then pop top item, the end result is the 2nd item is popped;