Logotipo de Zephyrnet

Código Pitónico: mejores prácticas para hacer que su Python sea más legible

Fecha:

Todo veterano Desarrolladores de Python (Pythonistas) predican sobre cómo escribir código Pythonic. Si eres alguien que ha pasado algún tiempo escribiendo código Pythonic, habrás encontrado las mejores prácticas. Pero, ¿qué es exactamente el código Pythonic y cómo debe recordar los principales puntos débiles / evitar prácticas obvias (malas)?

Afortunadamente, la comunidad de Python está bendecida con un conjunto relativamente simple y completo de pautas de estilo de código y modismos “Pythonic”. Éstas son una de las razones clave de la alta legibilidad del código Pythonic. La legibilidad y la sintaxis simplista están en el corazón de Python.

En esta publicación, vamos a hablar sobre algunas pautas de estilo muy importantes y modismos pitónicos, y sobre cómo lidiar con el código heredado.

Una declaración de código por línea

Si escribe declaraciones inconexas en una sola línea, está violando la esencia de Python. La única excepción son las listas por comprensión y algunas otras declaraciones compuestas. Estos están permitidos y aceptados por su brevedad y expresividad.

Mala práctica

print 'foo'; print 'bar' if x == 1: print 'foo' if <complex comparison> and <other complex comparison>: # do something

Buenas prácticas

print 'foo'
print 'bar' if x == 1: print 'foo' cond1 = <complex comparison>
cond2 = <other complex comparison>
if cond1 and cond2: # do something

Código explícito

La forma más simple (y más fácil de entender) de escribir código es siempre la mejor.

Mala práctica

def make_complex(*args): x, y = args return dict(**locals())

El código anterior devuelve:
{'args': (1, 2), 'x': 1, 'y': 2}
Esto es redundante ya que solo necesitábamos x e y mientras que también devuelve 'args'.
Además, si tuviéramos:

def make_complex(*args): x,y = args z = x+y return dict(**locals())

La función anterior devolverá 'z': 3 también en el dict local. La mejor práctica es especificar lo que se necesita y seguir el enfoque más directo. Recuerde mantener las cosas simples y explícitas.
Otro escollo de esta mala práctica es que si pasa más de 2 parámetros al llamar a la función: make_complex(1,2,3)arrojaría un valueError Me gusta esto:
Error de código pitónico

Buenas prácticas

def make_complex(x, y): return {'x': x, 'y': y}

Pasar args a funciones

Hay cuatro formas diferentes de pasar argumentos a una función:

  1. Argumentos posicionales: Éstos son los argumentos más sencillos. Los argumentos posicionales son completamente parte del significado de la función y su orden será el orden en el que se definan. Por ejemplo, en cal_area(length, breadth) or send_msg(message, recipient), el desarrollador no tiene que preocuparse por recordar que estas dos funciones requieren 2 argumentos o su orden.

Note: En los dos ejemplos anteriores, también puede llamar a funciones con diferentes órdenes usando palabras clave como: cal_area(breadth = 40.0, length=90) or send_msg(recipient='Mak', message='Hello there!').

  1. Argumentos de palabras clave: También conocido como kwargs, a menudo se usan como parámetros opcionales pasados ​​a la función. Cuando una función tiene más de dos o tres parámetros posicionales, su firma se vuelve difícil de recordar.
    Los Kwargs son útiles con el valor predeterminado. Por ejemplo, una mejor manera de escribir un send_msg la función sería: send_message(message, recipient, cc=None, bcc=None). Aquí, cc y bcc son opcionales y se devolverían como "Ninguno" si no se pasa ningún valor.

  2. Lista de argumentos arbitrarios: Si la lógica de negocios de la función requiere un número extensible de argumentos posicionales, se puede definir con *args construcciones Dentro de la función, args será una tupla de todos los argumentos posicionales restantes. Por ejemplo, send_msg(message, *args) se puede llamar con cada destinatario como argumento:
    send_msg('Hello there!', 'God', 'Mom', 'Cthulhu'), y el alcance de la función tendrá args igual a ('God', 'Mom', 'Cthulhu').

  3. El diccionario arbitrario de argumentos de palabras clave: Si su función requiere una serie indeterminada de argumentos con nombre, es posible utilizar el **kwargs construir. En el cuerpo de la función, kwargs será un diccionario de todos los argumentos nombrados pasados ​​que no hayan sido capturados por otros argumentos de palabras clave en la firma de la función.
    Usar *args, Python pasa argumentos de longitud variable sin palabras clave a la función, pero ¿qué sucede si queremos pasar el argumento de palabras clave? Utilizando **kwargs, podemos pasar la longitud variable de los argumentos de palabras clave a la función.
    Por ejemplo, para la siguiente función:

def introduction(**data): print("nData type of argument:",type(data)) for key, value in data.items(): print("{} is {}".format(key,value))
introduction(Firstname="Sita", Lastname="Sharma", Age=22, Phone=1234567890)
introduction(Firstname="John", Lastname="Wood", Email="johnwood@nomail.com", Country="Wakanda", Age=25, Phone=9876543210)

La salida será:

Data type of argument: <class 'dict'>
Firstname is Sita
Lastname is Sharma
Age is 22
Phone is 1234567890 Data type of argument: <class 'dict'>
Firstname is John
Lastname is Wood
Email is johnwood@nomail.com
Country is Wakanda
Age is 25
Phone is 9876543210

Note: La misma precaución que para las listas de argumentos arbitrarios es necesaria. Las razones son similares: estas poderosas técnicas solo deben usarse cuando existe una necesidad comprobada, y no deben usarse si la construcción más simple y clara es suficiente para expresar la intención de la función.

Si la guía de estilo de codificación se sigue con prudencia, sus funciones de Python serán:

  • fácil de leer (el nombre y los argumentos no necesitan explicaciones)
  • fácil de cambiar (agregar un nuevo argumento de palabra clave no rompe otras partes del código)

Declaraciones de devolución

A medida que una función crece en complejidad, se vuelve susceptible de tener múltiples declaraciones de retorno dentro del cuerpo de la función. Sin embargo, para mantener una intención clara y un nivel de legibilidad sostenido, es preferible evitar devolver valores significativos en múltiples puntos de salida en el cuerpo de la función.

Por ejemplo, eche un vistazo al siguiente ejemplo (explicado por los comentarios en línea) sobre cómo evitar agregar múltiples puntos de salida y generar excepciones en su lugar:

Mala práctica

def complex_function(a, b, c): if not a: return None if not b: return None # Some complex code trying to compute x from a, b and c if x: return x if not x: # Some Plan-B computation of x return x

Buenas prácticas

def complex_function(a, b, c): if not a or not b or not c: raise ValueError("The args can't be None") # Raising an exception is better # Some complex code trying to compute x from a, b and c # Resist temptation to return x if succeeded if not x: # Some Plan-B computation of x return x # One single exit point for the returned value x will help when maintaining the code.

Escribiendo Python idiomático

Un modismo es una frase que no tiene sentido literal, pero tiene sentido una vez que se familiariza con la cultura en la que surgió. Los modismos de programación no son diferentes. Son las pequeñas cosas que haces a diario en un lenguaje de programación o paradigma en particular que solo tienen sentido para una persona familiarizada con su cultura.

Los principiantes de Python pueden no ser conscientes de escribir Python idiomático, por lo que hemos enumerado algunos modismos comunes de Python:

Desembalaje

Si desea asignar nombres o referencias a los elementos de una lista mientras la desempaqueta, intente usar enumerate():

for index, item in enumerate(some_list): # do something with index and item

Puede usar variables de intercambio:

a, b = b, a

El desempaquetado anidado también funciona:

a, (b, c) = 1, (2, 3)

En Python 3, PEPE 3132 ha introducido un nuevo método de desembalaje extendido:

a, *rest = [1, 2, 3]
# a = 1, rest = [2, 3]
a, *middle, c = [1, 2, 3, 4]
# a = 1, middle = [2, 3], c = 4

Crear variables descartables

Si necesita asignar algo (por ejemplo, al desempacar), pero no necesita esa variable, use __:

filename = 'foobar.txt'
basename, __, ext = filename.rpartition('.')

Note:
Muchas guías de estilo Python recomiendan el uso de un solo guión bajo _ para variables descartables en lugar del doble guión bajo __ recomendado aquí. El problema es que _ se usa comúnmente como un alias para el gettext() función, y también se utiliza en la solicitud interactiva para mantener el valor de la última operación.

En cambio, usar un guión bajo doble es tan claro y casi tan conveniente. El beneficio de esta práctica es eliminar el riesgo de interferir accidentalmente con cualquiera de estos otros casos de uso.

Crea una lista de longitud N de lo mismo

Usa la lista de Python * operador para crear listas simples y listas anidadas también:

nones = [None]*4
foures_of_fours = [[4]]*5

Salida:
[Ninguno, Ninguno, Ninguno, Ninguno]
[[4], [4], [4], [4], [4]]

Buscar un artículo en una colección

A veces necesitamos buscar en una colección. Veamos dos opciones: listas y conjuntos. Tome el siguiente código, por ejemplo:

def in_test(iterable): for i in range(1000): if i in iterable: pass
from timeit import timeit
timeit( "in_test(iterable)", setup="from __main__ import in_test; iterable = set(range(1000))", number=10000) Output: 0.5591847896575928 timeit( "in_test(iterable)", setup="from __main__ import in_test; iterable = list(range(1000))", number=10000) Output: 50.18339991569519 timeit( "in_test(iterable)", setup="from __main__ import in_test; iterable = tuple(range(1000))", number=10000) Output: 51.597304821014404

Ambas funciones se ven idénticas, porque el lookup_set() está utilizando el hecho de que los conjuntos en Python son tablas hash. Sin embargo, los resultados de búsqueda de los dos son diferentes, es decir, los conjuntos usan O (log n), mientras que la lista tiene una complejidad temporal de O (n).

Para determinar si un elemento está en una lista, Python tendrá que revisar cada elemento hasta que encuentre un elemento coincidente. Esto lleva mucho tiempo, especialmente para listas largas. Por otro lado, en un conjunto, el hash del elemento le dirá a Python dónde buscar en el conjunto un elemento que coincida. Como resultado, la búsqueda se puede hacer rápidamente, incluso si el conjunto es grande.

Debido a estas diferencias en el rendimiento, a menudo es una buena idea usar conjuntos o diccionarios en lugar de listas en los casos en que:

  • la colección contendrá una gran cantidad de artículos
  • estarás buscando repetidamente artículos en la colección
  • no tienes elementos duplicados

Acceder a un elemento del diccionario

No use el método dict.has_key (). En cambio, usa x in d sintaxis, o pasar un argumento predeterminado a dict.get (), ya que es más Pythonic y es eliminado en Python 3.x.

Note: Python2 está a punto de retirarse en 2020. Se recomienda utilizar Python 3.x para cualquier tipo de desarrollo, ya que la mayoría de los paquetes de Python tienen / dejarán de lanzar actualizaciones para Python 2.x. Lee mas esta página.

Mala práctica

d = {'foo': 'bar'}
if d.has_key('foo'): print d['foo'] # prints 'bar'
else: print 'default_value'

Buenas prácticas

d = {'foo': 'bar'} print d.get('foo', 'default_value') # prints 'bar'
print d.get('thingy', 'default_value') # prints 'default_value' # alternative
if 'hello' in d: print d['foo']

Filtrando una Lista

Nunca elimine elementos de una lista mientras la está iterando. ¿Por qué? Si se accede a su lista a través de varias referencias, el hecho de que solo esté volviendo a colocar una de las referencias (y NO alterando el objeto de la lista en sí) puede provocar errores sutiles y desastrosos. Leer más sobre esto esta página.

Mala práctica

# Filter elements greater than 4
num_list = [1, 2, 3]
for i in num_list: if i > 2: num_list.remove(i)

No haga múltiples pases por la lista.

while i in num_list: num_list.remove(i)

Buenas prácticas

Use una lista de comprensión o expresión generadora:

# comprehensions create a new list object
filtered_values = [value for value in sequence if value != x] # generators don't create another list
filtered_values = (value for value in sequence if value != x)

Actualización de valores en una lista

Recuerde que la asignación nunca crea un nuevo objeto. Si dos o más variables se refieren a la misma lista, cambiar una de ellas las cambia a todas.

Mala práctica

# Add three to all list members.
a = [3, 4, 5]
b = a # a and b refer to the same list object for i in range(len(a)): a[i] += 3 # b[i] also changes

Buenas prácticas

a = [3, 4, 5]
b = a # assign the variable "a" to a new list without changing "b"
a = [i + 3 for i in a]
b = a[:] # even better way to copy a list

Ingrese al with open sintaxis para leer de archivos. Esto cerrará automáticamente los archivos por ti.

Mala práctica

f = open('file.txt')
a = f.read()
print a
f.close()

Buenas prácticas

with open('file.txt') as f: for line in f: print line

La with El método es mejor porque garantiza que siempre cierre el archivo, incluso si se genera una excepción dentro del bloque.

Manejo del código heredado

Hemos cubierto los conceptos básicos de escribir un buen código en Python. Ahora vale la pena mirar el arte de manejar grandes proyectos en Python. ¿Cómo puede emprender nuevos proyectos de código abierto o de código cerrado? ¿Cuáles son los pasos para refactorizar el código heredado? ¿Cuáles son las mejores prácticas para ponerse al día en un nuevo proyecto?

A menudo, cuando te unes a una nueva organización, te dan una base de código para comprender y refactorizar, o necesitas tomar código heredado para refactorizar. A veces, gracias a esta situación, se encontrará en una profunda angustia y no podrá averiguar el punto de partida.

En este punto, es importante definir "código / proyecto heredado" para que todos estemos en la misma página. Esto es lo que encontrará:

  • un proyecto "más antiguo" que ha existido desde siempre
  • una base de código sin ningún tipo de prueba
  • el proyecto en el que nadie quiere trabajar
  • "Todos los que trabajaron en esto dejaron la compañía hace años ..."

Todo lo anterior es algo correcto, pero a veces los proyectos se realizan a toda prisa y se ponen en producción antes de que todos se den cuenta de que hay mucho margen de mejora. Entonces, ¿cómo debemos abordar un proyecto heredado?

A continuación se muestra una lista rápida de los pasos que debe seguir para que su viaje de refactorización sea más simple y fluido:

  1. En primer lugar, asegúrese de que el proyecto esté en un sistema de control de versiones.
  2. Eliminar el código comentado. (Una vez que el proyecto esté en producción, asegúrese siempre de eliminar el código comentado).
  3. Ejecutar pruebas / agregar pruebas. Asegúrese de tener al menos un 80% de cobertura de prueba. Utilizar pytest o paquetes similares de Python para rastrear la cobertura de prueba.
  4. Uso Pylint/Buitre. Siempre considere ejecutar algún tipo de linter sobre el código para ver qué tan "saludable" es. Intenta buscar:
    • Variables no utilizadas
    • Cualquier cosa que se observe como un error potencial
  5. Utilice formateadores como Flake8 o PEP8. Estas pautas se pueden usar para reformatear el código de Python para hacerlo más PEP8 queja.
  6. Escriba Python más idiomático (como se describió anteriormente).

Conclusión

Con la comunidad de Python en expansión y los Pythonistas en ciernes, tenemos Python en casi todos los campos de desarrollo, como la ciencia de datos, el desarrollo web, el desarrollo móvil y la inteligencia artificial, etc. Como tal, es cada vez más importante asegurarse de que siempre enviemos código de nivel empresarial siguiendo las pautas adecuadas.

Gracias a estas herramientas básicas, y la belleza del lenguaje Python en sí, producir código y productos increíbles no tiene por qué ser una propuesta aterradora. Ahora que ha seguido estas pautas, continúe y pruébelas en un proyecto de código abierto Python!

Para obtener más prácticas recomendadas de Python, consulte estas publicaciones:

Fuente: https://www.codementor.io/blog/pythonic-code-6yxqdoktzt

punto_img

Información más reciente

punto_img