# Import

One cool way to import

To import a module from a parent directory in Python, you can make use of the `sys` module and modify the `sys.path` list to include the parent directory.

Here's an example of how you can import the `0-add_integer` module from the parent directory when the code snippet is in a subdirectory named `dir`:

```python
#This file is in a sub directory called test
import sys
sys.path.append('..')  # Add parent directory to the sys.path list

add_integer = __import__('0-add_integer').add_integer
```

In this example, `sys.path.append('..')` adds the parent directory to the list of directories Python searches for module imports. By appending `'..'`, you are adding the directory that contains the current directory (`dir`) to the path.

Once the parent directory is included in the `sys.path`, you can use `__import__('0-add_integer').add_integer` to import the `add_integer` function from the `0-add_integer` module in the parent directory.

### Relative imports

Relative imports in Python allow you to import modules or packages based on their relative location to the current module. Instead of using an absolute import with the full path to the module, you can use a relative import statement to specify the module's location relative to the current module's location.

Relative imports are useful when you have a module structure with submodules or subpackages, and you want to import modules within the same package or in a parent or sibling package. They help organize and maintain the structure of your code by providing a concise way to import related modules.

Python supports two types of relative imports:

1. Implicit relative imports: These imports are specified using the dot (`.`) syntax and allow you to import modules from the same package or subpackage.
    
    Example:
    
    ```python
    # Importing a module from the same package
    from . import module_name
    
    # Importing a module from a subpackage
    from .subpackage import module_name
    ```
    
2. Explicit relative imports: These imports are specified using the `from` keyword and allow you to import modules from a parent or sibling package by specifying the relative path.
    
    Example:
    
    ```python
    # Importing a module from a parent package
    from ..parent_package import module_name
    
    # Importing a module from a sibling package
    from .sibling_package import module_name
    ```
    

Note that relative imports can only be used within a package. A package is a directory that contains an `__init__.py` file, which signifies it as a package.

### Difference between absolute and relative

The main difference between absolute and relative imports in Python is how the imported module's location is specified.

1. Absolute imports: Absolute imports use the full path to the module or package from the Python's module search path (`sys.path`). You specify the complete import path starting from the top-level package or the Python standard library.
    
    Example:
    
    ```python
    # Absolute import
    import package.module
    ```
    
    In this case, Python searches for the `package.module` module in the module search path and imports it. Absolute imports are more explicit and provide a clear and unambiguous way to import modules. They are commonly used when importing modules from external libraries or modules outside the current package.
    
2. Relative imports: Relative imports specify the location of the imported module relative to the current module's location. They use dot (`.`) syntax to denote the relative relationship.
    
    Example:
    
    ```python
    # Relative import
    from . import module
    ```
    
    In this case, Python searches for the `module` module within the same package or subpackage as the current module. Relative imports are useful for importing modules within the same package or for importing modules from parent or sibling packages. They provide a concise and convenient way to import related modules within a package structure.
    

The choice between absolute and relative imports depends on the specific use case and the structure of your project. Absolute imports are generally used for external or third-party modules, while relative imports are commonly used within a package structure to maintain module organization and encapsulation.

**How python searches for modules**

By default, Python searches for modules in specific locations based on the `sys.path` variable.

The `sys.path` variable is a list of directory names where Python looks for modules. It includes the current working directory (the directory from which the script is executed) and other directories such as standard library locations and directories specified in the `PYTHONPATH` environment variable.

When you use a relative import statement, Python searches for the module relative to the location of the importing module, not recursively through all parent and subdirectories.

If you want to import a module from a specific location, you need to specify the correct path to the module. This can be done by modifying `sys.path` to include the desired directory or by using an absolute import statement with the full path to the module.

In summary, Python does not automatically search all parent and subdirectories for the module you want to import. You need to provide the correct path to the module or modify `sys.path` to include the desired directory where the module is located.

So therefore, the code below wont work in a sub directory

```python
#!/usr/bin/python3
def experiment():
    add_integer = __import__('0-add_integer').add_integer
    print(add_integer(1, 2))
experiment()
```

unlesss the file 0-add\_integer.py is present in the same directory as experiment.py

**Nb: Return doesn't return to the console**

```python
#!/usr/bin/python3
def experiment():
    add_integer = __import__('0-add_integer').add_integer
    return add_integer(1, 2)
experiment()
```

the return value of the `experiment()` function will not be displayed or returned anywhere. When you call the `experiment()` function, it will calculate the result of `add_integer(1, 2)`, but since you haven't specified what to do with the return value, it will essentially be discarded.

**This works ok:**

```python
#!/usr/bin/python3
def experiment():
    add_integer = __import__('0-add_integer').add_integer
    return add_integer(1, 2)

result = experiment()
print(result)
```
