Érase una vez, en mis días de estudiante, fui mordido por una pitón, aunque el período de incubación se retrasó y resultó que me convertí en un programador de perlas.
Sin embargo, en algún momento, la perla se agotó y decidí tomar Python, al principio solo hice algo y descubrí lo que se necesitaba para esta tarea, y luego me di cuenta de que necesitaba algún tipo de conocimiento sistemático y leí varios libros:
- Bill Lyubanovich “Python simple. Estilo moderno de programación "
- Dan Bader "Python puro. Las sutilezas de la programación para profesionales »
- Brett Slatkin "Secretos de Python: 59 consejos para escribir código efectivo"
Lo que me pareció bastante adecuado para comprender las sutilezas básicas del lenguaje, aunque no recuerdo haber mencionado espacios en ellos, pero no estoy seguro de que sea una característica realmente necesaria: si lo presioné de memoria, lo más probable es que este método no sea suficiente, pero por supuesto Todo depende de la situación.
Como resultado, he acumulado algunas notas sobre las características de Python, que, según me parece, pueden ser útiles para alguien que quiera migrar a él desde otros idiomas.
Me di cuenta de que durante las entrevistas en Python con bastante frecuencia hacen preguntas sobre cosas que no están relacionadas con el desarrollo real, como cuál podría ser la clave del diccionario (o qué significa x = yield y
), muchachos, en la vida real, la clave puede ser solo un número o una cadena, en esos casos únicos cuando no es así, puede leer la documentación y descubrir por qué preguntar esto. ¿Encontrar lo que el entrevistado no sabe? Entonces, al final, todos recordarán la respuesta a esta pregunta en particular y dejará de funcionar.
Considero que las versiones de Python superiores a 3.5 son relevantes ( es hora de olvidarse del segundo Python durante mucho tiempo ) ya que Esta es la versión en Debian estable, lo que significa que en todos los demás lugares hay versiones más recientes)
Como no soy para nada un gurú de Python, espero que me corrijan en los comentarios si de repente congelo algún tipo de estupidez.
Escribiendo
Python es un lenguaje de tipo dinámico, es decir comprueba la coincidencia de tipos en tiempo de ejecución, por ejemplo:
cat type.py a=5 b='5' print(a+b)
realizar:
python3 type.py ... TypeError: unsupported operand type(s) for +: 'int' and 'str'
Sin embargo, si su proyecto ha madurado a la necesidad de tipeo estático, python también brinda esa oportunidad al usar el analizador estático mypy
:
mypy type.py type.py:3: error: Unsupported operand types for + ("int" and "str")
Es cierto que no todos los errores se detectan de esta manera:
cat type2.py def greeting(name): return 'Hello ' + name greeting(5)
mypy no jurará aquí, pero se producirá un error durante la ejecución, por lo que las versiones actuales de python admiten una sintaxis especial para especificar tipos de argumentos de función:
cat type3.py def greeting(name: str) -> str: return 'Hello ' + name greeting(5)
y ahora
mypy type3.py type3.py:4: error: Argument 1 to "greeting" has incompatible type "int"; expected "str"
Variables y datos
Las variables en python no almacenan datos, sino que solo se refieren a ellos, y los datos pueden ser mutables (mutables) e inmutables (inmutables).
Esto conduce a un comportamiento diferente según el tipo de datos en situaciones casi idénticas, por ejemplo, un código de este tipo:
x = 1 y = x x = 2 print(y)
conduce al hecho de que las variables x
e y
refieren a datos diferentes, y esto:
x = [1, 2, 3] y = x x[0] = 7 print(y)
no, x
e y
siguen siendo enlaces a la misma lista (aunque, como se señaló en los comentarios, el ejemplo no es muy exitoso, pero aún no he pensado en uno mejor) que, por cierto, en Python puede verificar con el operador is
(estoy seguro de que el creador de Java siempre perderá un buen sueño) de vergüenza cuando me enteré de este operador en python).
Aunque las filas parecen una lista, son un tipo de datos inmutable, esto significa que la cadena en sí no se puede cambiar, solo puede crear una nueva, pero puede asignar un valor diferente a la variable, aunque los datos originales no cambiarán:
>>> mystr = 'sss' >>> newstr = mystr # >>> mystr[0] = 'a' ... TypeError: 'str' object does not support item assignment >>> mystr = 'ssa' # >>> newstr # 'sss'
Hablando de cadenas, debido a su inmunidad, la concatenación de una lista muy grande de cadenas agregando o agregando en un bucle puede no ser muy efectiva (dependiendo de la implementación en un compilador / versión en particular), generalmente para tales casos se recomienda usar el método de combinación , que se comporta un poco inesperado
>>> str_list = ['ss', 'dd', 'gg'] >>> 'XXX'.join(str_list) 'ssXXXddXXXgg' >>> str = 'hello' >>> 'XXX'.join(str) 'hXXXeXXXlXXXlXXXo'
En primer lugar, la línea donde se llama el método se convierte en un separador, y no en el comienzo de una nueva línea como se podría pensar, y en segundo lugar, debe pasar una lista (un objeto iterable), y no una línea separada, porque también es un objeto iterable y se simbolizará .
Dado que las variables son enlaces, es bastante normal querer hacer una copia de un objeto para no romper el objeto original, pero existe una trampa: la función de copia copia solo un nivel, que claramente no es lo que se espera de una función con ese nombre, así que use la deepcopy
.
Un problema similar con la copia puede ocurrir cuando una colección se multiplica por un escalar, como se explica aquí .
Alcance
El tema del alcance probablemente merece un artículo separado, pero hay una buena respuesta para SO .
En resumen, el alcance es léxico y hay seis áreas de visibilidad: variables en el cuerpo de la función, en el cierre, en el módulo, en el cuerpo de la clase, funciones de Python incorporadas y variables dentro de la lista y otras inclusiones.
Hay una sutileza: la variable predeterminada es legible en espacios de nombres anidados léxicamente, pero la modificación requiere el uso de palabras clave especiales nonlocal
y global
para modificar las variables un nivel más alto o visibilidad global, respectivamente.
Por ejemplo, un código como este:
x = 7 print(id(x)) def func(): print(id(x)) return x print(func())
Funciona con una variable global, y esta:
x = 7 print(id(x)) def func(): x = 1 print(id(x)) return x print(func()) print(x)
ya genera uno local.
Desde mi punto de vista, esto no es muy bueno, en principio, cualquier uso de variables no locales en una función es parte de la interfaz pública de la función, su firma, lo que significa que debe declararse explícitamente y ser visible al comienzo de la función. Además, las palabras clave no son muy informativas: global
suena como una definición de una función global, pero en realidad significa use global
.
En python no hay un punto de entrada obligatorio desde el que se inicia el programa, como se hace en muchos idiomas, solo todo lo que está escrito a nivel de módulo se ejecuta secuencialmente, sin embargo, dado que las variables a nivel de módulo son variables globales, desde mi punto de vista, debería ser una buena práctica meter el código principal en la función main()
, seguido de su llamada al final del archivo:
if __name__ == '__main__': main()
Esta condición funcionará si el archivo se llama como un script y no se importa como un módulo.
Argumentos de funciones
Python proporciona oportunidades simplemente elegantes para definir argumentos de funciones: argumentos posicionales, con nombre y sus combinaciones.
Pero debe comprender cómo se pasan los argumentos, porque en python, todas las variables son enlaces a datos, entonces puede adivinar que la transferencia es por referencia, pero hay una peculiaridad: el enlace en sí se pasa por valor, es decir puede modificar el valor mutable por referencia:
def add_element(mylist): mylist.append(3) mylist = [1,2] add_element(mylist) print(mylist)
realizar:
python3 arg_modify.py [1, 2, 3]
sin embargo, no puede sobrescribir el enlace original en una función:
def try_del(mylist): mylist = [] return mylist mylist = [1,2] try_del(mylist) print(mylist)
el enlace fuente está vivo y funcionando:
python3 arg_kill.py [1, 2]
También puede establecer valores predeterminados para los argumentos, pero hay algo que no es obvio para recordar: los valores predeterminados se calculan una vez al definir la función, esto no crea ningún problema si pasa datos sin cambios como el valor predeterminado, y si pasa datos variables o valor dinámico, el resultado será un poco inesperado:
datos mutables:
cat arg_list.py def func(arg = []): arg.append('x') return arg print(func()) print(func()) print(func())
resultado:
python3 arg_list.py ['x'] ['x', 'x'] ['x', 'x', 'x']
valor dinámico:
cat arg_now.py from datetime import datetime def func(arg = datetime.now()): return arg print(func()) print(func()) print(func())
obtenemos:
python3 arg_now.py 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879
OOP
OOP en python se ha hecho de manera muy interesante (algunas propiedades lo valen) y este es un gran tema, pero los sapiens familiarizados con OOP pueden googlear todo (o encontrarlo en el centro ), por lo que no necesita repetirlo, aunque vale la pena decir que Python debería ser un poco una filosofía diferente - es que un programador inteligente máquinas, y no es una amenaza (UPD: más ), por lo que el valor por defecto de Python no es habitual para otros modificadores de acceso idiomas: métodos privados implementadas mediante la adición de un doble guión bajo (que cambia el tiempo de ejecución del nombre del método no es OAPC oportunidad de usarlo), y protegido de un guión bajo (que no hace nada hilo, es sólo una convención de nomenclatura).
Aquellos que pierden la funcionalidad habitual pueden buscar intentos de traer tales oportunidades a Python, yo busqué en Google un par de opciones ( lang , python-access ), pero no las probé ni las estudié.
El único inconveniente de las clases estándar es el código repetitivo en cualquier método Dunder , personalmente me gusta la biblioteca attrs , es mucho más pitónica.
Vale la pena mencionar que dado que en Python todos los objetos, incluidas las funciones y clases, las clases se pueden crear dinámicamente (sin usar eval
) por la función type .
También vale la pena leer sobre metaclases ( en el Habr ) y descriptores ( Habr ).
Una peculiaridad que vale la pena recordar es que los atributos de una clase y un objeto no son lo mismo, en el caso de los atributos inmutables esto no causa problemas ya que los atributos son "sombreados": los atributos del objeto con el mismo nombre se crean automáticamente, pero en el caso de los atributos mutables, puede no obtener exactamente lo que se esperaba:
cat class_attr.py class MyClass: storage = [7,] def __init__(self, number): self.number = number obj = MyClass(1) obj2 = MyClass(2) obj.number = 5 obj.storage.append(8) print(obj2.storage, obj2.number)
obtenemos:
python3 class_attr.py [7, 8] 2
como puede ver, cambiaron obj
, y el storage
cambió en obj2
. Este atributo (a diferencia del number
) no pertenece a la instancia, sino a la clase.
Constantes
Como en el caso de los modificadores de acceso, python no intenta limitar el desarrollador, por lo tanto, es imposible definir una variable escalar protegida de la modificación de la manera estándar, simplemente existe un acuerdo de que las variables con un nombre en mayúsculas deben considerarse constantes.
Python, por otro lado, tiene estructuras de datos inmutables como la tupla, por lo que si desea hacer que una estructura global como una configuración sea inmutable y no desee dependencias adicionales, entonces nombrar tupla es una buena opción, aunque requerirá un poco más de esfuerzo para describir los tipos, por lo tanto Me gusta la implementación alternativa de la estructura inmutable con notación de punto - Box (ver parámetro frozen_box).
Bueno, si desea constantes escalares, puede implementar el control de acceso en la etapa de "compilación", es decir comprueba mypy, ejemplo y detalles .
.sort () vs sorted ()
Hay dos formas de ordenar una lista en python. El primero es el método .sort()
que modifica la lista original y no devuelve nada (Ninguno), es decir no puede hacer esto:
my_list = my_list.sort()
La segunda es la función sorted()
, que genera una nueva lista y puede funcionar con todos los objetos iterables. Quien quiera más información debe comenzar con SO .
Biblioteca estándar
Por lo general, la biblioteca estándar de Python incluye excelentes soluciones a problemas comunes, pero vale la pena ser crítica, porque hay suficientes rarezas. Es cierto que también sucede que lo que parece extraño a primera vista resulta ser la mejor solución, solo necesita conocer todas las condiciones (consulte el rango a continuación), pero todavía hay rarezas.
Por ejemplo, el módulo de la unidad unittest incluido en el kit no tiene nada que ver con python y huele a Java, por lo tanto, como dice el autor de python : "Eveybody está usando py.test ...". Aunque es bastante interesante, aunque no siempre es adecuado, el módulo doctest viene de serie.
El módulo urllib suministrado no tiene una interfaz tan hermosa como el módulo de solicitudes de terceros.
La misma historia con el módulo para analizar los parámetros de la línea de comandos: el argparse incluido es una demostración de OOP del cerebro, y el módulo docopt parece ser solo una solución inteligente: ¡la mejor autodocumentación! Aunque, según los rumores, a pesar del docopt y el clic, queda un nicho.
También con el depurador, tal como lo entiendo, pocas personas usan el pdb incluido en el paquete, hay muchas alternativas, pero parece que la mayoría de los desarrolladores usan ipdb , que, desde mi punto de vista, es más conveniente usarlo a través del módulo contenedor de depuración .
Permite, en lugar de import ipdb;ipdb.set_trace()
simplemente escribir import debug
, también agrega un módulo see para una fácil inspección de objetos.
Para reemplazar el módulo de serialización estándar, pickle se hace eneldo , por cierto, vale la pena recordar que estos módulos no son adecuados para el intercambio de datos en sistemas externos ya que restaurar objetos arbitrarios recibidos de una fuente no controlada no es seguro, para tales casos hay json (para REST) y gRPC (para RPC).
Para reemplazar el módulo estándar de procesamiento de expresiones regulares, re crea el módulo regex con todo tipo de extras adicionales, como las clases de caracteres ala \p{Cyrillic}
.
Por cierto, algo no se encontró para Python, un depurador divertido para expresiones regulares similares a la cebada perlada .
Aquí hay otro ejemplo: una persona creó su módulo en el lugar para corregir la curvatura y la incompletitud de la API del módulo de entrada de archivos estándar en la parte en el lugar de la edición de archivos.
Bueno, pienso mucho en estos casos, ya que incluso me he encontrado con más de uno, así que tenga cuidado y no olvide mirar todo tipo de listas útiles e increíbles , creo que un buen nutricionista tiene olfato para medir la racionalidad de la solución, este es, por cierto, un tema para otra discusión. Según mis sentimientos (por supuesto, no hay estadísticas sobre este tema y aparentemente no puede ser) en el mundo de Python, el nivel de especialistas está por encima del promedio, porque a menudo los buenos softwares se escriben en Python, escribe en los comentarios lo que piensas sobre esto.
Concurrencia y competencia
Python ofrece amplias oportunidades para la programación paralela y competitiva, pero no sin características.
Si necesita paralelismo, y esto sucede cuando sus tareas requieren cálculo, entonces debe prestar atención al módulo de multiprocesamiento .
Y si sus tareas tienen muchas expectativas de IO, entonces Python ofrece muchas opciones para elegir, desde hilos y gevent , hasta asyncio .
Todas estas opciones parecen bastante adecuadas para su uso (aunque los hilos requieren muchos más recursos), pero existe la sensación de que Asyncio está exprimiendo lentamente el resto, incluso gracias a todo tipo de cosas como uvloop .
Si alguien no se ha dado cuenta: en Python, los hilos no son sobre paralelismo, no soy lo suficientemente competente como para hablar bien de GIL , pero hay suficiente material sobre este tema, por lo tanto, no hay tal necesidad, lo principal para recordar es que los hilos en Python (más precisamente en CPython) se comportan de manera diferente a otros lenguajes de programación: se ejecutan en un solo núcleo, lo que significa que no son adecuados para casos en los que necesita un paralelismo real, sin embargo, la ejecución de subprocesos se detiene al esperar la entrada / salida, por lo que pueden usarse para competir
Otras rarezas
En python, a = a + b
no siempre es equivalente a a += b
:
a = [1] a = a + (2,3) TypeError: can only concatenate list (not "tuple") to list a += (2,3) a [1, 2, 3]
Lo estoy enviando a SO para obtener detalles, hasta que haya encontrado el tiempo para descubrir por qué es así, en el sentido de por qué lo hicieron, como si esto fuera nuevamente sobre mutabilidad.
Rarezas que no son rarezas
A primera vista, me pareció extraño que el tipo de rango no incluye el borde derecho, pero luego una persona amable me dijo que ignorara dónde necesito aprender y resultó que todo era bastante lógico.
Un gran tema separado es el redondeo (aunque este problema es común para casi todos los lenguajes de programación), además de usar el redondeo como lo desee, excepto que todos estudiaron en el curso escolar de matemáticas, porque los problemas de representar números de punto flotante todavía se superponen, me refiero a artículo detallado
En términos generales, en lugar del algoritmo de media vuelta habitual para las matemáticas escolares, se utiliza el algoritmo de media par , que reduce la probabilidad de distorsión en el análisis estadístico y, por lo tanto, lo recomienda el estándar IEEE 754.
Además, no podía entender por qué -22//10=-3
, y luego otra persona amable señaló que esto se deriva inevitablemente de la definición matemática misma, según la cual el resto no puede ser negativo, lo que conduce a un comportamiento tan inusual para Números negativos
ACHTUNG! Ahora, esto es nuevamente algo extraño y no entiendo nada, vea este hilo .
Depuración de expresiones regulares
Y aquí resultó que en el mundo de Python no hay una herramienta para depurar interactivamente expresiones regulares similares al excelente módulo perla Regexp :: Debugger ( presentación de video ), por supuesto que hay un montón de herramientas en línea, hay algún tipo de soluciones patentadas por Windows, pero para mí no es eso, Puede valer la pena usar una herramienta de barra de perlas, ya que los rex de Python no son muy diferentes de la barra de perlas, escribiré una instrucción para aquellos que no poseen la barra de perlas:
sudo apt install cpanminus cpanm Regexp::Debugger perl -I ~/perl5/lib/perl5/ -E "use Regexp::Debugger; 'ababc' =~ /(a|b) b+ c/x"
Creo que incluso una persona que no esté familiarizada con la perla comprenderá dónde es necesario ingresar la línea, y dónde está la expresión regular, x
es una bandera similar a la pitón re.VERBOSE.
Presione s
pase a través de la expresión regular, una descripción detallada de los comandos disponibles en la documentación .
La documentación
Hay una función de ayuda en python, que le permite obtener ayuda sobre cualquier función cargada (tomada de su cadena de documentación), el nombre de la función se pasa como un parámetro:
$ python3 >>> help(help)
pero esta no siempre es una forma conveniente y a menudo es más conveniente usar la utilidad pydoc:
pydoc3 urllib.parse.urlparse
la utilidad le permite buscar por palabras clave e incluso iniciar un servidor local con documentación html, pero no he probado este último.