08.05.2019       Выпуск 281 (06.05.2019 - 12.05.2019)       Статьи

Подборка @pythonetc, апрель 2019

Это десятая подборка советов про Python и программирование из моего авторского канала @pythonetc.

Читать>>




Экспериментальная функция:

Ниже вы видите текст статьи по ссылке. По нему можно быстро понять ссылка достойна прочтения или нет

Просим обратить внимание, что текст по ссылке и здесь может не совпадать.

Это десятая подборка советов про Python и программирование из моего авторского канала @pythonetc.

Предыдущие подборки

.


Хранение и отправка объектов по сети в виде байтов — это очень большая тема. Для этих целей в Python обычно используется ряд инструментов, давайте обсудим их достоинства и недостатки.

В качестве примера я попробую сериализовать объект Cities, который содержит объекты City, расположенные в определённом порядке. Можно воспользоваться четырьмя подходами:

1. JSON. Человекочитаемый, простой в использовании, но потребляет много памяти. То же самое справедливо в отношении форматов YAML и XML.

class City:
    def to_dict(self):
        return dict(
            name=self._name,
            country=self._country,
            lon=self._lon,
            lat=self._lat,
        )

class Cities:
    def __init__(self, cities):
        self._cities = cities

    def to_json(self):
        return json.dumps([
            c.to_dict() for c in self._cities
        ]).encode('utf8')

2. Pickle. Это нативный инструмент Python, настраиваемый, потребляет меньше памяти, чем JSON. Недостаток: для извлечения данных нужно использовать Python.

class Cities:
    def pickle(self):
        return pickle.dumps(self)

3. Protobuf (и прочие бинарные сериализаторы, например, msgpack). Потребляет ещё меньше памяти, может использоваться из любого языка программирования, но требует написания явной схемы:

syntax = "proto2";


message City {
    required string name = 1;
    required string country = 2;
    required float lon = 3;
    required float lat = 4;
}

message Cities {
    repeated City cities = 1;
}

class City:
    def to_protobuf(self):
        result = city_pb2.City()
        result.name = self._name
        result.country = self._country
        result.lon = self._lon
        result.lat = self._lat

        return result

class Cities:
    def to_protobuf(self):
        result = city_pb2.Cities()
        result.cities.extend([
            c.to_protobuf() for c in self._cities
        ])

        return result

4. Вручную. Вы можете вручную упаковывать и распаковывать данные с помощью модуля

struct

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

protobuf

, поскольку он поддерживает версионирование и явные схемы.

class City:
    def to_bytes(self):
        name_encoded = self._name.encode('utf8')
        name_length = len(name_encoded)

        country_encoded = self._country.encode('utf8')
        country_length = len(country_encoded)

        return struct.pack(
            'BsBsff',
            name_length, name_encoded,
            country_length, country_encoded,
            self._lon, self._lat,
        )

class Cities:
    def to_bytes(self):
        return b''.join(
            c.to_bytes() for c in self._cities
        )

Если аргумент функции имеет значение по умолчанию

None

и аннотирован как

T

, тогда

mypy

автоматически будет считать его

Optional[T]

(то есть

Union[T, None]

).

С другими типами это не работает, так что у вас не получится написать что-нибудь вроде

f(x: A = B())

. Также этот трюк не работает с присвоением переменной:

a: A = None

приведёт к ошибке.

def f(x: int = None):
    reveal_type(x)

def g(y: int = 'x'):
    reveal_type(y)

z: int = None
reveal_type(z)

$ mypy test.py
test.py:2: error: Revealed type is 'Union[builtins.int, None]'
test.py:4: error: Incompatible default for argument "y" (default has type "str", argument has type "int")
test.py:5: error: Revealed type is 'builtins.int'
test.py:7: error: Incompatible types in assignment (expression has type "None", variable has type "int")
test.py:8: error: Revealed type is 'builtins.int'

***

В Python 3, при выходе из блока

except

переменные, хранящие пойманные исключения, удаляются из

locals()

, даже если они уже существовали:

>>> e = 2
>>> try:
...     1/0
... except Exception as e:
...     pass
... 
>>> e
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'e' is not defined

Если хотите сохранить ссылку на исключение, нужно использовать другую переменную:

>>> error = None
>>> try:
...     1/0
... except Exception as e:
...     error = e
... 
>>> error
ZeroDivisionError('division by zero',)

В Python 2, однако, этого не происходит.


Вы можете легко сделать свой собственный

pypi

-репозиторий. Он позволяет выпускать пакеты внутри вашего проекта и устанавливать их с помощью

pip

, как если бы они были обычными пакетами.

Важно отметить, что вам не нужно устанавливать какое-то особое ПО, вы можете использовать обычный HTTP-сервер. Вот как это работает у меня.

Возьмём примитивный пакет

pythonetc

.

setup.py:

from setuptools import setup, find_packages

setup(
    name='pythonetc',
    version='1.0',
    packages=find_packages(),
)

pythonetc.py:

def ping():
    return 'pong'

Сделаем его релиз в директорию

~/pypi

:

$ python setup.py sdist bdist_wheel
…
$ mv dist ~/pypi/pythonetc

И начнём предоставлять пакет с домена

pypi.pushtaev.ru

с помощью nginx:

$ cat /etc/nginx/sites-enabled/pypi
server {
        listen 80;
        server_name pypi.pushtaev.ru;
        root /home/vadim/pypi;

        index index.html index.htm index.nginx-debian.html;

        location / {
                autoindex on;
                try_files $uri $uri/ =404;
        }
}

Теперь пакет можно установить:

$ pip install -i http://pypi.pushtaev.ru --trusted-host pypi.pushtaev.ru pythonetc
…
Collecting pythonetc
  Downloading http://pypi.pushtaev.ru/pythonetc/pythonetc-1.0-py3-none-any.whl
Installing collected packages: pythonetc
Successfully installed pythonetc-1.0
$ python
Python 3.7.0+ (heads/3.7:0964aac, Mar 29 2019, 00:40:55)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pythonetc
>>> pythonetc.ping()
'pong'

Часто нужно объявлять словарь с ключами, одноименными локальным переменным. Например:

dict(
    context=context,
    mode=mode,
    action_type=action_type,
)

В ECMAScript для таких случаев даже есть специальная форма литерала

object

(называется Object Literal Property Value Shorthand):

> var a = 1;
< undefined
> var b = 2;
< undefined
> {a, b}
< {a: 1, b: 2}

Можно создать такой же помощник и в Python (увы, он вовсе не так хорош, как нотация в ECMAScript):

def shorthand_dict(lcls, names):
    return {k: lcls[k] for k in names}

context = dict(user_id=42, user_ip='1.2.3.4')
mode = 'force'
action_type = 7

shorthand_dict(locals(), [
    'context',
    'mode',
    'action_type',
])

Вы можете спросить, зачем передавать

locals()

в качестве параметра? А можно получать

locals

вызывающего объекта в вызываемом? Можно, но придётся воспользоваться модулем

inspect

:

import inspect

def shorthand_dict(names):
    lcls = inspect.currentframe().f_back.f_locals
    return {k: lcls[k] for k in names}

context = dict(user_id=42, user_ip='1.2.3.4')
mode = 'force'
action_type = 7

shorthand_dict([
    'context',
    'mode',
    'action_type',
])

Можно пойти ещё дальше и применить такое решение —

https://github.com/alexmojaki/sorcery

:

from sorcery import dict_of
dict_of(context, mode, action_type)





Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus