Як правильно організувати Python-проект

7 хв. читання

В цьому пості я покажу як правильно організувати новий Python-проект. Звісно, це не єдиний шлях, я просто розкажу про найважливіші аспекти.

Python часто помилково вважають лише мовою сценаріїв. Так, для написання скриптів він теж згодиться, але не тільки. Він використовується в веб-програмуванні, науковій діяльності, обробці даних та машинному навчанню.

Якщо вам потрібно написати маленький скрипт, то цей пост не для вас. Просто напишіть його і запустіть, ось і вся наука.

Але коли ви пишете щось серйозне, що буде підтримуватися довгий час, що залежить від якихось зовнішніх сервісів, ви повинні добре все налаштувати.

Пакунки

Коли ви починаєте новий проект, першим питанням постає "як зберігати код?". Python не має чіткої структури каталогів, як, наприклад, Maven для Java.

Я рекомендую завжди розміщувати код в пакунках. Якщо ви використовуєте готові шаблони (як от django-admin startproject), то вони зазвичай створюються автоматично.

А ось так може виглядати Python-пакунок.

README.rst — все що потрібно знати новачку про проект
requirements.txt   — залежності
mypkg/ — сам пакунок
----__init__.py    
----main.py  — файл з кодом
----test_main.py — тести
----mylib.py   
----test_mylib.py

В Python пакунок — це директорія з правильною назвою, та містить в собі файл __init__.py. Зазвичай використовують імена з латинських літер в нижньому регістрі. Також можна використовувати _, але не рекомендується.

Коли файл додано до пакунку його можна імпортувати використовуючи синтаксис <pkg_name>.<module_name>.

Пакунки також можуть бути вкладеними:

mypkg/   
----__init__.py    
----main.py — код
----subpkg/    
----subpkg/__init__.py     
----subpkg/sublib.p — код

Кожна субдиректорія повинна містити файл __init__.py.

Пакунки підтримуються багатьма інструментами. Наприклад, py.test може знайти для вас всі тести в пакунку, а setuptools може знаходити та інсталювати пакунки.

Ось приклад файлу setup.py для нашого пакунку.

#!/usr/bin/env python
# coding=utf-8

"""
python distribute file
"""

from __future__ import (absolute_import, division, print_function,
                        unicode_literals, with_statement)

from setuptools import setup, find_packages


def requirements_file_to_list(fn="requirements.txt"):
    """читає файл з залежностями і створює список, який можна використовувати при інсталяції
    """
    with open(fn, 'r') as f:
        return [x.rstrip() for x in list(f) if x and not x.startswith('#')]


setup(
    name="mypkg",
    version="0.1.0",
    packages=find_packages(),
    install_requires=requirements_file_to_list(),
    dependency_links=[
        # Якщо ваш пакунок має залежності, яких немає на PyPI,
        # тут ви повинні вказати посилання на початкові файли
        # і вказати його в файлі requirements.txt
    ],
    entry_points={
        # 'console_scripts': [
        #     'main = mypkg.main:main',
        # ]
    },
    package_data={
        'mypkg': ['logger.conf']
    },
    author="FIXME add author",
    author_email="FIXME add email",
    maintainer="FIXME add maintainer",
    maintainer_email="FIXME add email",
    description="FIXME add description",
    long_description=open('README.rst').read(),
    license="GPLv2+",
    url="https://pypi.python.org/pypi/mypkg",
    classifiers=[
        'Development Status :: 3 - Alpha',
        'License :: OSI Approved :: GNU General Public License (GPL)',
        'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3.4',
    ]
)

Більше інформації про пакунки та їх розповсюдження ви можете знайти в офіційній документації, тут та тут.

Використовуйте Git

Сьогодні систему контролю версій (СКВ, VCS) повинен використовувати кожен проект. Ну а Git найшвидша та найпопулярніша. Звісно, ви можете використовувати інший інструмент, якщо знайомі з ним.

Коли використовуєте VCS, коммітьте пакунки, файл requirements.txt, але не virtualenv та python байткод.

Досягти цього можна використанням файлу .gitignore. Ось мій:

.venv/
bin/
dist/
build/
wheelhouse/
*.egg-info/
*.py[co]
__pycache__/
.tox
*.deb
doc/_build/

Python 3 vs Python 2

Так як третя версія несумісна з другою, ви повинні вирішити яку використовувати. Зазвичай, слід обирати третю версію, 2017-й же на дворі.

Написання коду, що працює з обома версіями не складне. Tox — це утиліта, що створить для вас virtualenv, встановить залежності та запустить тести відразу на декількох версіях Python'у.

Ось приклад файлу tox.ini для простого тестування бібліотеки з пакунком foo:

[tox]
envlist = py27,py34

[testenv]
commands = pep8 --ignore=E202,E501 foo
           pylint -E foo
           py.test foo
whitelist_externals = make
deps = pep8
       pylint
       pytest

Щоб запустити ці тести слід просто викликати утиліту tox. Tox створить декілька віртуальних середовищ в директорії .tox, встановить залежності і запустить тести.

Використовуйте virtualenv

Virtualenv слід використовувати для кожного проекту. Це інструмент, що створює ізольоване середовище зі своїм інтерпретатором, пакунками та pip.

Якщо ви пишете лише для тих платформ, де Python 3 вже встановлено, то ви можете використовувати venv. Але я надаю перевагу старому, але універсальному virtualenv.

Використовують її ось так:

virtualenv --python=python3 -q .venv
. .venv/bin/activate
pip install -r requirements.txt

Також Python та pip можна викликати прямо з директорії:

virtualenv --python=python3 -q .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -V

Обробка залежностей з pip

pip — це інструмент для встановлення, оновлення та видалення пакунків. При створенні проекти ви повинні створити файл requirements.txt, в якому будуть вказані всі залежності.

Ось так він може виглядати:

bottle==0.12.9
gevent==1.1.2
psycopg2==2.6.2
boto3==1.4.0
fake-factory==0.6.0
celery==3.1.23
redis==2.10.5
lxml==3.6.4

Як бачите, слід вказувати не тільки назву, а й версію, щоб потім не було проблем з новими несумісними оновленнями. Отримати список пакунків ви можете за допомогою команди

pip freeze

Але я не люблю використовувати її вивід напряму, адже там будуть і залежності ваших залежностей, що робить список довгим та брудним.

При розробці ПЗ, ви можете встановлювати залежності за допомогою pip з PyPI. Але не при розгортанні. При розгортанні я рекомендую використовувати pip wheel та розповсюджувати wheels разом з додатком.

Вибір фреймворку для тестування

Більшість проектів потребують тестів, щоб переконатися, що компоненти працюють коректно. Це полегшує підтримку та рефакторинг проекту.

Фреймворк для тестування може спростити вам написання тестів та їх запуск. Я рекомендую py.test. В нього немає шаблону для тестів, а його текстовий вивід результатів просто прекрасний!

Приклад:

#!/usr/bin/env python
# coding=utf-8

"""
example of using py.test
"""

def fib(n):
    """повертає n-не число Фібоначчі

    Args:
        n: число >= 0

    Return:
        n-не число Фібоначчі

    """
    if n <= 0:
        return -1
    i = j = 1
    for _ in xrange(n - 1):
        i, j = j, i + j
    return i


def test_fib():
    assert fib(1) == 1
    assert fib(2) == 1
    assert fib(3) == 2
    assert fib(4) == 3
    assert fib(5) == 5


def test_fib_bad_input():
    assert fib(0) == -1
    assert fib(-34) == -1

Як бачите, тести це звичайні функції. Використовуйте assert для перевірок. Тести можна писати в тих самих файлах, або окремих. Також за згодою файли з тестами та функції починаються з test_. Ви можете запустити всі тести за допомогою

py.test <pkgname>

Інтеграція pep8 та pylint

pep8 — це інструмент, що може перевіряти ваш кода на відповідність PEP 8 style guide. Керівництво по оформленню призване допомогти зробити код читабельним та зрозумілим для кожного.

Pylint — це аналізатор коду, що може знайти найпопулярніші помилки програмістів. Наприклад, він може знайти перевизначення функції, не використовувані параметри, невизначені символи, погані імпорти тощо. Зазвичай я запускаю pylint з опцією -E, що виводить тільки помилки та попередження, без неї вивід програми буде занадто довгим. Але якщо ви вирішите прочитати повний вивід, ви будете вражені наскільки pylint розуміє ваш код. Іншим плюсом цієї опції є те, що перевірка виповнюється швидше.

pep8 та pylint гнучко налаштовуються. Для деяких проектів буває потрібно відключити деякі перевірки. Особисто я вимикаю деякі перевірки завжди. Перевірки можуть бути вимкнені на рівні рядка коду, функції чи цілого модуля. Вимикайте перевірки глобально лише коли будете впевнені, що вони принесуть більше головної болі чим користі.

Ось Makefile, що інтегрує virtualenv, pep8, pylint та pytest:

PYTHON_MODULES := mypkg
PYTHONPATH := .
VENV := .venv
PYTEST := env PYTHONPATH=$(PYTHONPATH) PYTEST=1 $(VENV)/bin/py.test
PYLINT := env PYTHONPATH=$(PYTHONPATH) $(VENV)/bin/pylint --disable=I0011 --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}"
PEP8 := env PYTHONPATH=$(PYTHONPATH) $(VENV)/bin/pep8 --repeat --ignore=E202,E501,E402
PYTHON := env PYTHONPATH=$(PYTHONPATH) $(VENV)/bin/python
PIP := $(VENV)/bin/pip

DEFAULT_PYTHON := /usr/bin/python3
VIRTUALENV := /usr/local/bin/virtualenv

REQUIREMENTS := -r requirements.txt

default: check-coding-style

venv:
        test -d $(VENV) || $(VIRTUALENV) -p $(DEFAULT_PYTHON) -q $(VENV)
requirements:
        @if [ -d wheelhouse ]; then \\
                $(PIP) install -q --no-index --find-links=wheelhouse $(REQUIREMENTS); \\
        else \\
                $(PIP) install -q $(REQUIREMENTS); \\
        fi
bootstrap: venv requirements

check-coding-style: bootstrap
        $(PEP8) $(PYTHON_MODULES)
        $(PYLINT) -E $(PYTHON_MODULES)
pylint-full: check-coding-style
        $(PYLINT) $(PYTHON_MODULES)
test: check-coding-style
        $(PYTEST) $(PYTHON_MODULES)
check:
        $(PYTEST) $(PYTHON_MODULES)

.PHONY: default venv requirements bootstrap check-coding-style pylint-full test check

Щоб запустити відразу pep8, pylint та pytest, просто виконайте make test. Це створить віртуальне середовище в директорії .venv, встановить залежності та запустить перевірки. Щоб запустити лише pytest, виконайте make check. Можливо, ви захочете запускати pytest лише тоді коли проект стане достатньо великим, тоді коли pylint корисний завжди.

Цей makefile — урізана версія, в якій використовуються тільки вищезгадані інструменти, щоб полегшити розуміння. Його повна версія тут. Там набагато більше команд, і деякі залежать від зовнішніх скриптів. Тому вам слід адаптувати його під себе.

Якщо ви використовуєте git, ви можете встановити pre-commit hook, який буде запускати make test перед кожним коммітом. Якщо хоч якийсь тест не буде пройдено, комміт не відбудеться. Щоб зробити це, створіть файл .git/hooks/pre-commit з наступним змістом:

#!/bin/sh

make test

і дайте права на виконання:

chmod +x .git/hooks/pre-commit

Також ви можете налаштувати post-commit та post-merge за своїми потребами.

Напишіть скрипт перевірки середовища

Іноді проект має зовнішні залежності. Якщо така залежність недоступна, проект не буде працювати.

Такими залежностями можуть бути бази даних, інтернет-з'єднання, системні команди доступ до інших систем, розмір диску тощо.

А так як ви сумлінний розробник, то повинні написати скрипт, що перевіряє доступність всіх цих сервісів. Це полегшить життя вам при розгортанні на іншій машині та пошуку помилок.

Налаштуйте логування

Логування повинно бути в кожному проекті. Модуль logging досить гнучкий та потужний, а ще входить в стандартну бібліотеку, що означає, що він доступний завжди. Але його складно вивчити та налаштувати.

Моя порада: налаштуйте його один раз і забудьте назавжди. Я вчив його по документації, там багато прикладів і вона досить зрозуміла.

Використання, на відміну від конфігурації, дуже просте.

Ось так його використовують:

import logging

logger = logging.getLogger(__name__)


def my_function(param1):
    logger.info(u"запускаю my_function()")
    # ...
    logger.error(u"щось пішло не так, param1=%s", param1)
    # ...

Але не забувайте, що ваші логи повинні бути корисними. Не флудьте в ваш лог-файл.

Не падати!

Якщо ви пишете демона, скрипт що працює довгий час або скрипт для роботи з багатьма елементами (наприклад, файлами), важливо щоб він не падав. Іноді це називають безпечним програмуванням.

Ви повинні знати місця, де програма може впасти і ретельно їх обробляти.

Відловлюйте виключення правильно

KeyError, IndexError, IOError, DatabaseError та UnicodeDecodeError — досить поширені помилки, і ви повинні знати де вони можуть з'явитися і правильно їх обробляти.

Перевіряйте дані користувача

Завжди перевіряйте що написав користувач. Можете навіть вважати, що це ваш найзліший ворог і він більш за все хоче зламати вашу програму.

Реалізуйте повторні спроби

Іноді помилки бувають в середовищі. І вам не лишається нічого окрім як чекати. Навчіть чекати вашу програму, і зробіть цю систему досить гнучкою.

Падайте відразу

Якщо вам потрібно зробити велику кількість обрахунків, а потім записати в БД, то спершу слід перевірити з'єднання з БД, а лише потім проводити обрахунки. Інакше вони будуть марними.

Запускайте демонів за допомогою upstart, systemd чи supervisord

Зараз немає потреби запускати демона традиційними засобами Unix. Використовуйте щось із вищезазначених систем. Вони дозволяють автоматично запускати, перезапускати в разі невдачі. Деякі надають можливості логування.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.8K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація