company_banner

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



    Это десятая подборка советов про 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)
    
    • +29
    • 5,9k
    • 1
    Mail.ru Group
    1 340,53
    Строим Интернет
    Поделиться публикацией

    Похожие публикации

    Комментарии 1

      0

      Долго думал о последней проблеме — что происходит, и зачем это нужно. Посмотрел примеры в репозитории sorcery, опять не понял, а потом прочитал под заголовоком


      Dark magic delights in Python

      После этого все стало на свои места :) Just for fun забавно конечно, но в дайджесте с практичными советами выглядит немного странно...

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое