Commit 9ee9201f authored by Mathieu's avatar Mathieu
Browse files

[good practices] (readme) Add a readme about good practices

parent 9cd188c4
......@@ -19,7 +19,7 @@ This is not a formation to learn python from scratch, you should know the python
# Why python ?
## Pro
## Pros
- flexibility (not typed)
- concision of the syntax
......
# Good practices in python
The list below is inspired from [https://docs.quantifiedcode.com/python-anti-patterns/]
## About good practices
The practices below are not enforced by python, your code will run even if you don't respect them.
Here are a few reasons you should use them:
- your code will be easier to read;
- your code will be easier to write;
- your code will be easier to maintain;
- you won't surprise others that will read your code;
It will save time for everybody, you included !
## Naming conventions
Details:
- [https://docs.quantifiedcode.com/python-anti-patterns/maintainability/using_single_letter_as_variable_name.html]
- [https://docs.quantifiedcode.com/python-anti-patterns/correctness/assigning_to_builtin.html]
- [https://docs.quantifiedcode.com/python-anti-patterns/readability/putting_type_information_in_a_variable_name.html]
- [https://docs.quantifiedcode.com/python-anti-patterns/readability/using_camelcase_in_function_names.html]
Conventions
- variables / functions: snake_case
- classes: CamelCase
```python
path_to_images = ...
def get_path_to_images():
pass
class PathToImages:
pass
```
**/!\ Do not use built-in function names to store variables**
```python
# You shouldn't do
max = 10
min = 0
```
**Don't use single letter as names**
## Use comprehensions
See [../a_list_comprehension]
Details
- [https://docs.quantifiedcode.com/python-anti-patterns/readability/using_map_or_filter_where_list_comprehension_is_possible.html]
- [https://docs.quantifiedcode.com/python-anti-patterns/readability/not_using_a_dict_comprehension.html]
## Encapsulation
Details
- [https://docs.quantifiedcode.com/python-anti-patterns/correctness/accessing_a_protected_member_from_outside_the_class.html]
- [https://docs.quantifiedcode.com/python-anti-patterns/correctness/implementing_java-style_getters_and_setters.html]
Python breaks the POO rule of encapsulation.
Nonetheless, there are two things you can still do to protect your classes' integrity.
### Use the _ naming convention
If you start an attribute/method name by an underscore, it means that it shouldn't be accessed from outside the object.
### Anti-pattern
Don't use setters/getters !
```python
class Person:
def __init__(self, age):
self._age = age
def get_age(self):
return self._age
def set_age(self, age):
self._age = age
```
### Good Practice
The attribute should be public:
```python
class Person:
def __init__(self, age):
self.age = age
```
### To use carefully
If you really need to have a setter (to change another variable, ...), you can (but only if you really need it) use the [@property decorator](https://docs.python.org/3/library/functions.html#property).
## Mutable value as default argument
Details
- [https://docs.quantifiedcode.com/python-anti-patterns/correctness/mutable_default_value_as_argument.html]
See [../d_dataclassses/00_mutable.py] for examples.
### Anti pattern
```python
class BadStock:
def __init__(self, products: List[str] = []):
self.products: List[str] = products
def add_to_stock(self, product: str):
self.products.append(product)
```
Every BadStock instance will share the same `products` attribute !
### Good practice
```python
class GoodStock:
def __init__(self, products: List[str] = None):
self.products: List[str] = products or []
def add_to_stock(self, product: str):
self.products.append(product)
```
This way, the default list is created during the object instantiation, so it will be a different one for each object.
NB: If you use dataclasses, the `default_factory` function will take care of this issue for you.
## Functions that return more than one type
Details
- [https://docs.quantifiedcode.com/python-anti-patterns/maintainability/returning_more_than_one_variable_type_from_function_call.html]
If your function return different types according to the situation, it makes it more difficult to use. It should be avoided.
### Anti-pattern
```python
def get_last_subscriber(subscribers):
if not len(subscribers):
return None
return subscribers[-1]
def do_something_to_last_subscriber(subscribers):
last_subscriber = get_last_subscriber(subscribers)
if last_subscriber is None:
# No subscriber, do something
pass
else:
# Subscribers, do something else
pass
```
### Good practice
```python
class NoSubscribersException(Exception):
pass
def get_last_subscriber(subscribers):
if not len(subscribers):
raise NoSubscribersException()
return subscribers[-1]
def do_something_to_last_subscriber(subscribers):
try:
last_subscriber = get_last_subscriber(subscribers)
# Subscribers, do something
pass
except NoSubscribersException:
# No subscriber, do something
pass
```
## Unpythonic loops
Details
- [https://docs.quantifiedcode.com/python-anti-patterns/readability/using_an_unpythonic_loop.html]
- [https://docs.quantifiedcode.com/python-anti-patterns/readability/not_using_zip_to_iterate_over_a_pair_of_lists.html]
You should use [enumerate](https://docs.python.org/3/library/functions.html#enumerate), [zip](https://docs.python.org/3/library/functions.html#zip), when appropriate.
### Anti-pattern
```python
persons = ['Jean', 'Paul', 'Fred']
for i in range(len(persons)):
person = persons[i]
print(f'the {i+1}th person in the list is {person}')
```
Or, worse:
```python
persons = ['Jean', 'Paul', 'Fred']
i = 0
for person in persons:
i += 1
print(f'the {i+1}th person in the list is {person}')
```
### Good practice
```python
persons = ['Jean', 'Paul', 'Fred']
for i, person in enumerate(persons):
print(f'the {i+1}th person in the list is {person}')
```
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment