
Es una nueva selección de consejos y trucos sobre Python y la programación de mi canal de Telegram @pythonetc.
Publicaciones anteriores
Almacenar y enviar objetos a través de la red como bytes es un gran tema. Analicemos algunas herramientas que generalmente se usan para eso en Python y sus ventajas y desventajas.
Como ejemplo, intentaré serializar el objeto Ciudades que contiene algunos objetos Ciudad, así como su orden. Aquí hay cuatro métodos que puede usar:
1. JSON. Es legible para los humanos, fácil de usar, pero consume mucha memoria. Lo mismo es cierto para otros formatos como YAML o 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 es nativo de Python, se puede personalizar y consume menos memoria que JSON. La desventaja es que debe usar Python para eliminar los datos.
class Cities: def pickle(self): return pickle.dumps(self)
3. Protobuf (y otros serializadores binarios como msgpack). Consume incluso menos memoria, puede usarse desde cualquier otro lenguaje de programación, pero requiere un esquema personalizado:
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. Manual. Puede empaquetar y desempaquetar datos manualmente con el módulo de estructura. Le permite consumir la cantidad mínima absoluta de memoria, pero protobuf aún puede ser una mejor opción ya que admite versiones y esquemas explícitos.
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 argumento de función tiene el valor predeterminado de
None
y se anota como
T
,
mypy
trata automáticamente como
Optional[T]
(en otras palabras,
Union[T, None]
).
Eso no funciona con otros tipos, por lo que no puede tener algo como
f(x: A = B())
. Tampoco funciona con una asignación variable:
a: A = None
causará un error.
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'
En Python 3, una vez que se sale del bloque
except
, las variables que almacenan las excepciones capturadas se eliminan de los
locals()
incluso si existían previamente:
>>> 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 desea guardar una referencia a la excepción, debe usar otra variable:
>>> error = None >>> try: ... 1/0 ... except Exception as e: ... error = e ... >>> error ZeroDivisionError('division by zero',)
Esto no es cierto para Python 2.
Puede tener su propio repositorio
pypi
. Le permite lanzar paquetes dentro de su proyecto e instalarlos con pip como si fueran paquetes regulares.
Es notable que no tenga que instalar ningún software específico, sino que puede usar un servidor http normal. Así es como funciona para mí, por ejemplo.
Tengamos un paquete trivial llamado
pythonetc
.
setup.py: from setuptools import setup, find_packages setup( name='pythonetc', version='1.0', packages=find_packages(), ) pythonetc.py: def ping(): return 'pong'
Vamos a lanzarlo al directorio ~ / pypi:
$ python setup.py sdist bdist_wheel … $ mv dist ~/pypi/pythonetc
Ahora servidor esto en el dominio
pypi.pushtaev.ru
con 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; } }
Ahora se puede instalar:
$ 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'
Es bastante frecuente cuando tiene que declarar un diccionario con todas las claves iguales a las variables locales con el mismo nombre. Algo como esto:
dict( context=context, mode=mode, action_type=action_type, )
ECMAScript incluso tiene la forma especial de literal de objeto para tales casos (se llama abreviatura de valor de propiedad literal de objeto):
> var a = 1; < undefined > var b = 2; < undefined > {a, b} < {a: 1, b: 2}
Es posible crear un asistente similar en Python (por desgracia, ni siquiera se ve tan bien como la notación 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', ])
Quizás se pregunte por qué tenemos que pasar
locals()
como parámetro en el ejemplo anterior. ¿Es posible obtener los
locals
de la persona que llama en la persona que llama? De hecho lo es, pero tienes que
inpsect
con el módulo
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', ])
Puede ir aún más lejos y usar algo como esto:
https://github.com/alexmojaki/sorcery :
from sorcery import dict_of dict_of(context, mode, action_type)