Skip to content

Commit 188fb5a

Browse files
author
Artem Innokentiev
committed
Added common info
1 parent 6640e74 commit 188fb5a

File tree

3 files changed

+6
-369
lines changed

3 files changed

+6
-369
lines changed

EN.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
# Evrone Python Guidelines (EN)
22

3-
![GitHub last commit](https://img.shields.io/github/last-commit/evrone/evrone-python-guidelines?logo=GitHub)
4-
![GitHub release (latest by date)](https://img.shields.io/github/v/release/evrone/evrone-python-guidelines)
5-
63

74
## Table of Contents
85
- [About the code](#about-the-code)

README.md

Lines changed: 6 additions & 363 deletions
Original file line numberDiff line numberDiff line change
@@ -1,369 +1,12 @@
11
# Evrone Python Guidelines
22

3-
![GitHub last commit](https://img.shields.io/github/last-commit/evrone/evrone-python-guidelines?logo=GitHub)
4-
![GitHub release (latest by date)](https://img.shields.io/github/v/release/evrone/evrone-python-guidelines)
3+
This repo contains guidelines that Evrone Python team uses when writes the code.
4+
Read guidelines carefully and keep them under pillow.
55

6+
Coding guidelines are accessible in different languages:
7+
- [English](/EN.md)
8+
- [Russian](/RU.md)
69

7-
## Содержание
8-
- [Про код](#про-код)
9-
- [Основные принципы](#основные-принципы)
10-
- [Атомарность операций](#атомарность-операций)
11-
- [Логические блоки](#логические-блоки)
12-
- [Размеры методов, функций и модулей](#размеры-методов-функций-и-модулей)
13-
- [Импорты](#импорты)
14-
- [Файлы `__init__.py`](#файлы-__init__py)
15-
- [Докстринги](#докстринги)
16-
- [Про Pull Request](#про-pull-request)
17-
- [Создание Pull Request](#создание-pull-request)
18-
- [Рефакторинг и Pull Request](#рефакторинг-и-pull-request)
19-
- [Размер Pull Request](#размер-pull-request)
20-
- [Про тулинг](#про-тулинг)
21-
- [Тестирование (pytest)](#тестирование-pytest)
22-
- [Пакетный менеджер (poetry)](#пакетный-менеджер-poetry)
23-
- [Форматирование кода (Black)](#форматирование-кода-black)
24-
- [Форматирование импортов (isort)](#форматирование-импортов-isort)
25-
- [Линтер (flake8)](#линтер-flake8)
26-
- [Тайп-чекер (mypy)](#тайп-чекер-mypy)
27-
- [Pre-commit хуки (pre-commit)](#pre-commit-хуки-pre-commit)
28-
- [Прочее](#прочее)
29-
- [Документация к REST API](#документация-к-rest-api)
3010

31-
32-
## Про код
33-
34-
### Основные принципы
35-
- **Поддерживаемость** (представьте, сможете ли вы понять свой код через год или через два)
36-
- **Простота** (между сложным и простым решением следует выбрать простое)
37-
- **Очевидность** (представьте, когда подключится новый программист, насколько ему будет понятно, почему именно **так** написан этот код)
38-
39-
40-
### Атомарность операций
41-
**1 действие ~ 1 строка**
42-
43-
Постарайтесь делать атомарные операции в коде - на каждой строке ровно **одно** действие.
44-
45-
Плохо ❌:
46-
```python
47-
# 1. 3 действия на одной строке - 3 вызова функции
48-
foo_result = foo(bar(spam(x)))
49-
50-
# 2. 3 действия на одной строке - вызов функции foo, get_c, from_b
51-
foo_result = foo(a=a, b=b, c=get_c(from_b())
52-
53-
# 3. 3 действия на одной строке - фильтрация по аргументам, условное получение элементов (через or), вызов метода .value
54-
result = [(a.value() or A, b or B) for a, b in iterator if a < b]
55-
56-
# 4. 4 действия на одной строке - из библиотеки / переменной foo идет получение атрибута bar, получение атрибута spam, получение атрибута hello и вызов calculate_weather
57-
result = calculate_weather(foo.bar.spam.hello)
58-
```
59-
60-
Хорошо ✅:
61-
```python
62-
# 1. делаем поочередный вызов каждой функции
63-
spam_result = spam(x)
64-
bar_result = bar(spam_result)
65-
foo_result = foo(bar_result)
66-
67-
# 2. поочередно вызываем функции, результат пишем в переменную и используем ее при вызове foo
68-
from_b_result = from_b()
69-
c = get_c(from_b_result)
70-
foo_result = foo(a=a, b=b, c=c)
71-
72-
# 3. последовательно проводим действия над списком - вначале фильтруем, вызываем метод .value у a, выбираем между элементами (or)
73-
filtered_result = ((a, b) for a, b in iterator if a < b)
74-
intermediate_result = ((a.value(), b) for a, b in filtered_result)
75-
result = [(a or A, b or B) for a, b in intermediate_result]
76-
77-
# 4 . последовательно читаем атрибуты bar, spam, hello и вызываем функцию calculate_weather
78-
bar = foo.bar
79-
spam = bar.spam
80-
hello = spam.hello
81-
result = calculate_weather(hello)
82-
```
83-
84-
85-
**Почему?** Потому что код становится более читабельным, не нужно исполнять несколько выражений в голове во время чтения кода. Разбитый до простых атомных операций код воспринимается гораздо лучше, чем сложный уан-лайнер. Постарайтесь упростить свой код настолько, насколько это возможно - код чаще читается, чем пишется.
86-
87-
88-
### Логические блоки
89-
90-
Постарайтесь делить код на логические блоки - так глазу программиста будет в разы проще прочитать и уловить суть.
91-
92-
Плохо ❌:
93-
```python
94-
def register_model(self, app_label, model):
95-
model_name = model._meta.model_name
96-
app_models = self.all_models[app_label]
97-
if model_name in app_models:
98-
if (model.__name__ == app_models[model_name].__name__ and
99-
model.__module__ == app_models[model_name].__module__):
100-
warnings.warn(
101-
"Model '%s.%s' was already registered. "
102-
"Reloading models is not advised as it can lead to inconsistencies, "
103-
"most notably with related models." % (app_label, model_name),
104-
RuntimeWarning, stacklevel=2)
105-
else:
106-
raise RuntimeError(
107-
"Conflicting '%s' models in application '%s': %s and %s." %
108-
(model_name, app_label, app_models[model_name], model))
109-
app_models[model_name] = model
110-
self.do_pending_operations(model)
111-
self.clear_cache()
112-
```
113-
114-
Хорошо ✅:
115-
```python
116-
def register_model(self, app_label, model):
117-
model_name = model._meta.model_name
118-
app_models = self.all_models[app_label]
119-
120-
if model_name in app_models:
121-
if (
122-
model.__name__ == app_models[model_name].__name__ and
123-
model.__module__ == app_models[model_name].__module__
124-
):
125-
warnings.warn(
126-
"Model '%s.%s' was already registered. "
127-
"Reloading models is not advised as it can lead to inconsistencies, "
128-
"most notably with related models." % (app_label, model_name),
129-
RuntimeWarning, stacklevel=2)
130-
131-
else:
132-
raise RuntimeError(
133-
"Conflicting '%s' models in application '%s': %s and %s." %
134-
(model_name, app_label, app_models[model_name], model))
135-
136-
app_models[model_name] = model
137-
138-
self.do_pending_operations(model)
139-
self.clear_cache()
140-
```
141-
142-
**Почему?** Кроме того, что это повышает читабельность, [Zen of Python](https://www.python.org/dev/peps/pep-0020/) рассказывает нам о том, как надо писать идиоматический код на Python.
143-
Одно из высказываний звучит как "Sparse is better than dense." - "Разреженное лучше чем сжатое". Сжатый код сложнее прочитать чем разреженный.
144-
145-
146-
### Размеры методов, функций и модулей
147-
148-
Предельный размер метода или функции - **50** строк.
149-
Достижение предельного размера говорит о том, что функция (метод) делает слишком много - декомпозируйте действия внутри функции (метода).
150-
151-
152-
Предельный размер модуля - **300** строк.
153-
Достижение предельного размера говорит о том, что модуль получил слишком много логики - декомпозируйте модуль на несколько.
154-
155-
Длина строки - 100 символов.
156-
157-
158-
### Импорты
159-
160-
Рекомендуемый способ импортирования - абсолютный.
161-
162-
Плохо ❌:
163-
```python
164-
# spam.py
165-
from . import foo, bar
166-
```
167-
168-
Хорошо ✅:
169-
```python
170-
171-
# spam.py
172-
from some.absolute.path import foo, bar
173-
```
174-
175-
**Почему?** Потому что абсолютный импорт явно определяет локацию (путь) модуля, который импортируется. При релативном
176-
импорте всегда нужно помнить путь и вычислять в уме локацию модулей `foo.py`, `bar.py` относительно `spam.py`
177-
178-
179-
### Файлы `__init__.py`
180-
181-
В `__init__.py` файлах пишем только импорты.
182-
183-
**Почему?** Потому что `__init__.py` - последнее место, в которое посмотрит программист, который будет читать код в будущем.
184-
185-
186-
### Докстринги
187-
Рекомендуем добавлять докстринги в функции, методы и классы.
188-
189-
**Почему?** Потому что программист, который впервые увидит ваш код, сможет быстрее понять, что в нем происходит.
190-
Код читается намного больше, чем пишется.
191-
192-
193-
## Про Pull Request
194-
195-
### Создание Pull Request
196-
**1 Pull Request = 1 issue**
197-
198-
Один Pull Request должен решать ровно одно issue.
199-
200-
**Почему?** Потому что ревьюверу сложнее держать контекст нескольких задач в голове и переключаться между ними. Когда PR содержит несколько issue - это часто приводит к тому, что PR увеличивается и требует больше времени и сил на ревью от ревьювера.
201-
202-
203-
### Рефакторинг и Pull Request
204-
Рефакторинг лучше всего выносить в отдельный Pull Request.
205-
206-
**Почему?** Когда рефакторинг идет вместе с решением определенного issue, то рефакторинг размывает контекст issue и вводит правки, которые не имеют отношения к данному PR.
207-
208-
209-
### Размер Pull Request
210-
Итоговый дифф PR не должен превышать +/- 600 измененных строк.
211-
212-
Плохо ❌:
213-
214-
![bad](https://user-images.githubusercontent.com/8825727/113953748-6fc7ba80-9853-11eb-9673-827995e54f73.png)
215-
```
216-
Дифф 444 + 333 = 777
217-
```
218-
219-
Хорошо ✅:
220-
221-
![good](https://user-images.githubusercontent.com/8825727/113953831-a30a4980-9853-11eb-854b-d4c4f6559f2c.png)
222-
```
223-
Дифф 222 + 111 = 333
224-
```
225-
226-
227-
**Почему?** Потому что чем больше PR - тем более он становится неконтролируемым и мерж производится "закрыв глаза и заткнув уши".
228-
Также, большинству ревьюверов будет сложно воспринять такой объем изменений за один раз.
229-
230-
231-
## Про тулинг
232-
233-
### Тестирование (pytest)
234-
[pytest](https://pytest.org) - фреймворк для тестирования кода
235-
236-
Рекомендуемый конфиг в `pytest.ini`:
237-
```ini
238-
[pytest]
239-
DJANGO_SETTINGS_MODULE = settings.local
240-
python_files = tests.py test_*.py *_tests.py
241-
242-
```
243-
244-
### Пакетный менеджер (poetry)
245-
246-
[poetry](https://python-poetry.org) - менеджер зависимостей и сборщик пакетов
247-
248-
249-
### Форматирование кода (Black)
250-
[Black](https://black.readthedocs.io/en/stable/) - автоформаттер кода по PEP8
251-
252-
Рекомендуемый конфиг в `pyproject.toml`:
253-
```toml
254-
255-
[tool.black]
256-
line-length = 100
257-
target-version = ['py38']
258-
exclude = '''
259-
(
260-
\.eggs
261-
|\.git
262-
|\.hg
263-
|\.mypy_cache
264-
|\.nox
265-
|\.tox
266-
|\.venv
267-
|_build
268-
|buck-out
269-
|build
270-
|dist
271-
)
272-
'''
273-
274-
```
275-
276-
277-
### Форматирование импортов (isort)
278-
[isort](https://pycqa.github.io/isort/) - автоформаттер блока импортов
279-
280-
Рекомендуемый конфиг в `pyproject.toml`:
281-
```toml
282-
283-
[tool.isort]
284-
line_length = 100
285-
sections = ["FUTURE", "STDLIB", "DJANGO", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
286-
multi_line_output = 3
287-
known_django = "django"
288-
profile = "django"
289-
src_paths = "app"
290-
lines_after_imports = 2
291-
292-
```
293-
294-
295-
### Линтер (flake8)
296-
[flake8](https://flake8.pycqa.org/en/latest/) - валидатор соответствия PEP8
297-
298-
Рекомендуемый конфиг в `.flake8`:
299-
```ini
300-
[flake8]
301-
max-line-length = 100
302-
max-complexity = 5
303-
exclude = .venv,venv,**/migrations/*,snapshots
304-
per-file-ignores =
305-
tests/**: S101
306-
**/tests/**: S101
307-
```
308-
309-
310-
### Тайп-чекер (mypy)
311-
312-
[mypy](http://mypy.readthedocs.io) - чекер для статической типизации
313-
314-
Рекомендуемый конфиг `mypy.ini`:
315-
316-
```ini
317-
[mypy]
318-
ignore_missing_imports = True
319-
allow_untyped_globals = True
320-
321-
[mypy-*.migrations.*]
322-
ignore_errors = True
323-
324-
```
325-
326-
327-
### Pre-commit хуки (pre-commit)
328-
329-
[pre-commit](https://pre-commit.com) - фреймворк для управления `pre-commit` хуками
330-
331-
Рекомендуемый конфиг `.pre-commit-config.yaml`:
332-
333-
```yaml
334-
default_language_version:
335-
python: python3.8
336-
337-
repos:
338-
- repo: local
339-
hooks:
340-
- id: black
341-
name: black
342-
entry: black app
343-
language: python
344-
types: [python]
345-
346-
- id: isort
347-
name: isort
348-
entry: isort app
349-
language: python
350-
types: [python]
351-
352-
- id: flake8
353-
name: flake8
354-
entry: flake8 server
355-
language: python
356-
types: [python]
357-
```
358-
359-
360-
## Прочее
361-
362-
### Документация к REST API
363-
Рекомендуемый формат документации - [OpenAPI](https://www.openapis.org).
364-
Схема для OpenAPI должна генерироваться "на лету", чтобы обеспечивать клиентов API свежими изменениями.
365-
366-
**Почему?** Потому что это один из распространенных форматов для документирования REST API, который вышел из Swagger. Данный формат документации поддерживается большим количеством клиентов (Swagger, Postman, Insomnia Designer и многие другие). Также, рукописная документация имеет свойство быстро устаревать, а документация, которая генерируется напрямую из кода позволяет не думать о постоянном обновлении документации.
367-
368-
## Спонсор
11+
## Sponsor
36912
[<img src="https://evrone.com/logo/evrone-sponsored-logo.png" width=300>](https://evrone.com/?utm_source=github.com&utm_campaign=evrone-python-codestyle)

RU.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
# Evrone Python Guidelines (RU)
22

3-
![GitHub last commit](https://img.shields.io/github/last-commit/evrone/evrone-python-guidelines?logo=GitHub)
4-
![GitHub release (latest by date)](https://img.shields.io/github/v/release/evrone/evrone-python-guidelines)
5-
63

74
## Содержание
85
- [Про код](#про-код)

0 commit comments

Comments
 (0)