31
Успешный Open Source Андрей Светлов

Writing Open Source Library

Embed Size (px)

DESCRIPTION

My slides from pycon.ru 2014

Citation preview

Page 1: Writing Open Source Library

Успешный Open Source

Андрей Светлов

Page 2: Writing Open Source Library

О себе

● 15 лет использования Python● Python Core Developer● Соавтор нескольких библиотек (asyncio,

aiohttp, aiozmq, aiopg)● [email protected]● http://asvetlov.blogspot.com

Page 3: Writing Open Source Library
Page 4: Writing Open Source Library

Код на github

Этого явно недостаточно

Page 5: Writing Open Source Library

Публичность

● Не знают — не используют.● Статьи, блоги, stackoverflow и т.д.● Не жалейте время на bug tracker

Page 6: Writing Open Source Library

Документация

● Шанс составить первое впечатление● Английский● Docstrings и README.rst — недостаточно● Нарративность● Примеры● readthedocs.org

Page 7: Writing Open Source Library

Тесты

● Читаемые и хорошо организованные● Полные● coverage.py● Как минимум 95% покрытие● Continuous Integration (travis-ci.org)

Page 8: Writing Open Source Library

Код

● pep8 — обязательно● VCS (гитхаб прекрасен)● Версии продукта● Ветки, тэги, релизы● Примеры использования

Page 9: Writing Open Source Library

Инфраструктура

● setup.py● PyPI● readthedocs● travis-ci● инструкция по установке

Page 10: Writing Open Source Library

Ясность

Хорошая библиотека — простая библиотека

Page 11: Writing Open Source Library

Pubic и Private APIclass A:

def __init__(self, param):

self.param = param

def public(self):

return self.private()

def private(self):

return self.param * 2

class A:

def __init__(self, param):

self._param = param

def public(self):

return self._private()

def _private(self):

return self._param * 2

Page 12: Writing Open Source Library

Public API

● по умолчанию всё "закрыть".● открывать только то что сознательно

желаем сделать открытым● Не нужно бояться "закрытых" классов

Page 13: Writing Open Source Library

Docstringsclass A:

def __init__(self, param):

self._param = param

def public(self):

return self._private()

def _private(self):

"""Private function"""

return self._param * 2

class A:

"""Docstring for class"""

def __init__(self, param):

self._param = param

def public(self):

"""This is public"""

return self._private()

def _private(self):

# Private function

return self._param * 2

Page 14: Writing Open Source Library

Понятное API

Если нелегко написать документацию — API плохое

Page 15: Writing Open Source Library

Хорошие имена

twisted.internet.defer.Deferred

Twised's Deferred does something — OMG!

asyncio.Future

Page 16: Writing Open Source Library

Трудность выбора имен

ZeroMQ Req/Rep, Push/Pull и Pub/SubRequester? Pusher???

RPC Извещения Подписка

Клиент Req Push Pub

Сервер Rep Pull Sub

Page 17: Writing Open Source Library

Решение

connect_rpc — RPCClientserve_rpc — RPCServerconnect_pipeline — PipelineClientserve_pipeline — PipelineServerconnect_pubsub — PubSubClientserve_pubsub — PubSubServer

Page 18: Writing Open Source Library

Наследованиеclass Base:

def do_work(self):

self.handle('param')

class Derived(Base):

def handle(self, param):

do_something()

class Base(metaclass=abc.ABCMeta):

def do_work(self):

self.handle('param')

@abc.abstractmethod

def handle(self, param):

pass

class Derived(Base):

def handle(self, param):

do_something()

Page 19: Writing Open Source Library

Агрегация лучше наследованияclass Service:

def do_work(self):

self.log('operaion')

def log(self, param):

pass

class SocketLoggerMixin:

def log(self, param):

self.socket.send(param)

class SocketLoggingService(

SocketLoggerMixin, Service):

pass

class Service:

def __init__(self, logger):

self._logger = logger

def do_work(self):

self._logger.log('operaion')

class Logger:

def log(self, param):

pass

Page 20: Writing Open Source Library

Естественная полнота APIclass Subscriber:

def subscribe(self, name):

pass

class Subscriber:

def subscribe(self, name):

pass

def unsubscribe(self, name):

pass

@property

def subscriptions(sef):

pass

Page 21: Writing Open Source Library

Предметная область

Матрицыa * ba @ b

a + blen(a)

Page 22: Writing Open Source Library

Устоявшиеся соглашенияclass Point:

def __init__(self, a, b):

self.a, self.b = a, b

def __add__(self, other):

self.a += other.a

self.b += other.b

return self

class Point:

def __init__(self, x, y):

self._x, self._y = x, y

x = property(lambda self: self._x)

y = property(lambda self: self._y)

def __add__(self, other):

return Point(self._x + other._x,

self._y + other._y)

Page 23: Writing Open Source Library

Магические методыdef __eq__(self, other):

if isinstance(other, Point):

return self._x == other._x and self._y == other._y

else:

return NotImplented

def __ne__(self, other):

return not self == other

def __hash__(self):

return (self._x, self._y)

Page 24: Writing Open Source Library

Коллекцииclass ReadonlyDict(collections.abc.Mapping):

def __init__(self, dct):

self._dct = dct

def __len__(self):

return len(self._dct)

def __iter__(self):

return iter(self._dct)

def __getitem__(self, key):

return self._dct[key]

assert set(ReadonlyDict({'a': 1, 'b': 2}).keys()) == set('a', 'b')

Page 25: Writing Open Source Library

Неудачный пример

sqlalchemy.engine.result.RowProxyЭто, хмм, abc.Sequencelen(result_proxy)

'column_name' in result_proxy

result_proxy[0]

result_proxy['column_name']

result_proxy[1: 5]

result_proxy1 < result_proxy2

iter(result_proxy)

Page 26: Writing Open Source Library

Исключенияdef login(login, passwd):

res = db.execute(

"SELECT * FROM users "

"WHERE login = %s", login)

return res['passwd'] == passwd

def login(login, passwd):

try:

res = db.execute(

"SELECT * FROM users "

"WHERE login = %s", login)

if res['passwd'] != passwd:

raise CannotLogin()

except DatabaseNotFoundError as ex:

raise CannotLogin() from ex

Page 27: Writing Open Source Library

Singletons

Просто зло.Я их не использую.

Page 28: Writing Open Source Library

Логирование

logging.getLogger('').info('log message')Настраивание объема сообщений в логproduction != dev environmentбиблиотека != приложение пользователя

Page 29: Writing Open Source Library

Проверка параметровdef write(self, data):

if not isinstance(data, (bytes, bytearray, memoryview)):

raise TypeError('data argument must be byte-ish (%r)',

type(data))

if server_side:

if not sslcontext:

raise ValueError('Server side ssl needs a valid SSLContext')

Page 30: Writing Open Source Library

библиотека != приложение

● Обратная совместимость● Внимание к деталям● Простота и понятность● Документация

Page 31: Writing Open Source Library

Вопросы?

[email protected]