# Modules & Scopes

## The use of `__name__ == '__main__'`

Adding this to a python file script will ensure that everything inside this condition will be only ran, if the script file was runned directly.

```python
if __name__ == '__main__':
```

This happens because when you import functions from other files, python runs the entire file. So if you have code executing there, it will execute it on the import.

To avoid this you put this code inside this condition.

## 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
```
