In julia, we have both import and using.
In python, it seems that we have only import.
What is the counterpart of using in python?
In julia, we have both import and using.
In python, it seems that we have only import.
What is the counterpart of using in python?
It would help if you said what the difference was in Juila!
In Python, import math
makes a local name math
that refers to the module math
so that you can then refer things in it, e.g. math.sin
, math.cos
.
The alternative form is from math import sin, cos
which makes local names sin
and cos
that refer to sin
and cos
from the module math
.
It’s also possible to import all of the public names from module math
using from math import *
, but this is discouraged.
For reference re Julia:
https://docs.julialang.org/en/v1/manual/modules/
Julia’s simple using
apparently brings both the module name and the components on its “export list” (roughly equivalent to __all__
, except it apparently has special syntactic support instead of relying on a “magic” attribute with special semantics) into the current namespace. As such, the closest equivalent is something like
import numpy
from numpy import *
However, the use of star-imports is much less common in Python, as is the use of explicit __all__
to customize the behaviour of a star-import.
Julia’s using
with an explicit list is straightforward:
from numpy import byte, sin, zeros # arbitrarily chosen for the example
Julia’s import
and using
also have implications for the semantics of adding “methods” (this seems to mean specializations based on the static type of the arguments?) to functions. Python’s functions don’t have this, so such distinctions are moot. The standard library provides a dispatch decorator that works on the dynamic type of a single argument; decorators are just syntactic sugar for calling a higher-order function, and this one dynamically creates a new function object. (The functions thus decorated then gain their own, function-specific decorator for “registering” dispatch functions - this looks to me roughly equivalent to Julia’s “methods” but dynamically typed. In Python, “method” only means a function scoped to within a class, or more precisely, looked up on an instance of that class.)
If an imported function was already using that decorator, nothing would prevent the local code from registering special-case dispatch, and such registration would normally affect other code within the same process that had imported the module (because module imports are cached, such that everyone who imports the same module gets the same object). It would not matter how the initial import was done, because it’s the same, common object being modified.
However, an ordinarily-defined function wouldn’t have that functionality. To register dispatches, it would have to be decorated with the dispatch functionality first, which creates a new object. This can be done after the fact (without the decorator syntax), but it would not modify the source module in any way.
The source module can be modified explicitly by any code that can refer to that actual object:
from functools import singledispatch
import other_code
other_code.a_function = singledispatch(other_code.a_function)
@other_code.a_function.register(int)
def specialization(value):
...
Again, all other code which imports the other_code
module will “see” that change. Note that from other_code import a_function
is importing other_code
- it just isn’t putting that name into the namespace. It will rely on the same cache of module objects, and look up the other_code
attribute from that module to dump into the current namespace.
But we couldn’t make this initial change after just from other_code import a_function
, because we wouldn’t have a way to refer to the other_code
module so as to modify it.
The import syntax in Python is just syntactic sugar for some variable assignment, on top of the underlying machinery for actually looking up or creating a module
object. Examining the bytecode:
>>> import dis
>>> dis.dis('import numpy')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (None)
4 IMPORT_NAME 0 (numpy)
6 STORE_NAME 0 (numpy)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
>>> dis.dis('from numpy import byte, sin, zeros')
1 0 LOAD_CONST 0 (0)
2 LOAD_CONST 1 (('byte', 'sin', 'zeros'))
4 IMPORT_NAME 0 (numpy)
6 IMPORT_FROM 1 (byte)
8 STORE_NAME 1 (byte)
10 IMPORT_FROM 2 (sin)
12 STORE_NAME 2 (sin)
14 IMPORT_FROM 3 (zeros)
16 STORE_NAME 3 (zeros)
18 POP_TOP
20 LOAD_CONST 2 (None)
22 RETURN_VALUE
IMPORT_FROM
is basically just doing an attribute assignment. IMPORT_NAME
is implemented by the built-in __import__
function. However, this code can’t be directly exactly emulated with function calls and assignments, because import from
only makes one IMPORT_NAME
call and also doesn’t store the result anywhere except the virtual machine’s stack.