diff --git a/README.md b/README.md index facc0052955c800a3c4006edba9162a979bc0fc4..20ebf6e1f3e1eb42398920ce9dfab4226dbaec0f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/e_good_practices/README.md b/e_good_practices/README.md new file mode 100644 index 0000000000000000000000000000000000000000..00dbf69cd77e9a91bbcaf02584adee58fdf86a0e --- /dev/null +++ b/e_good_practices/README.md @@ -0,0 +1,230 @@ + +# 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}') +```