#!/usr/bin/env python # coding: utf-8 # # Module Design # - Minimize Coupling: Avoid Global Variables # - Maximize Cohesion: One Purpose # # ## Data Hiding # There is no way to prevent a client from changing names inside a module # In Python, data hiding is a convention, not a constraint. # # ### `_x` and `__all__` # - Name with a single underscore (`_x`) # - What not to be copied # - A client imports using **`from *`** will ignore these variables # - It's **not private declaration**. You can still import them. # - `__all__` # - What to be copied, higher priority that `_x` # - When this feature is used, **`from *`** will copy out only names listed in `__all__` list. # --- # # Enabling Future Language Features `__future__` # ```python # from __future__ import featurename # ``` # In[1]: import __future__ [ featurename for featurename in dir(__future__) if not featurename.startswith("_") and featurename[0].islower() ] # Although import from future is supported, there is no way to import from the past # --- # # Mixed Usage Modes: `__name__` and `__main__` # - If the file is being run as a top-level program, **`__name__`** is set to **`__main__`**. # - If it's imported, **`__name__`** is set to the modules's name as known by its clients # # This **`__name__`** servers as usage mode flag # In[2]: if __name__ == "__main__": print(__name__) # --- # # The as Extension for import and from # # ## import as # # ```python # import modulename as name # ``` # # is the same as # # ```python # import modulename # name = modulename # del modulename # ``` # # # # # ## from import as # ```python # from module1 import utility as util1 # ``` # --- # # Import Module by Name String # Original import cannot import a module by name string # # The following two way can do so # - `__import__` # - It's is generally intended for customizing import operations by reassignment in the built-in scope # - `importlib.import_module` # - The optional second argument can give the package the anchor point for resolving relative imports # # They returns the module object, so assign it to a name to keep it # In[3]: module_name = "os" os = __import__(module_name) dir(os) # In[4]: import importlib module_name = "sys" sys = importlib.import_module(module_name) dir(sys) # --- # # Transitive Module Reloads # To achieve it, scanning modules' **`__dict__`** namespace and check each item's **type** to find nested modules to reload. # It works only on **import**, not **from**. # The transitive reloader relies on the fact that module reloads update module objects in place. # --- # # Module Gotchas # ## Statement Order Matters in Top-Level Code # Don't mix def with top-level code. # Put **def** at the top of the file and top-level code at bottom # In[5]: def func(): print(x) x = 1 # When imported, the above module would cause an error. # ## from Copies Names but Doesn't Link # **from** is a name-copy operation, not a name aliasing. # # ## `from *` Can Obscure the Meaning of Variables # Again, avoiding using **`from *`** or at most once per file. # # ## reload May Not Impact from Imports