Python import - The law of Demeter
Tags: python, import, design, guideline
Published: 2022-01-04 10:15:51

## The Demeter Law

Python import can be a little tricky. The law of Demeter guideline and his applications to python import can help you mastering them and also writes more user-friendly, testable and reliable code.

According to wikipedia, the law of Demeter (or principle of least knowledge) is a software developing guideline that can be summarized this way:

- Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
- Each unit should only talk to its friends; don't talk to strangers.
- Only talk to your immediate friends.

If we want to apply these to python import (the python module would be the unit), it would become something like: 

- Each module should only import public object of submodule, but only from the closest submodule. (aka "immediate friends")

## The method

In order to accomplish that, the key is to declare explicitly in each module (though the \__init\__.py) which object are exposed. Then these objects are accessible to the parent module folder, which can also re-expose them if necessary.

The benefices of this method are:
- Your \__init\__.py files become some kind of "documentation" of which objects should be accessed or not from other modules.

- You fully control your import system, which is a key to loose-coupling.

- The final user (other developper using your module) experience is enhanced, import are short and clear.

A downside is that it is more verbose.

## First example

Assuming this setup:

```
project
│   __main__.py
└───demeter
│   │   __init__.py
│   └───module1
│       │   __init__.py
│       │   code1.py
```

```python
# demeter/module1/code1.py

class MyClass1:
    pass
```

With that setup, we can access directly MyClass1 class from \__main\__.py:

```python
# __main__.py

from demeter.module1.code1 import MyClass1

class1 = MyClass1()
```

This works, however this is a demeter's law violation: \__main\__.py import  MyClass1 from  demeter/module1/code1.py which is not an "immediate friend".

A good smell to detect it are long import. When applying the law of demeter, import are usually pretty short.

A cleaner way to do that would be...

1. Explicitly expose MyClass1 from module1 to upper level module:

```python

# demeter/module1/__init__.py

from demeter.module1 import code1 as code1

MyClass1 = code1.MyClass1
```

2. Re-expose it to the upper level:

```python
# demeter/__init__.py

from demeter import module1

MyClass1 = module1.MyClass1
```

3. Use it 

```python
# __main__.py

from demeter import module1

MyClass1 = module1.MyClass1
```

That way:

- __\__main\__.py__ only have knowledge of the __demeter__ module and the __demeter__ module only have knowledge about the __module1__ module. The principe of least knowledge is respected
- modules (__demeter__ and __module1__) explicitly expose what can be used from them(__explicit is better than implicit__, https://www.python.org/dev/peps/pep-0020/). 

## Second example

Assuming this extension of the previous setup:

```
project
│   __main__.py
└───demeter
│   │   __init__.py
│   └───module1
│       │   __init__.py
│       │   code1.py
|       └───module2
│       │   __init__.py
│       │   code2.py
```

```python
# demeter/module1/module2/code2.py

class MyClass2:
    pass
```

To use MyClass2 in \__main\__.py we would...

Expose it to module1:

```python
# demeter/module1/module2/__init__.py

from demeter.module1.module2 import code2 as code2

MyClass2 = code2.MyClass2
```

Re-expose it but this time from module1:

```python
# demeter/module1/__init__.py

from demeter.module1 import code1 as code1
from demeter.module1 import module2 as module2

MyClass1 = code1.MyClass1
MyClass2 = module2.MyClass2 
```
and...

```python
# demeter/__init__.py

from demeter import module1

MyClass1 = module1.MyClass1
MyClass2 = module1.MyClass2

```

Finally, use it from your \__main__.py:

```python
# __main__.py

from demeter import module1

MyClass1 = module1.MyClass1
MyClass2 = module1.MyClass2

```


## Whole module example

The law of demeter can also be applied to whole module.

The previous example could be something like:

```python
# demeter/module1/code1.py

class MyClass1:
    pass
```

```python
# demeter/module1/module2/code2.py

class MyClass2:
    pass
```

```python
# demeter/module1/module2/__init__.py

from  demeter.module1.module2  import  code2  as  code2
```

```python
# demeter/module1/__init__.py

from  demeter.module1  import  code1  as  code1
from  demeter.module1  import  module2  as  module2

MyClass1 = code1.MyClass1
```

```python
# demeter/__init__.py

from  demeter  import  module1

MyClass1 = module1.MyClass1
```

```python

# demeter/__main__.py
import  demeter

module1 = demeter.module1

MyClass1 = module1.MyClass1
MyClass2 = module1.module2.code2.MyClass2
```

This second method looks very similar to the bad practice example, isn't it?
However, the key points are that every desired module and object are **explicitly** exposed.

So you still benefice the full control of your module system and you respect the principle of least knowledge.

## VSCode, Pylance, Pyrights

If you use VSCode and Pylance (which use Pyrights under the hood),  this method will avoid you to have some [reportUnknownMemberType errors](https://github.com/microsoft/pylance-release/blob/main/DIAGNOSTIC_SEVERITY_RULES.md#diagnostic-severity-rules)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/em33tfo8udnxi7tu6vvx.png)

## Conclusion

I hope you learned something reading this article (my first one uWu ^^).

It's up to you to choose if you prefer expose full module or only object. I guess it depends on the context, the project, the people etc....

Even if it look a little "too much" and require more code and more rigorous , these methods really enhance control and quality on the long run as well as deeply understanding (and thinking about ;) ) responsibility of every part of your code.