Upload
andrew-svetlov
View
536
Download
2
Embed Size (px)
DESCRIPTION
My slides from pycon.ru 2014
Citation preview
Успешный Open Source
Андрей Светлов
О себе
● 15 лет использования Python● Python Core Developer● Соавтор нескольких библиотек (asyncio,
aiohttp, aiozmq, aiopg)● [email protected]● http://asvetlov.blogspot.com
Код на github
Этого явно недостаточно
Публичность
● Не знают — не используют.● Статьи, блоги, stackoverflow и т.д.● Не жалейте время на bug tracker
Документация
● Шанс составить первое впечатление● Английский● Docstrings и README.rst — недостаточно● Нарративность● Примеры● readthedocs.org
Тесты
● Читаемые и хорошо организованные● Полные● coverage.py● Как минимум 95% покрытие● Continuous Integration (travis-ci.org)
Код
● pep8 — обязательно● VCS (гитхаб прекрасен)● Версии продукта● Ветки, тэги, релизы● Примеры использования
Инфраструктура
● setup.py● PyPI● readthedocs● travis-ci● инструкция по установке
Ясность
Хорошая библиотека — простая библиотека
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
Public API
● по умолчанию всё "закрыть".● открывать только то что сознательно
желаем сделать открытым● Не нужно бояться "закрытых" классов
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
Понятное API
Если нелегко написать документацию — API плохое
Хорошие имена
twisted.internet.defer.Deferred
Twised's Deferred does something — OMG!
asyncio.Future
Трудность выбора имен
ZeroMQ Req/Rep, Push/Pull и Pub/SubRequester? Pusher???
RPC Извещения Подписка
Клиент Req Push Pub
Сервер Rep Pull Sub
Решение
connect_rpc — RPCClientserve_rpc — RPCServerconnect_pipeline — PipelineClientserve_pipeline — PipelineServerconnect_pubsub — PubSubClientserve_pubsub — PubSubServer
Наследование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()
Агрегация лучше наследования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
Естественная полнота APIclass Subscriber:
def subscribe(self, name):
pass
class Subscriber:
def subscribe(self, name):
pass
def unsubscribe(self, name):
pass
@property
def subscriptions(sef):
pass
Предметная область
Матрицыa * ba @ b
a + blen(a)
Устоявшиеся соглашения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)
Магические методы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)
Коллекции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')
Неудачный пример
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)
Исключения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
Singletons
Просто зло.Я их не использую.
Логирование
logging.getLogger('').info('log message')Настраивание объема сообщений в логproduction != dev environmentбиблиотека != приложение пользователя
Проверка параметров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')
библиотека != приложение
● Обратная совместимость● Внимание к деталям● Простота и понятность● Документация
Вопросы?