# Modules & Scopes

## Modules or Packages

They are created by adding a `__init__.py` file inside a folder named `<your-package-name>`.

{% hint style="danger" %}
In Python versions (3.3+), `__init__.py` is technically not required for packages, but it is still best practice.
{% endhint %}

```
├── /<package-name>
  ├── __init__.py
  ├── ...
```

Submodules are created the same way. They are only packages inside other packages.

```
├── /<package-name>
  ├── __init__.py
  ├── ...
  ├── /<subpackage-name>
    ├── __init__.py
    ├── ...
```

{% hint style="warning" %}
There is no tree-shaking in Python. So whenever you import a package, all of its code and its dependencies are loaded in memory.

If you want to delimit the amount of memory used, divide your code into submodules. *(When importing submodules, only the submodule is loaded)*
{% endhint %}

### \_\_init\_\_.py

Inside this file you may:

* Package initialization code: Code that should run when the package is imported. (Like setting up logging, loading configuration, or initializing package-level variables)
* Expose submodules or functions.
* Defining `__all__`.
* Add version information. (With `__version__`)

#### Exposing submodules

You can import submodules, classes, or functions so they are available directly from the package. This is called **explicit re-exporting**.

E.g., suppose you have a package `main` that has two subpackages `sub1` and `sub2`.

{% code title="**init**.py" %}

```python
from .sub1 import func_s1
from .sub2 import func_s2
```

{% endcode %}

When outside, user can:

```python
# Instead of
from main.sub1 import func_s1

# You just
from main import func_s1
```

#### \_\_all\_\_

With `__all__` you can specify which function of your module will be "exported", when calling for `from module import *`.

{% hint style="info" %}
This is only at a namespace level.

And it does not prevent direct imports.
{% endhint %}

{% code title="**init**.py" %}

```python
__all__ = ['func1', 'func2']
```

{% endcode %}

### Visibility in python

In Python everything is public by default.

You can use the **underscore convetion**, by prefixing a name with an underscore (e.g., `_my_func`), to hint users that it is "private".

**But this is only convention, it does not prevent access.**

### Importing packages

```python
import mymodule
from mymodule import *

# Alias the package name
import mymodule as othername

# Import specific things
from mymodule import someMethod, someClass

# Importing from submodules
from mymodule.subpackage import someMethod
from mymodule.subpackage.subsubpackage import someMethod
```

## Scope in Python

Variable creation is limited by the scope of where it is created.

*A variable created in the main body of the Python code is a global variable and belongs to the global scope.*

### Global variables

Creating or referencing global variables inside blocks can be done with `global` keyword.

```python
def someFunc():
    # Creates a new global variable 
    global someVar
    someVar = 5
```

```python
someVar = 5

def someFunc():
    # This will reference to the global variable
    global someVar
    someVar = 6
```

### Non-local variable reference

Using `nonlocal` to a variable will make the reference to the variable be of the outer scope.

{% hint style="info" %}
Useful with nested functions.
{% endhint %}

```python
def myfunc1():
    x = "Jane"
    def myfunc2():
        # This will change 'x' value from "Jane" to "hello"
        nonlocal x
        x = "hello"
    myfunc2()
    return x
```
