
C'est une nouvelle sélection de trucs et astuces sur Python et la programmation de mon canal Telegram @pythonetc.
Publications précédentes .
Le stockage et l'envoi d'objets via le réseau sous forme d'octets est un sujet très important. Discutons de quelques outils qui sont généralement utilisés pour cela en Python et de leurs avantages et inconvénients.
À titre d'exemple, je vais essayer de sérialiser l'objet Cities qui contient certains objets City ainsi que leur ordre. Voici quatre méthodes que vous pouvez utiliser:
1. JSON. Il est lisible par l'homme, facile à utiliser, mais consomme beaucoup de mémoire. Il en va de même pour d'autres formats comme YAML ou 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. Pickle est natif pour Python, peut être personnalisé et consomme moins de mémoire que JSON. L'inconvénient est que vous devez utiliser Python pour décompresser les données.
class Cities: def pickle(self): return pickle.dumps(self)
3. Protobuf (et autres sérialiseurs binaires tels que msgpack). Consomme encore moins de mémoire, peut être utilisé à partir de tout autre langage de programmation, mais nécessite un schéma personnalisé:
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. Manuel. Vous pouvez emballer et décompresser manuellement les données avec le module struct. Il vous permet de consommer la quantité minimale absolue de mémoire, mais protobuf peut toujours être un meilleur choix car il prend en charge la gestion des versions et les schémas explicites.
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 )
Si un argument de fonction a la valeur par défaut
None
et est annoté comme
T
,
mypy
le traite automatiquement comme
Optional[T]
(en d'autres termes,
Union[T, None]
).
Cela ne fonctionne pas avec d'autres types, vous ne pouvez donc pas avoir quelque chose comme
f(x: A = B())
. Cela ne fonctionne pas non plus avec une affectation de variable:
a: A = None
provoquera une erreur.
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'
Dans Python 3, une fois que le bloc
except
est fermé, les variables qui stockent les exceptions interceptées sont supprimées de
locals()
même si elles existaient auparavant:
>>> 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
Si vous souhaitez enregistrer une référence à l'exception, vous devez utiliser une autre variable:
>>> error = None >>> try: ... 1/0 ... except Exception as e: ... error = e ... >>> error ZeroDivisionError('division by zero',)
Ce n'est pas vrai pour Python 2.
Vous pouvez avoir votre propre référentiel
pypi
. Il vous permet de publier des packages dans votre projet et de les installer avec pip comme s'il s'agissait de packages standard.
Il est remarquable que vous n'ayez pas à installer de logiciel spécifique, mais que vous pouvez utiliser un serveur http normal à la place. Voici comment cela fonctionne pour moi, par exemple.
Ayons un paquet trivial nommé
pythonetc
.
setup.py: from setuptools import setup, find_packages setup( name='pythonetc', version='1.0', packages=find_packages(), ) pythonetc.py: def ping(): return 'pong'
Libérons-le dans le répertoire ~ / pypi:
$ python setup.py sdist bdist_wheel … $ mv dist ~/pypi/pythonetc
Maintenant,
pypi.pushtaev.ru
le sur le domaine
pypi.pushtaev.ru
avec 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; } }
Il peut maintenant être installé:
$ 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'
C'est assez souvent lorsque vous devez déclarer un dictionnaire avec toutes les clés égales aux variables locales du même nom. Quelque chose comme ça:
dict( context=context, mode=mode, action_type=action_type, )
ECMAScript a même la forme spéciale de littéral d'objet pour de tels cas (il est appelé raccourci de valeur de propriété de littéral d'objet):
> var a = 1; < undefined > var b = 2; < undefined > {a, b} < {a: 1, b: 2}
Il est possible de créer un assistant similaire en Python (hélas, il n'est même pas aussi bon que la notation 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', ])
Vous vous demandez peut-être pourquoi nous devons passer
locals()
comme paramètre dans l'exemple précédent. Est-il possible d'obtenir les
locals
de l'appelant dans l'appelé? En effet, mais vous devez jouer avec le module
inpsect
:
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', ])
Vous pouvez aller encore plus loin et utiliser quelque chose comme ça -
https://github.com/alexmojaki/sorcery :
from sorcery import dict_of dict_of(context, mode, action_type)