
Ceci est la dixième collection de conseils et de programmation Python de mon flux @pythonetc.
Sélections précédentes .
Le stockage et l'envoi d'objets sur le réseau sous forme d'octets est un sujet très important. À ces fins, Python utilise généralement un certain nombre d'outils, discutons de leurs avantages et inconvénients.
Par exemple, je vais essayer de sérialiser un objet Cities qui contient des objets City dans un ordre spécifique. Quatre approches peuvent être utilisées:
1. JSON. Lisible par l'homme, facile à utiliser, mais consomme beaucoup de mémoire. Il en va de même pour les formats YAML et 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. Il s'agit d'un outil Python natif, personnalisable, consomme moins de mémoire que JSON. Inconvénient: Python doit être utilisé pour récupérer des données.
class Cities: def pickle(self): return pickle.dumps(self)
3. Protobuf (et d'autres sérialiseurs binaires, par exemple, msgpack). Il consomme encore moins de mémoire, peut être utilisé à partir de n'importe quel langage de programmation, mais nécessite l'écriture d'un schéma explicite:
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. Manuellement. Vous pouvez emballer et décompresser manuellement les données à l'aide du module
struct
. De cette façon, vous pouvez obtenir la consommation de mémoire la plus faible possible, mais il est parfois préférable d'utiliser
protobuf
, 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 l'argument de la fonction a la valeur par défaut
None
et est annoté
T
, alors
mypy
le considérera automatiquement comme
Optional[T]
(c'est-à-dire
Union[T, None]
).
Cela ne fonctionne pas avec d'autres types, vous ne pourrez donc pas écrire quelque chose comme
f(x: A = B())
. En outre, cette astuce ne fonctionne pas avec l'affectation des variables:
a: A = None
entraînera 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, lorsque vous quittez le bloc
except
, les variables qui stockent les exceptions interceptées sont supprimées de
locals()
, même si elles existaient déjà:
>>> 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 conserver le lien vers l'exception, vous devez utiliser une autre variable:
>>> error = None >>> try: ... 1/0 ... except Exception as e: ... error = e ... >>> error ZeroDivisionError('division by zero',)
En Python 2, cependant, cela ne se produit pas.
Vous pouvez facilement créer 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 important de noter que vous n'avez pas besoin d'installer de logiciel spécial, vous pouvez utiliser un serveur HTTP standard. Voilà comment cela fonctionne pour moi.
Prenez le paquet
pythonetc
primitif.
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
Et nous allons commencer à fournir le package du domaine
pypi.pushtaev.ru
utilisant 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; } }
Maintenant, le package peut ê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'
Souvent, vous devez déclarer un dictionnaire avec des clés du même nom que les variables locales. Par exemple:
dict( context=context, mode=mode, action_type=action_type, )
Dans de tels cas, ECMAScript a même une forme spéciale du littéral
object
(appelée raccourci de valeur de propriété de littéral objet):
> var a = 1; < undefined > var b = 2; < undefined > {a, b} < {a: 1, b: 2}
Vous pouvez créer le même assistant en Python (hélas, ce n'est pas du tout aussi bon que la notation dans 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 passer
locals()
comme paramètre? Est-il possible d'obtenir des
locals
objet appelant dans l'appelé? Vous pouvez, mais vous devez utiliser le module d'
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', ])
Vous pouvez aller encore plus loin et appliquer une telle solution -
https://github.com/alexmojaki/sorcery :
from sorcery import dict_of dict_of(context, mode, action_type)