@Pythonetc Septiembre 2018


Esta es la cuarta colección de consejos y programación de Python de mi feed @pythonetc .


Selecciones anteriores:



Anulación y sobrecarga


Hay dos conceptos que son fáciles de confundir: anulación y sobrecarga.


La anulación ocurre cuando una clase secundaria define un método ya proporcionado por las clases principales y, por lo tanto, lo reemplaza. En algunos idiomas, es necesario marcar explícitamente el método de anulación (en C # se usa el modificador de override ), y en algunos idiomas esto se hace como se desee (anotación de @Override en Java). Python no requiere el uso de un modificador especial y no proporciona el marcado estándar de dichos métodos (en aras de la legibilidad, alguien usa el decorador personalizado @override , que no hace nada).


La sobrecarga es otra historia. Este término se refiere a una situación en la que hay varias funciones con el mismo nombre, pero con firmas diferentes. La sobrecarga es posible en Java y C ++; a menudo se usa para proporcionar argumentos predeterminados:


 class Foo { public static void main(String[] args) { System.out.println(Hello()); } public static String Hello() { return Hello("world"); } public static String Hello(String name) { return "Hello, " + name; } } 

Python no admite la búsqueda de funciones por firma, solo por nombre. Por supuesto, puede escribir código que analice explícitamente los tipos y la cantidad de argumentos, pero esto se verá incómodo, y esta práctica es mejor evitarla:


 def quadrilateral_area(*args): if len(args) == 4: quadrilateral = Quadrilateral(*args) elif len(args) == 1: quadrilateral = args[0] else: raise TypeError() return quadrilateral.area() 

Si necesita sugerencias de typing , use el módulo de typing con el decorador @overload :


 from typing import overload @overload def quadrilateral_area( q: Quadrilateral ) -> float: ... @overload def quadrilateral_area( p1: Point, p2: Point, p3: Point, p4: Point ) -> float: ... 

Vivificación automática


collections.defaultdict permite crear un diccionario que devuelve un valor predeterminado si falta la clave solicitada (en lugar de arrojar un KeyError ). Para crear un defaultdict predeterminado defaultdict debe proporcionar no solo un valor predeterminado, sino una fábrica de dichos valores.


Por lo tanto, puede crear un diccionario con un número prácticamente infinito de diccionarios anidados, lo que le permite utilizar construcciones como d[a][b][c]...[z] .


 >>> def infinite_dict(): ... return defaultdict(infinite_dict) ... >>> d = infinite_dict() >>> d[1][2][3][4] = 10 >>> dict(d[1][2][3][5]) {} 

Este comportamiento se llama "auto-vivificación", un término que proviene de Perl.


Instanciación


La instanciación de objetos implica dos pasos importantes. Primero, el método __new__ se llama desde la clase, que crea y devuelve un nuevo objeto. Luego, Python llama al método __init__ , que establece el estado inicial de este objeto.


Sin embargo, __init__ no se llamará si __new__ devuelve un objeto que no es una instancia de la clase original. En este caso, el objeto podría ser creado por otra clase, lo que significa que __init__ ya ha sido invocado en el objeto:


 class Foo: def __new__(cls, x): return dict(x=x) def __init__(self, x): print(x) # Never called print(Foo(0)) 

También significa que no debe crear una instancia de la misma clase en __new__ usando el constructor regular ( Foo(...) ). Esto puede conducir a la ejecución repetida de __init__ , o incluso a una recursión infinita.


Recursión infinita:


 class Foo: def __new__(cls, x): return Foo(-x) # Recursion 

Doble ejecución __init__ :


 class Foo: def __new__(cls, x): if x < 0: return Foo(-x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

La forma correcta:


 class Foo: def __new__(cls, x): if x < 0: return cls.__new__(cls, -x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

El operador [] y los cortes


En Python, puede anular el operador [] definiendo el método mágico __getitem__ . Entonces, por ejemplo, puede crear un objeto que virtualmente contenga un número infinito de elementos repetidos:


 class Cycle: def __init__(self, lst): self._lst = lst def __getitem__(self, index): return self._lst[ index % len(self._lst) ] print(Cycle(['a', 'b', 'c'])[100]) # 'b' 

Lo inusual aquí es que el operador [] admite una sintaxis única. Al usarlo puede obtener no solo [2] , sino también [2:10] , [2:10:2] , [2::2] e incluso [:] . La semántica del operador es: [inicio: detener: paso], sin embargo, puede usarlo de cualquier otra manera para crear objetos personalizados.


Pero si llama a __getitem__ con esta sintaxis, ¿qué obtendrá como parámetro de índice? Esto es exactamente por qué existen los objetos de corte.


 In : class Inspector: ...: def __getitem__(self, index): ...: print(index) ...: In : Inspector()[1] 1 In : Inspector()[1:2] slice(1, 2, None) In : Inspector()[1:2:3] slice(1, 2, 3) In : Inspector()[:] slice(None, None, None) 

Incluso puede combinar la sintaxis de tuplas y sectores:


 In : Inspector()[:, 0, :] (slice(None, None, None), 0, slice(None, None, None)) 

slice no hace nada, solo almacena los atributos start , stop y step .


 In : s = slice(1, 2, 3) In : s.start Out: 1 In : s.stop Out: 2 In : s.step Out: 3 

Interrupción de la rutina de Asyncio


Cualquier asyncio asyncio ejecutable puede ser abortado usando el método cancel() . En este caso, se enviará un CanceledError a la corutina, como resultado, esta y todas las corutinas asociadas con ella se interrumpirán hasta que el error sea detectado y eliminado.


CancelledError es una subclase de Exception , lo que significa que puede detectarse accidentalmente mediante una combinación de try ... except Exception , diseñada para detectar "cualquier error". Para atrapar con seguridad un error de una rutina, debe hacer esto:


 try: await action() except asyncio.CancelledError: raise except Exception: logging.exception('action failed') 

Planificación de la ejecución


Para planificar la ejecución de algún código en un momento determinado, asyncio generalmente crea una tarea que se ejecuta await asyncio.sleep(x) :


 import asyncio async def do(n=0): print(n) await asyncio.sleep(1) loop.create_task(do(n + 1)) loop.create_task(do(n + 1)) loop = asyncio.get_event_loop() loop.create_task(do()) loop.run_forever() 

Pero crear una nueva tarea puede ser costoso, y no es necesario hacerlo si no planea realizar operaciones asincrónicas (como la función do en mi ejemplo). En su lugar, puede usar las funciones loop.call_later y loop.call_at , que le permiten programar una llamada de devolución de llamada asíncrona:


 import asyncio def do(n=0): print(n) loop = asyncio.get_event_loop() loop.call_later(1, do, n+1) loop.call_later(1, do, n+1) loop = asyncio.get_event_loop() do() loop.run_forever() 

Source: https://habr.com/ru/post/es425125/


All Articles