Python Language Modularization: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(98 intermediate revisions by the same user not shown)
Line 7: Line 7:
=Internal=
=Internal=
* [[Python Language#Modularization|Python Language]]
* [[Python Language#Modularization|Python Language]]
=TODO=
* [[Python_Module_Internal_Representation_and_Introspection#Overview|Module Internal Representation and Introspection]]
<font color=darkkhaki>
* https://packaging.python.org/en/latest/tutorials/packaging-projects/
* Expand [[#Importing_.2A_from_a_Package|Importing * from a Package]]
* Expand [[#Subpackages|Subpackages]]
** PROCESS: https://docs.python.org/3/tutorial/modules.html
** PROCESS: https://docs.python.org/3/reference/import.html
* [[PyOOP]] "Modules and packages"
* [[PyOOP]] "Organizing modules"
* [[PyOOP]] "Absolute imports"
* [[PyOOP]] "Relative imports"
* [[PyOOP]] "Organizing module content"
* [[PyOOP]] "Third-party libraries" + "Case Study"
</font>


=<span id='Library'></span>Overview=
=<span id='Library'></span>Overview=
Python code can be organized as [[#Standalone_Program|standalone programs]], [[#Module|modules]] and [[#Package|packages]]. When a module or a package is published, people refer to it as a library. In this context, the term library is simply a generic term for a bunch of code that was designed with the aim of being [[Designing_Modular_Systems#Reuse|reused]] by many applications. It provides some generic functionality that can be used by specific applications.  
Python code can be organized as [[#Standalone_Program|standalone programs]], [[#Module|modules]] and [[#Package|packages]]. When a module or a package is [[#Publishing_a_Python_Package_in_a_Repository|published]], people refer to it as a library. In this context, the term library is simply a generic term for a bunch of code that was designed with the aim of being [[Designing_Modular_Systems#Reuse|reused]] by many applications. It provides some generic functionality, usually in form of functions and classes, that can be used by specific applications.  


Modular programming refers to the process of braking a large unwieldy body of code into separate, smaller, more manageable modules. Individual modules can the be combined into creating a larger application. More details about modular programming is available in [[Designing_Modular_Systems#Overview|Designing Modular Systems]] article.
Modular programming refers to the process of braking a large unwieldy body of code into separate, smaller, more manageable modules. Individual modules can the be combined into creating a larger application. More details about modular programming is available in [[Designing_Modular_Systems#Overview|Designing Modular Systems]] article.
Line 45: Line 32:
print('hello2')
print('hello2')
EOF
EOF
</syntaxhighlight>
</syntaxhighlight>


The same approach can be taken when Python code needs to be executed from within a bash script, for more details see: {{Internal|Calling_Python_from_bash#Inline_Python_Code|Calling Python from bash &#124; Inline Python Code}}
A stable and flexible way of executing Python programs in command line is invoking them from a thin bash wrapper that prepares the environment variables and other settings. This approach is described here:
 
==<span id='__main__'></span>Distinguishing between Importing and Executing a Module==
{{External|https://docs.python.org/3/library/__main__.html}}
 
Note that if you want to maintain a module that can be imported '''and''' executed as a script, the module must not contain executable code outside functions and classes. If it does, that code will be executed when the module is imported, which is generally not what you want.
 
To distinguish between the case when the file is loaded as a module and it is executed as a script, Python sets the <code>__name__</code> variable to different values. When the module is imported, <code>__name__</code> is set to the module name. When the module is executed as a script, the variable value is set to "__main__".


As such, this fact can be used to wrap the executable code in a function that is only executed when the module is executed as a script:
{{Internal|Calling_Python_from_bash#Running_a_Python_Program_with_a_Bash_Wrapper|Running a Python Program with a Bash Wrapper}}
<syntaxhighlight lang='py'>
...
 
def main():
# this code is executed when the module is executed as script
print("...")
 
if __name__ == '__main__':
  main()
</syntaxhighlight>


=<span id='Module'></span>Modules=
=<span id='Module'></span>Modules=
There are three different ways to define a module in Python:
There are three different ways to define a module in Python:
* A module can be written in Python itself, with the code living in one code file.
* A module can be written in Python itself, with its code contained in one single code file.
* A module can be written in C and loaded dynamically at runtime. This is the case of the regular expression [[Python_Module_re#Overview|re]] module.
* A module can be written in C and loaded dynamically at runtime. This is the case of the regular expression [[Python_Module_re#Overview|re]] module.
* A module can be intrinsically contained by the interpreter. This is called a [[#Built-in_Modules|built-in module]].
* A module can be intrinsically contained by the interpreter. This is called a [[#Built-in_Modules|built-in module]].


The modules written in Python are the most common, and they are the type of modules Python developer usually write. They are exceedingly straightforward to build: just place Python code in a file with the <code>py</code>. A Python module consists of '''one''' file. The module can be [[#Importing|imported]] inside another Python program or executed on its own. The module can define object instances such as strings or list, and that are assigned to variables, and also functions and classes. If intended to run on its own, the module will also include runnable code.  
The modules written in Python are the most common, and they are the type of modules Python developer usually write. They are exceedingly straightforward to build: just place Python code in a file with the <code>py</code>. Modules are simple Python files: a module consists of '''just one''' file. The module can be [[#Importing|imported]] inside another Python program or executed on its own. The module can define object instances such as strings or lists, which are assigned to variables, and also functions and classes. If intended to run on its own, the module will also include runnable code.  


The file name is the name of the module with the suffix <code>.py</code> appended. Modules are loaded into Python by the process of [[#Import|importing]], where the name of the objects present in one module are made available in the [[Python_Language#Variables_Namespace_and_Scope|namespace]] of the calling layer.  
The file name is the name of the module with the suffix <code>.py</code> appended. Modules are loaded into Python by the process of [[#Import|importing]], where the name of the objects present in one module are made available in the [[Python_Language#Variables_Namespace_and_Scope|namespace]] of the calling layer.  


Each module has its own [[Python_Language#Variables_Namespace_and_Scope|global namespace]], where all objects defined inside the module - strings, lists, functions, classes, etc. - are identified by unique names.
<span id='Global_Namespace'></span>Each module has its own [[Python_Language#Variables_Namespace_and_Scope|global namespace]], where all objects defined inside the module (strings, lists, functions, classes, etc.) are identified by unique names.
 
A module may exist as part of a [[#Package|package]].
==Module Name==
==Module Name==
Naming conventions are documented here: https://www.python.org/dev/peps/pep-0008/#id36: Modules should have short, all-lowercase names. Underscores can be used in the module name if it improves readability. The name of the module cannot contain dashes ('-'). The name of the directory the module is stored in can contain dashes.
Naming conventions are documented here
 
{{External|[https://peps.python.org/pep-0008/#package-and-module-names PEP 8 – Style Guide for Python Code, Package and Module Names]}}
 
Modules should have short, all-lowercase names. Underscores can be used in the module name if it improves readability. The name of the module cannot contain dashes ('-'). The name of the directory the module is stored in can contain dashes.
 
==Module Docstring==
Contains documentation for the module. Added on the first line:
 
<syntaxhighlight lang='py'>
"""
This module does this and that.
"""
</syntaxhighlight>
 
==Built-in Modules==
==Built-in Modules==
The built-in modules are contained by the Python interpreter.
The built-in modules are contained by the Python interpreter.
* [[Python Module intertools#Overview|intertools]]
* [[Python Module intertools#Overview|intertools]]
==Module Internal Representation and Introspection==
The modules, once loaded, can be introspected for their attributes, as instances of the <code>module</code> class. For more details, see:
{{Internal|Python_Module_Internal_Representation_and_Introspection#Overview|Module Internal Representation and Introspection}}


=<span id='Import'></span>Importing=
=<span id='Import'></span>Importing=
Importing means making accessible names available in a [[#Module|module]] or a [[#Package|package]] into the namespace associated with the code that invokes the <code>import</code> statement. The import operation binds the imported module's name into the namespace associated with the calling layer.
Importing means making accessible names available in a [[#Module|module]] or a [[#Package|package]] into the [[Python_Language#Namespace|namespace]] associated with the code that invokes the <code>import</code> statement. The import operation binds the imported module's name into the namespace associated with the calling layer. All module-level code is executed immediately at the time it is imported. In case of functions, the function will be created, but its code will not be executed until the function is called.


==<span id='Importing_All_The_Code_from_a_Module'></span><span id='Importing_an_Entire_Module'></span>Importing a Module==
==<span id='Importing_All_The_Code_from_a_Module'></span><span id='Importing_an_Entire_Module'></span>Importing a Module==
Line 96: Line 84:
import mymodule
import mymodule
</syntaxhighlight>
</syntaxhighlight>
This statement does nothing except binding the specified module name into the current namespace, this potentially enabling access to the imported module global namespace. The objects that are defined in the imported module remain in the module’s private symbol table.
 
A module is importable if the file corresponding to the module being imported is accessible to the Python interpreter. For more details on how the Python interpreter finds modules see [[#Locating_Module_Files_-_Module_Search_Path|Locating Module Files - Module Search Path]].
 
The <code>import mymodule</code> statement does nothing except binding the specified module name into the current namespace, thus potentially enabling access to the imported module global namespace. The objects that are defined in the imported module remain in the module’s private symbol table.
<syntaxhighlight lang='python'>
<syntaxhighlight lang='python'>
print(globals())
print(globals())
Line 104: Line 95:
'mymodule': <module 'mymodule' from '/Users/ovidiu/playground/pyhton/modules/mymodule.py'>
'mymodule': <module 'mymodule' from '/Users/ovidiu/playground/pyhton/modules/mymodule.py'>
</syntaxhighlight>
</syntaxhighlight>
For more details on accessing a module global namespace see <code>[[Python_Language#Global_Symbol_Table_and_globals.28.29|<tt>globals()</tt>]]</code>.


However, this allows the Python code from the current scope to use the module name to access objects contained by the module. Assuming that <code>mymodule</code> declares a function <code>some_func</code>, the function can be invoked by prefixing its name with the name of the module, using the dot notation. This is called qualifying the internal names of a module with the module's name. The function will be looked up in the <code>mymodule</code> global namespace, which is made accessible to the calling layer as <code>mymodule</code>:
However, binding the module name into the current namespace allows the Python code from the current scope to use the module name to access objects contained by the module. Assuming that <code>mymodule</code> declares a function <code>some_func</code>, the function can be invoked by prefixing its name with the name of the module, using the dot notation. This is called qualifying the internal names of a module with the module's name. The function will be looked up in the <code>mymodule</code>'s global namespace, which is made accessible to the calling layer as <code>mymodule</code>. The same applies to other objects declared by the module, such as variables, classes, etc.:
<syntaxhighlight lang='python'>
<syntaxhighlight lang='python'>
import mymodule
import mymodule
Line 112: Line 104:
</syntaxhighlight>
</syntaxhighlight>


The file corresponding to the module being imported must be accessible, as described in [[#Locating_Module_Files|Locating Module Files]].
Once imported, the file associated with the module can be determined using the module object's <code>__file__</code> attribute:
 
{{Internal|Python_Module_Internal_Representation_and_Introspection#file|Module Internal Representation &#124; <tt>__file__</tt>}}
Once imported, the file associated with the module can be determined using the module object's <code>__file__</code> attribute. The directory portion of <code>__file__</code> should be one of the directories in <code>[[#sys.path|sys.path]]</code>.
<syntaxhighlight lang='python'>
import mymodule
 
[...]
print(mymodule.__file__)
</syntaxhighlight>
 
Multiple comma-separated module names can be specified in the same import statement, but various static analysis programs flag this as a style violation:
Multiple comma-separated module names can be specified in the same import statement, but various static analysis programs flag this as a style violation:
<syntaxhighlight lang='python'>
<syntaxhighlight lang='python'>
import mymodule, mymodule2 # The style checker flags this as a style violation
import mymodule, mymodule2 # The style checker flags this as a style violation
</syntaxhighlight>
</syntaxhighlight>
<span id='Absolute_Import'></span>It most cases, we use the '''absolute import''' syntax, where we specify the complete path to the module, function or class we want to import. The absolute import statement uses the period operator to separate the name of the packages or modules. The alternative is to use [[#Relative_Import|relative import]] syntax.
===<span id='Importing_a_Module_from_a_Function'></span>Importing a Module from a Function or Class===
===<span id='Importing_a_Module_from_a_Function'></span>Importing a Module from a Function or Class===
A module can be imported in the global namespace of another module, or inside a function or a class. You should consider importing in the global namespace if the imported code might be used in more than one place, and from inside of the function or class if you know its use will be limited. Putting all imports at the top of the file makes all dependencies of the importing module code explicit.
A module can be imported in the global namespace of another module, or inside a function or a class. You should consider importing in the global namespace if the imported code might be used in more than one place, and inside of the function or class if you know its use will be limited. Putting all imports at the top of the file makes all dependencies of the importing module code explicit.


While importing into a function's namespace, all that the <code>import</code> statement does is to bind the specified module's namespace into the [[Python_Language#Local_Namespaces_and_Variables|local namespace]] of the function. To be accessed, the imported module's object must be qualified with the name of the module. Note that the import does not occur until the function is called:
While importing into a function's namespace, all that the <code>import</code> statement does is to bind the specified module's namespace into the [[Python_Language#Local_Namespaces_and_Variables|local namespace]] of the function. The imported module's objects must be qualified with the name of the module to be accessed. Note that the import does not occur until the function is called:
<syntaxhighlight lang='python'>
<syntaxhighlight lang='python'>
def some_func():
def some_func():
Line 139: Line 125:


===<span id='Import_As'></span>Importing a Module with Another Name===
===<span id='Import_As'></span>Importing a Module with Another Name===
The object of an imported module are qualified with the name of the module to be used. The prefix can be changed, usually to shorten it, by using the [[Python_Language#Reserved_Words|<code>as</code> reserved word]] in the import statement. This syntax effectively renames the module being imported in the namespace. The same technique is useful if there are two modules with the same name.
The objects of an imported module are qualified with the name of the module to be used. The prefix can be changed, usually to shorten it, by using the [[Python_Language#Reserved_Words|<code>as</code> reserved word]] in the import statement. This syntax effectively renames the module being imported in the namespace. The same technique is useful if there are two modules with the same name.
<syntaxhighlight lang='python'>
<syntaxhighlight lang='python'>
import mymodule as m
import mymodule as m
Line 153: Line 139:
'm': <module 'mymodule' from '/Users/ovidiu/playground/pyhton/modules/mymodule.py'>
'm': <module 'mymodule' from '/Users/ovidiu/playground/pyhton/modules/mymodule.py'>
</syntaxhighlight>
</syntaxhighlight>
For more details accessing a function's local namespace see <code>[[Python_Language#Local_Symbol_Table_and_locals.28.29|locals()]]</code>.


==<span id='Importing_only_a_Function'></span><span id='Importing_only_Specific_Constructs_from_a_Module'></span>Importing Specific Objects from a Module==
===Programmatic Import using the Module Name as String===
An alternate <code>import</code> statement syntax allow binding of specific objects into the caller's symbol table. When the <code>from</code>/<code>import</code> combination is used, the specified imported module objects are inserted into the caller's symbol table and are available in the caller's namespace under their original name. No qualification with dot notation is needed to access them.
So far, we used the <code>import</code> statement to import modules by their name, which is provided as part of the statement. Modules can also be imported dynamically in the program by using the module name as string, or the file the module exists in:
<syntaxhighlight lang='python'>
====<tt>__import()__</tt>====
from mymodule import my_func, my_func_2
<code>[[Python_Language_Functions#import|__import__()]]</code> is a built-in function. Because this function is meant for use by the Python interpreter and not for general use, it is better to use <code>[[#importlib.import_module.28.29|importlib.import_module()]]</code> to programmatically import a module.
...
<syntaxhighlight lang='py'>
# invoke the function directly, without prefixing it with the name of the module
__import__('some_package.some_subpackage.some_module')
my_func()
</syntaxhighlight>
 
====<tt>importlib.import_module()</tt>====
Recommended idiom to import modules programmatically:
<syntaxhighlight lang='py'>
import importlib.import_module
module = importlib.import_module('some_name')
assert isinstance(module, types.ModuleType)
</syntaxhighlight>
</syntaxhighlight>


The objects can keep their original name, as shown above, or it can be aliased, which means a custom name is inserted in the caller's symbol table.
When the module is loaded as part of a package, use:
<syntaxhighlight lang='python'>
<syntaxhighlight lang='py'>
from mymodule import my_func as m_f
module_name = "..."
...
module = importlib.import_module(f'.{module_name}', package.__name__)
m_f()
</syntaxhighlight>
</syntaxhighlight>
⚠️ Because this form of import places the object names directly into the caller’s symbol table, any objects that already exist with the same name will be overwritten.


==Importing * from a Module==
====<span id='load_source.28.29'></span><tt>imp.load_source()</tt>====
<syntaxhighlight lang='python'>
<syntaxhighlight lang='py'>
from mymodule import *
import imp
module = imp.load_source('some_package.some_subpackage.some_module', '.../some_package/some_subpackage/some_module.py')
</syntaxhighlight>
</syntaxhighlight>
This syntax places the names of all objects from the imported module into the local symbol table, with the exception of those whose name begins with an underscore (_).This technique is not necessarily recommended in large-scale production code. Unless you are confident there won’t be a conflict, there is a chance of overwriting an existing name inadvertently, so it should be used with caution.
==Recommended Style==
Uses <code>import</code> statements for packages and modules only, not for individual classes or functions.
* Use <code>import x</code> for importing packages and modules.
* Use <code>from x import y</code> where <code>x</code> is the package prefix and <code>y</code> is the module name with no prefix.
* Use <code>from x import y as z</code> if two modules named <code>y</code> are to be imported, if <code>y</code> conflicts with a top-level name defined in the current module, or if <code>y</code> is an inconveniently long name.
* Use <code>import y as z</code> only when <code>z</code> is a standard abbreviation (e.g., np for numpy).
* <font color=darkkhaki>TO PROCES: https://google.github.io/styleguide/pyguide.html#2241-exemptions</code>


==<span id='sys.path'></span><span id='Module_Search_Path'></span>Locating Module Files - Module Search Path==
==<span id='sys.path'></span><span id='Module_Search_Path'></span>Locating Module Files - Module Search Path==
Line 229: Line 214:


PyCharm trick: If the module name and the parent directory have the same name, PyCharm will stop issuing static analysis error "No module named ..."
PyCharm trick: If the module name and the parent directory have the same name, PyCharm will stop issuing static analysis error "No module named ..."
===<span id='Append_to_site.addsitedir'></span><span id='Append_to_sys.path'></span>Programmatically modify <tt>ssite.addsitedir</tt>===
===<span id='Append_to_site.addsitedir'></span><span id='Append_to_sys.path'></span>Programmatically modify <tt>site.addsitedir</tt>===
Another way is to use <code>site.addsitedir</code> to add a directory to <code>sys.path</code>. The difference between this and just plain appending is that when you use <code>addsitedir</code>, it also looks for <code>.pth</code> files within that directory and uses them to possibly add additional directories to <code>sys.path</code> based on the contents of the files.
<code>site.addsitedir</code> can be used to add a directory to <code>sys.path</code>. The difference between this and just plain appending is that when you use <code>addsitedir</code>, it also looks for <code>.pth</code> files within that directory and uses them to possibly add additional directories to <code>sys.path</code> based on the contents of the files.
 
==<span id='Importing_only_a_Function'></span><span id='Importing_only_Specific_Constructs_from_a_Module'></span>Importing Specific Objects from a Module==
An alternate <code>import</code> statement syntax allow binding of specific objects into the caller's symbol table. When the <code>from</code>/<code>import</code> combination is used, the specified imported module objects are inserted into the caller's symbol table and are available in the caller's namespace under their original name. No qualification with dot notation is needed to access them.
<syntaxhighlight lang='python'>
from mymodule import my_func, my_func_2
...
# invoke the function directly, without prefixing it with the name of the module
my_func()
</syntaxhighlight>
 
The objects can keep their original name, as shown above, or it can be aliased, which means a custom name is inserted in the caller's symbol table.
<syntaxhighlight lang='python'>
from mymodule import my_func as m_f
...
m_f()
</syntaxhighlight>
⚠️ Because this form of import places the object names directly into the caller’s symbol table, any objects that already exist with the same name will be overwritten.
 
==Importing * from a Module==
<syntaxhighlight lang='python'>
from mymodule import *
</syntaxhighlight>
This syntax places the names of all objects from the imported module into the local symbol table, with the exception of those whose name begins with an underscore (_).This technique is not necessarily recommended in large-scale production code. Unless you are confident there won’t be a conflict, there is a chance of overwriting an existing name inadvertently, so it should be used with caution. Doing this will unnecessarily clutter the namespace. Not doing it makes the code easier to read: when we explicitly import a class or a function with <code>from x import y</code> syntax, we can easily see where <code>y</code> comes from. However, if we use <code>from x import *</code> syntax, it takes a lot longer to find where <code>y</code> is located. In addition, most code editors are able to provide code completion, ability to navigate to the definition of a class or inline documentation if normal imports are used. The <code>import *</code> syntax usually removes this capabilities. Finally, <code>import *</code> syntax can bring unexpected objects into the target namespace, because it will import any classes or modules that were themselves imported in the file being imported.
 
Explicit is better than implicit.
 
==Recommended Style==
{{External|https://google.github.io/styleguide/pyguide.html#22-imports}}
Uses <code>import</code> statements for packages and modules only, not for individual classes or functions.
* Use <code>import x</code> for importing packages and modules.
* Use <code>from x import y</code> where <code>x</code> is the package prefix and <code>y</code> is the module name with no prefix.
* Use <code>from x import y as z</code> if two modules named <code>y</code> are to be imported, if <code>y</code> conflicts with a top-level name defined in the current module, or if <code>y</code> is an inconveniently long name.
* Use <code>import y as z</code> only when <code>z</code> is a standard abbreviation (e.g., np for numpy).
* <font color=darkkhaki>TO PROCES: https://google.github.io/styleguide/pyguide.html#2241-exemptions</font>
 
==Handling Unsuccessful Imports==
==Handling Unsuccessful Imports==
An unsuccessful import attempt, caused by the unavailability of the imported module, can be caught in a <code>try</code> block:
An unsuccessful import attempt, caused by the unavailability of the imported module, can be caught in a <code>try</code> block:
Line 248: Line 268:
==Reloading a Module==
==Reloading a Module==
<font color=darkkhaki>TO PROCESS: https://realpython.com/python-modules-packages/#reloading-a-module</font>
<font color=darkkhaki>TO PROCESS: https://realpython.com/python-modules-packages/#reloading-a-module</font>
==Relative Imports==
==<span id='Relative_Import'></span>Relative Imports==
 
Relative import syntax is an alternative to [[#Absolute_Import|absolute import]]. This syntax is useful when working with related module inside a package, that are stored in a know relative position to each other. Relative import is a way of saying find a class, function or module as it is positioned relative to the current module.
 
In this situation:
<syntaxhighlight lang='text'>
some_package
    ├─ module_1.py
    └─ module_2.py
</syntaxhighlight>
where <code>module_1</code> declares <code>func_1()</code>, <code>func_1()</code> can be imported in <code>module_2</code> with a relative import:
 
<syntaxhighlight lang='py'>
from .module_1 import func_1
 
def func_2():
  func_1()
</syntaxhighlight>
The period in front of "module_1" says to sue the "module_1" module inside the current package.
In this situation:
<syntaxhighlight lang='text'>
some_package
    ├─ module_1.py
    └─ some_subpackage
            └─ module_2.py
</syntaxhighlight>
in <code>module_2</code> we can use:
<syntaxhighlight lang='py'>
from ..module_1 import func_1
</syntaxhighlight>
 
==<span id='__main__'></span>Distinguishing between Importing and Executing a Module==
{{External|https://docs.python.org/3/library/__main__.html}}
 
Note that if you want to maintain a module that can be imported '''and''' executed as a script, the module must not contain executable code outside functions and classes. If it does, that code will be executed when the module is imported, which is generally not what you want. To distinguish between the case when the file is loaded as a module and it is executed as a script, Python sets the <code>[[Python_Language#name|__name__]]</code> variable to different values. When the module is imported, <code>__name__</code> is set to the module name. When the module is executed as a script, the variable value is set to "__main__".
 
As such, this fact can be used to wrap the executable code in a function that is only executed when the module is executed as a script:
<syntaxhighlight lang='py'>
...
 
def main():
# this code is executed when the module is executed as script
print("...")
 
if __name__ == '__main__':
  main()
</syntaxhighlight>
Also see: {{Internal|Python_Module_Internal_Representation_and_Introspection#name|<tt>__name__</tt>}}
===How to Execute (Run) a Module?===
<font color=darkkhaki>
Document the <code>-m</code> flag.</font>
<syntaxhighlight lang='py'>
python -m <module-name> ....
</syntaxhighlight>


=Package=
=Package=
A package is Python code stored into multiple files, organized in a file hierarchy.


{{Internal|Python Packages#Overview|Python Packages}}
Since we cannot put modules inside modules, because a module is just a file, and a file can hold only one file after all, Python offers the package mechanism. A package is a collection of [[#Module|modules]], and optionally [[#Subpackage|subpackages]], recursively, in a folder. The name of the package is the name of the folder.


A package is Python code stored into multiple files, organized in a file hierarchy. A package may contain multiple [[#Module|modules]], each stored in its own file, either in the package root directory or recursively in subdirectories. The package root directory may also contain two optional files named <code>[[#init_.py|__init__.py]]</code> and <code>[[#main_.py|__main__.py]]</code>. Packages allow for a hierarchical structuring of the module namespace using dot notation. In the same way that modules avoid collisions between global variable names, packages avoid collision between module names. For example, the [[Python_Package_urllib|urllib]] package contains several modules: <code>urllib.request</code>, <code>urllib.error</code>, etc.
A package may contain multiple modules, each stored in its own file, either in the package root directory or recursively in subdirectories. The package root directory may also contain two optional files named <code>[[#init_.py|__init__.py]]</code> and <code>[[#main_.py|__main__.py]]</code>. Packages allow for a hierarchical structuring of the module namespace using dot notation. In the same way that modules avoid collisions between global variable names, packages avoid collision between module names. For example, the [[Python_Package_urllib|urllib]] package contains several modules: <code>urllib.request</code>, <code>urllib.error</code>, etc. A package also allows for [[#Subpackage|subpackages]].
<span id='Package_Example_1'></span>
<span id='Package_Example_1'></span>
Technically, a package is a Python module with a <code>__path__</code> attribute. The <code>__path__</code> contains the path of the package root directory, where the component modules, subpackages, <code>__init__.py</code> and <code>__main__.py</code> live.
<font size=-1>
<font size=-1>
  some_dir
  some_dir
Line 271: Line 342:
               └─ some_module_4.py <font color=teal># Defines some_func_4()</font>
               └─ some_module_4.py <font color=teal># Defines some_func_4()</font>
</font>
</font>
A package also allows [[#Subpackage|subpackages]].
Internally, a package is represented as an instance of the <code>module</code> class. The difference between a package instance and a simple module instance is that the the package instance as an extra <code>[[Python_Module_Internal_Representation_and_Introspection#path|__path__]]</code> attribute. For more details on the internal representation of a package, see:
 
{{Internal|Python_Module_Internal_Representation_and_Introspection#Overview|Module Internal Representation and Introspection}}
==Importing a Package==
To import the modules of the package represented above, ensure that the directory <code>some_dir</code>, the parent of <code>some_package_1</code>, is in the [[#Locating_Module_Files_-_Module_Search_Path|module search path]] and use the following import statements, where a module is identified using dot notation relative to its package name:
To import the modules of the package represented above, ensure that the directory <code>some_dir</code>, the parent of <code>some_package_1</code>, is in the [[#Locating_Module_Files_-_Module_Search_Path|module search path]] and use the following import statements, where a module is identified using dot notation relative to its package name:
<syntaxhighlight lang='py'>
<syntaxhighlight lang='py'>
Line 307: Line 379:
==Package Name==
==Package Name==
See [[#Module_Name|module name]] above.
See [[#Module_Name|module name]] above.
==Package Kinds==
== Package Internal Representation and Introspection==
A package, once loaded, is represented internally as an instance of the  <code>module</code> class. For more details, see:
{{Internal|Python_Module_Internal_Representation_and_Introspection#Overview|Module Internal Representation and Introspection}}


==<span id='Package_Kinds'></span>Package Types==
===Import Package===
An import package, commonly referred to with a single word "package", is <font color=darkkhaki>?</font>. Import packages are different, as a concept, from [[#Distribution_Package|distribution packages]].
===Distribution Package===
===Regular Package===
===Regular Package===
A traditional package, such as a directory containing an <code>[[#init_.py|__init__.py]]</code> file.
A traditional package, such as a directory containing an <code>[[#init_.py|__init__.py]]</code> file.
====<tt>__init__.py</tt>====
====<tt>__init__.py</tt>====
When a regular package is imported, this <code>__init__.py</code> file is implicitly executed, and the objects it defines are bound to names in the package’s namespace. The <code>__init__.py</code> file can contain the same Python code that any other module can contain, and Python will add some additional attributes to the module when it is imported.  
When a regular package is imported, this <code>__init__.py</code> file is implicitly executed, and the objects it defines are bound to names in the package’s namespace. The <code>__init__.py</code> file can contain the same Python code that any other module can contain, like variables, function and class declarations, and Python will add some additional attributes to the module when it is imported.  


Much of the Python documentation states that the <code>__init__.py</code> file must be present in the package directory, even if as an empty file, for the package to be valid. This was once true. Since Python 3.3, [https://peps.python.org/pep-0420/ PEP 420 Implicit Namespace Packages] were introduced and they allow for the creation of a package without any <code>__init__.py</code> file.  
Much of the Python documentation states that the <code>__init__.py</code> file must be present in the package directory, even if as an empty file, for the package to be valid. This was once true. Since Python 3.3, [https://peps.python.org/pep-0420/ PEP 420 Implicit Namespace Packages] were introduced and they allow for the creation of a package without any <code>__init__.py</code> file.  
Line 352: Line 430:
some_package_1.dir_1.dir_2.some_module_4.some_func_4()
some_package_1.dir_1.dir_2.some_module_4.some_func_4()
</syntaxhighlight>
</syntaxhighlight>
It is not recommended to put much code in <code>__init__.py</code> file. Programmers to not expect actual logic to happen in this file.
====<tt>__main__.py</tt>====
====<tt>__main__.py</tt>====
{{External|https://docs.python.org/3/library/__main__.html}}
{{External|https://docs.python.org/3/library/__main__.html}}
Line 360: Line 441:
===Namespace Package===
===Namespace Package===
A PEP 420 package which serves only as a container for subpackages. Namespace packages may have no physical representation, and have no <code>__init__.py</code> file.
A PEP 420 package which serves only as a container for subpackages. Namespace packages may have no physical representation, and have no <code>__init__.py</code> file.
==<span id='Subpackage'></span>Subpackages==
{{External|https://realpython.com/python-modules-packages/#subpackages}}
A subpackage is a folder containing modules and optionally other subpackages, stored in a [[#Package|package]].


==Importing * from a Package==
==Importing * from a Package==
<font color=darkkhaki>TO PROCESS: https://realpython.com/python-modules-packages/#importing-from-a-package</font>
{{External|https://realpython.com/python-modules-packages/#importing-from-a-package}}
When <code>from <package_name> import *</code> is encountered, Python follows this convention: if the <code>__init__.py</code> file in the package directory contains a list named <code>__all__</code>, it is taken to be a list of modules that should be imported.


==Package Metadata==
==Package Metadata==
Line 380: Line 466:
===<tt>Requires</tt>===
===<tt>Requires</tt>===
===<tt>Required-by</tt>===
===<tt>Required-by</tt>===
==<span id='Subpackage'></span>Subpackages==
<font color=darkkhaki>TO PROCESS: https://realpython.com/python-modules-packages/#subpackages</font>
==Package Example==
==Package Example==
A package example is available here: {{External|https://github.com/ovidiuf/playground/tree/master/pyhton/packages/some_package_1}}
A package example is available here: {{External|https://github.com/ovidiuf/playground/tree/master/pyhton/packages/some_package_1}}
Line 388: Line 471:


=Publishing a Python Package in a Repository=
=Publishing a Python Package in a Repository=
<font color=darkkhaki>
TODO:
* Turning your package into a library.
* https://packaging.python.org/en/latest/tutorials/packaging-projects/
</font>
=Python Standard Library=
{{Internal|Python_Language#Python_Standard_Library|Python Language &#124; Python Standard Library}}
=<tt>site-packages</tt>=
See {{Internal|Python_Versions#pip_relationship_to_Python_Version|Python Versions &#124; pip Relationship to Python Version}}

Latest revision as of 22:48, 2 November 2023

External

Internal

Overview

Python code can be organized as standalone programs, modules and packages. When a module or a package is published, people refer to it as a library. In this context, the term library is simply a generic term for a bunch of code that was designed with the aim of being reused by many applications. It provides some generic functionality, usually in form of functions and classes, that can be used by specific applications.

Modular programming refers to the process of braking a large unwieldy body of code into separate, smaller, more manageable modules. Individual modules can the be combined into creating a larger application. More details about modular programming is available in Designing Modular Systems article.

Standalone Program

A standalone program consists of one or more files of code that is read by the Python interpreter and executed. A typical way to interact with a Python program is command line arguments. For more details on handling command line arguments, see:

Command Line Argument Processing in Python

Python Script

A script is a module whose aim is to be executed. It has the same meaning as "program", standalone program, or "application", but it is usually used to describe simple and small program. It contains a stored set of instructions that can be handed over to the Python interpreter:

python3 ./my-script.py

Python scripts have .py extensions.

The python code can be specified in-line with a here-doc:

python3 <<EOF
print('hello')
print('hello2')
EOF

A stable and flexible way of executing Python programs in command line is invoking them from a thin bash wrapper that prepares the environment variables and other settings. This approach is described here:

Running a Python Program with a Bash Wrapper

Modules

There are three different ways to define a module in Python:

  • A module can be written in Python itself, with its code contained in one single code file.
  • A module can be written in C and loaded dynamically at runtime. This is the case of the regular expression re module.
  • A module can be intrinsically contained by the interpreter. This is called a built-in module.

The modules written in Python are the most common, and they are the type of modules Python developer usually write. They are exceedingly straightforward to build: just place Python code in a file with the py. Modules are simple Python files: a module consists of just one file. The module can be imported inside another Python program or executed on its own. The module can define object instances such as strings or lists, which are assigned to variables, and also functions and classes. If intended to run on its own, the module will also include runnable code.

The file name is the name of the module with the suffix .py appended. Modules are loaded into Python by the process of importing, where the name of the objects present in one module are made available in the namespace of the calling layer.

Each module has its own global namespace, where all objects defined inside the module (strings, lists, functions, classes, etc.) are identified by unique names.

A module may exist as part of a package.

Module Name

Naming conventions are documented here

PEP 8 – Style Guide for Python Code, Package and Module Names

Modules should have short, all-lowercase names. Underscores can be used in the module name if it improves readability. The name of the module cannot contain dashes ('-'). The name of the directory the module is stored in can contain dashes.

Module Docstring

Contains documentation for the module. Added on the first line:

"""
This module does this and that.
"""

Built-in Modules

The built-in modules are contained by the Python interpreter.

Module Internal Representation and Introspection

The modules, once loaded, can be introspected for their attributes, as instances of the module class. For more details, see:

Module Internal Representation and Introspection

Importing

Importing means making accessible names available in a module or a package into the namespace associated with the code that invokes the import statement. The import operation binds the imported module's name into the namespace associated with the calling layer. All module-level code is executed immediately at the time it is imported. In case of functions, the function will be created, but its code will not be executed until the function is called.

Importing a Module

The import statement, usually listed at the top of the file, followed by the name of the module, binds the name of the module into the caller's symbol table. This way, the name of the imported module becomes available in the caller's namespace. The name of the module is the name of the file containing the module code, without the .py extension.

import mymodule

A module is importable if the file corresponding to the module being imported is accessible to the Python interpreter. For more details on how the Python interpreter finds modules see Locating Module Files - Module Search Path.

The import mymodule statement does nothing except binding the specified module name into the current namespace, thus potentially enabling access to the imported module global namespace. The objects that are defined in the imported module remain in the module’s private symbol table.

print(globals())

[...]

'mymodule': <module 'mymodule' from '/Users/ovidiu/playground/pyhton/modules/mymodule.py'>

For more details on accessing a module global namespace see globals().

However, binding the module name into the current namespace allows the Python code from the current scope to use the module name to access objects contained by the module. Assuming that mymodule declares a function some_func, the function can be invoked by prefixing its name with the name of the module, using the dot notation. This is called qualifying the internal names of a module with the module's name. The function will be looked up in the mymodule's global namespace, which is made accessible to the calling layer as mymodule. The same applies to other objects declared by the module, such as variables, classes, etc.:

import mymodule

mymodule.some_func()

Once imported, the file associated with the module can be determined using the module object's __file__ attribute:

Module Internal Representation | __file__

Multiple comma-separated module names can be specified in the same import statement, but various static analysis programs flag this as a style violation:

import mymodule, mymodule2 # The style checker flags this as a style violation

It most cases, we use the absolute import syntax, where we specify the complete path to the module, function or class we want to import. The absolute import statement uses the period operator to separate the name of the packages or modules. The alternative is to use relative import syntax.

Importing a Module from a Function or Class

A module can be imported in the global namespace of another module, or inside a function or a class. You should consider importing in the global namespace if the imported code might be used in more than one place, and inside of the function or class if you know its use will be limited. Putting all imports at the top of the file makes all dependencies of the importing module code explicit.

While importing into a function's namespace, all that the import statement does is to bind the specified module's namespace into the local namespace of the function. The imported module's objects must be qualified with the name of the module to be accessed. Note that the import does not occur until the function is called:

def some_func():
  import mymodule
  [...]
  mymodule.my_func()

Python 3 does not allow indiscriminate import * from within a function.

Importing a Module with Another Name

The objects of an imported module are qualified with the name of the module to be used. The prefix can be changed, usually to shorten it, by using the as reserved word in the import statement. This syntax effectively renames the module being imported in the namespace. The same technique is useful if there are two modules with the same name.

import mymodule as m

[...]

m.my_func()
print(locals())

[...]
'm': <module 'mymodule' from '/Users/ovidiu/playground/pyhton/modules/mymodule.py'>

For more details accessing a function's local namespace see locals().

Programmatic Import using the Module Name as String

So far, we used the import statement to import modules by their name, which is provided as part of the statement. Modules can also be imported dynamically in the program by using the module name as string, or the file the module exists in:

__import()__

__import__() is a built-in function. Because this function is meant for use by the Python interpreter and not for general use, it is better to use importlib.import_module() to programmatically import a module.

__import__('some_package.some_subpackage.some_module')

importlib.import_module()

Recommended idiom to import modules programmatically:

import importlib.import_module
module = importlib.import_module('some_name')
assert isinstance(module, types.ModuleType)

When the module is loaded as part of a package, use:

module_name = "..."
module = importlib.import_module(f'.{module_name}', package.__name__)

imp.load_source()

import imp
module = imp.load_source('some_package.some_subpackage.some_module', '.../some_package/some_subpackage/some_module.py')

Locating Module Files - Module Search Path

The runtime looks at a list of directory names and ZIP files stored in the standard sys module as the variable path (sys.path). The initial value of sys.path is assembled from the following sources:

  • The directory in which the code performing the import is located. This is why an import will aways work if the module file being imported is in the same directory as the code doing the import.
  • The current directory if the script is executed interactively.
  • The list of directories contained in the PYTHONPATH environment variable, if set.
  • An installation-dependent list of directories configured at the time Python is installed.

sys.path value can be accessed and modified:

import sys
for i in sys.path:
  print(i)

 /opt/brew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python39.zip
 /opt/brew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9
 /opt/brew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload
 /Users/ovidiu/my-project/venv/lib/python3.9/site-packages

The initial blank output line is the empty string '', which stands for the current directory. The first match will be used. If a module with the same name as a module from standard library is encountered in the search path before the standard library, it will be used instead of the module coming from the standard library.

sys.path value can be modified as follows:

Update PYTHONPATH

Set the environment variable PYTHONPATH to a colon-separated list of directories to search for imported modules. A directory declared in PYTHONPATH will be inserted at the beginning of the sys.path list when Python starts up.

Programmatically modify sys.path

Appending an absolute path works:

import sys
sys.path.append('/path/to/search')

Appending a relative path does not work, unless the script is executed from the directory the path is relative to.

import sys
sys.path.append('./my-module') # This does not work unless the Python script is executed from "my-module"'s parent.

To use a relative directory and make append() insensitive to the location the program is run from, use this pattern:

import os
import sys
sys.path.append(os.path.dirname(__file__) + "/my-module")
import my_module

PyCharm trick: If the module name and the parent directory have the same name, PyCharm will stop issuing static analysis error "No module named ..."

Programmatically modify site.addsitedir

site.addsitedir can be used to add a directory to sys.path. The difference between this and just plain appending is that when you use addsitedir, it also looks for .pth files within that directory and uses them to possibly add additional directories to sys.path based on the contents of the files.

Importing Specific Objects from a Module

An alternate import statement syntax allow binding of specific objects into the caller's symbol table. When the from/import combination is used, the specified imported module objects are inserted into the caller's symbol table and are available in the caller's namespace under their original name. No qualification with dot notation is needed to access them.

from mymodule import my_func, my_func_2
...
# invoke the function directly, without prefixing it with the name of the module
my_func()

The objects can keep their original name, as shown above, or it can be aliased, which means a custom name is inserted in the caller's symbol table.

from mymodule import my_func as m_f
...
m_f()

⚠️ Because this form of import places the object names directly into the caller’s symbol table, any objects that already exist with the same name will be overwritten.

Importing * from a Module

from mymodule import *

This syntax places the names of all objects from the imported module into the local symbol table, with the exception of those whose name begins with an underscore (_).This technique is not necessarily recommended in large-scale production code. Unless you are confident there won’t be a conflict, there is a chance of overwriting an existing name inadvertently, so it should be used with caution. Doing this will unnecessarily clutter the namespace. Not doing it makes the code easier to read: when we explicitly import a class or a function with from x import y syntax, we can easily see where y comes from. However, if we use from x import * syntax, it takes a lot longer to find where y is located. In addition, most code editors are able to provide code completion, ability to navigate to the definition of a class or inline documentation if normal imports are used. The import * syntax usually removes this capabilities. Finally, import * syntax can bring unexpected objects into the target namespace, because it will import any classes or modules that were themselves imported in the file being imported.

Explicit is better than implicit.

Recommended Style

https://google.github.io/styleguide/pyguide.html#22-imports

Uses import statements for packages and modules only, not for individual classes or functions.

  • Use import x for importing packages and modules.
  • Use from x import y where x is the package prefix and y is the module name with no prefix.
  • Use from x import y as z if two modules named y are to be imported, if y conflicts with a top-level name defined in the current module, or if y is an inconveniently long name.
  • Use import y as z only when z is a standard abbreviation (e.g., np for numpy).
  • TO PROCES: https://google.github.io/styleguide/pyguide.html#2241-exemptions

Handling Unsuccessful Imports

An unsuccessful import attempt, caused by the unavailability of the imported module, can be caught in a try block:

try:
  import inexistent_module
except ImportError:
  print("module not found!")

The unavailability of specific objects within an existent module can be detected the same way:

try:
  from mymodule import inexistent_object
except ImportError:
  print("object not found!")

Reloading a Module

TO PROCESS: https://realpython.com/python-modules-packages/#reloading-a-module

Relative Imports

Relative import syntax is an alternative to absolute import. This syntax is useful when working with related module inside a package, that are stored in a know relative position to each other. Relative import is a way of saying find a class, function or module as it is positioned relative to the current module.

In this situation:

 some_package
    ├─ module_1.py
    └─ module_2.py

where module_1 declares func_1(), func_1() can be imported in module_2 with a relative import:

from .module_1 import func_1

def func_2():
  func_1()

The period in front of "module_1" says to sue the "module_1" module inside the current package. In this situation:

 some_package
    ├─ module_1.py
    └─ some_subpackage
             └─ module_2.py

in module_2 we can use:

from ..module_1 import func_1

Distinguishing between Importing and Executing a Module

https://docs.python.org/3/library/__main__.html

Note that if you want to maintain a module that can be imported and executed as a script, the module must not contain executable code outside functions and classes. If it does, that code will be executed when the module is imported, which is generally not what you want. To distinguish between the case when the file is loaded as a module and it is executed as a script, Python sets the __name__ variable to different values. When the module is imported, __name__ is set to the module name. When the module is executed as a script, the variable value is set to "__main__".

As such, this fact can be used to wrap the executable code in a function that is only executed when the module is executed as a script:

...

def main():
 # this code is executed when the module is executed as script
 print("...")

if __name__ == '__main__':
  main()

Also see:

__name__

How to Execute (Run) a Module?

Document the -m flag.

python -m <module-name> ....

Package

A package is Python code stored into multiple files, organized in a file hierarchy.

Since we cannot put modules inside modules, because a module is just a file, and a file can hold only one file after all, Python offers the package mechanism. A package is a collection of modules, and optionally subpackages, recursively, in a folder. The name of the package is the name of the folder.

A package may contain multiple modules, each stored in its own file, either in the package root directory or recursively in subdirectories. The package root directory may also contain two optional files named __init__.py and __main__.py. Packages allow for a hierarchical structuring of the module namespace using dot notation. In the same way that modules avoid collisions between global variable names, packages avoid collision between module names. For example, the urllib package contains several modules: urllib.request, urllib.error, etc. A package also allows for subpackages.

some_dir
 └─ some_package_1
     ├─ __init__.py  # Optional
     ├─ __main__.py  # Optional
     ├─ some_module_1.py # Defines some_func_1()
     ├─ some_module_2.py # Defines some_func_2()
     └─ dir_1
         ├─ some_module_3.py # Defines some_func_3()
         └─ dir_2
             └─ some_module_4.py # Defines some_func_4()

Internally, a package is represented as an instance of the module class. The difference between a package instance and a simple module instance is that the the package instance as an extra __path__ attribute. For more details on the internal representation of a package, see:

Module Internal Representation and Introspection

Importing a Package

To import the modules of the package represented above, ensure that the directory some_dir, the parent of some_package_1, is in the module search path and use the following import statements, where a module is identified using dot notation relative to its package name:

import some_package_1.some_module_1
import some_package_1.some_module_2
import some_package_1.dir_1.some_module_3
import some_package_1.dir_1.dir_2.some_module_4

some_package_1.some_module_1.some_func_1()
some_package_1.some_module_2.some_func_2()
some_package_1.dir_1.some_module_3.some_func_3()
some_package_1.dir_1.dir_2.some_module_4.some_func_4()

A slightly more compact version is:

from some_package_1 import some_module_1
from some_package_1 import some_module_2
from some_package_1.dir_1 import some_module_3
from some_package_1.dir_1.dir_2 import some_module_4

some_module_1.some_func_1()
some_module_2.some_func_2()
some_module_3.some_func_3()
some_module_4.some_func_4()

Importing the package itself is syntactically correct, but unless there is an __init__.py, the import does not do anything useful. In particular, it does not place any of the component module names in the package in the local namespace:

import some_package_1
print(str(some_package_1))
<module 'some_package_1' (namespace)>

Package Name

See module name above.

Package Internal Representation and Introspection

A package, once loaded, is represented internally as an instance of the module class. For more details, see:

Module Internal Representation and Introspection

Package Types

Import Package

An import package, commonly referred to with a single word "package", is ?. Import packages are different, as a concept, from distribution packages.

Distribution Package

Regular Package

A traditional package, such as a directory containing an __init__.py file.

__init__.py

When a regular package is imported, this __init__.py file is implicitly executed, and the objects it defines are bound to names in the package’s namespace. The __init__.py file can contain the same Python code that any other module can contain, like variables, function and class declarations, and Python will add some additional attributes to the module when it is imported.

Much of the Python documentation states that the __init__.py file must be present in the package directory, even if as an empty file, for the package to be valid. This was once true. Since Python 3.3, PEP 420 Implicit Namespace Packages were introduced and they allow for the creation of a package without any __init__.py file.

Assuming that __init__.py is declared in some_package_1 , as shown above, and has the following content:

# this is __init__.py
COLOR = 'blue'

then importing the package itself binds the COLOR in the package's namespace, making it accessible to the client program importing the package:

import some_package_1
assert 'blue' == some_package_1.COLOR

A module in the package can access the global variable by importing it in turn. In some_module_1.py:

from some_package_1 import COLOR

[...]

def print_color():
    print(f'color is {COLOR}')

__init__.py can also be used to automatically import the modules from the package, so the clients of the package won't have to import them individually, and the objects from the package's modules will bound to the package namespace.

# this is __init__.py
import some_package_1.some_module_1
import some_package_1.some_module_2
import some_package_1.dir_1.some_module_3
import some_package_1.dir_1.dir_2.some_module_4

For a client of the package:

import some_package_1
some_package_1.some_module_1.some_func_1()
some_package_1.some_module_2.some_func_2()
some_package_1.dir_1.some_module_3.some_func_3()
some_package_1.dir_1.dir_2.some_module_4.some_func_4()

It is not recommended to put much code in __init__.py file. Programmers to not expect actual logic to happen in this file.

__main__.py

https://docs.python.org/3/library/__main__.html

Packages can be run as if they were scripts if the package provides the top-level script __main__.py. The file contains the code of the "main" module, which will be imported automatically when the package is imported. As such, __main__.py file is used to provide a command-line interface for a package.

TO PROCESS: Idiomatic usage: https://docs.python.org/3/library/__main__.html#id1

Namespace Package

A PEP 420 package which serves only as a container for subpackages. Namespace packages may have no physical representation, and have no __init__.py file.

Subpackages

https://realpython.com/python-modules-packages/#subpackages

A subpackage is a folder containing modules and optionally other subpackages, stored in a package.

Importing * from a Package

https://realpython.com/python-modules-packages/#importing-from-a-package

When from <package_name> import * is encountered, Python follows this convention: if the __init__.py file in the package directory contains a list named __all__, it is taken to be a list of modules that should be imported.

Package Metadata

Name: pulumi
Version: 2.11.2
Summary: Pulumi's Python SDK
Home-page: https://github.com/pulumi/pulumi
Author:
Author-email:
License: Apache 2.0
Location: /Users/ovidiu/Library/Python/3.8/lib/python/site-packages
Requires: dill, grpcio, protobuf
Required-by: pulumi-aws, pulumi-kubernetes, pulumi-random, pulumi-tls

Requires

Required-by

Package Example

A package example is available here:

https://github.com/ovidiuf/playground/tree/master/pyhton/packages/some_package_1

A program that consumes it is available here:

https://github.com/ovidiuf/playground/tree/master/pyhton/packages/consumer-of-packages

Publishing a Python Package in a Repository

TODO:

Python Standard Library

Python Language | Python Standard Library

site-packages

See

Python Versions | pip Relationship to Python Version