Indice / Repaso: archivos y errores / 1.A
Apéndice: PEP 8, docstrings y comentarios
Salteable: nada de lo que viene después depende de esta página. Se lee en diez minutos y es lo que hace que tu código de dentro de tres meses siga siendo tuyo.
Para qué sirve
PEP 8 es la guía de estilo de Python y no la escribió nadie para molestar: la
escribieron porque el código se lee muchas más veces de las que se escribe. En esta
materia la consecuencia es concreta - los scripts de los ejercicios los va a
leer alguien más (un compañero, un docente, vos en el parcial), y una variable llamada
l cuesta minutos cada vez.
ELI5Explicado facil
No hay una forma «correcta» de escribir código, igual que no hay una forma correcta de ordenar una cocina. Lo que hay es una convención: si todos guardan los cubiertos en el mismo cajón, nadie pierde tiempo buscándolos.
PEP 8 es ese acuerdo, y su valor es que ya está tomado.
Nombres
import csv
# ---------------------------------------------------------------
# ANTES: corre bien, y cuesta leerlo.
# ---------------------------------------------------------------
def f(a):
l=[]
for x in a:
if x['cantidad'] not in ('','-','N/A','NaN','null') and x['precio_unitario'] not in ('','-','N/A','NaN','null') and int(x['cantidad'])>0:
l.append({'c':x['codigo'],'t':int(x['cantidad'])*float(x['precio_unitario'])})
return l
# ---------------------------------------------------------------
# DESPUES: el mismo resultado, con los nombres de PEP 8.
# ---------------------------------------------------------------
NULOS = {"", "-", "N/A", "NaN", "null"}
def valorizar_stock(filas):
"""Valor inmovilizado por producto: cantidad por precio unitario.
Devuelve una lista de dicts con 'codigo' y 'valor'. Saltea las filas
con cantidad o precio ausentes, y las de cantidad no positiva: la
politica la fija el enunciado, no esta funcion.
"""
valorizadas = []
for fila in filas:
crudo = fila["cantidad"].strip()
if crudo in NULOS:
continue
precio = fila["precio_unitario"].strip()
if precio in NULOS:
continue
cantidad = int(crudo)
if cantidad <= 0:
continue
valorizadas.append({
"codigo": fila["codigo"],
"valor": cantidad * float(precio),
})
return valorizadas
with open("inventario_productos.csv", encoding="utf-8", newline="") as f_entrada:
filas = list(csv.DictReader(f_entrada))
print("las dos versiones dan lo mismo:")
antes = f(filas)
despues = valorizar_stock(filas)
print(" antes :", len(antes), "productos")
print(" despues:", len(despues), "productos")
print(" iguales:", [round(x["t"], 2) for x in antes]
== [round(x["valor"], 2) for x in despues])
print()
print("las tres primeras:")
for item in despues[:3]:
print(f" {item['codigo']} {item['valor']:>12,.2f}")
print()
print("Que cambio, y por que importa:")
print(" f -> valorizar_stock : el nombre dice que hace")
print(" a -> filas : el nombre dice que recibe")
print(" x -> fila : idem, en el bucle")
print(" l / c / t -> codigo / valor : no hay que adivinar")
print(" la linea de 100 caracteres se parte y entra en la pantalla")
print(" la lista de nulos deja de estar repetida en un if")
print(" el docstring dice la politica, que el codigo solo ejecuta")
las dos versiones dan lo mismo:
antes : 31 productos
despues: 31 productos
iguales: True
las tres primeras:
P001 507.36
P002 64,105.96
P003 94,200.84
Que cambio, y por que importa:
f -> valorizar_stock : el nombre dice que hace
a -> filas : el nombre dice que recibe
x -> fila : idem, en el bucle
l / c / t -> codigo / valor : no hay que adivinar
la linea de 100 caracteres se parte y entra en la pantalla
la lista de nulos deja de estar repetida en un if
el docstring dice la politica, que el codigo solo ejecuta
| Qué | Cómo | Ejemplo |
|---|---|---|
| Variables y funciones | minusculas_con_guion_bajo | valorizar_stock, filas |
| Constantes | MAYUSCULAS, arriba del archivo | NULOS, COLUMNAS |
| Clases | CamelCase | FilaInvalida |
| Módulos | corto y en minúscula | referencia.py |
| Descartable | _ | for _ in range(3) |
Un nombre bueno hace innecesario el comentario. umbral = 0.75 obliga a
preguntar «¿de qué?»; por_ciento_asistencia = 0.75 no. Y el largo del
nombre se paga una vez al escribirlo y se cobra cada vez que se lee.
Las únicas abreviaturas de una letra que aguantan son las del dominio:
i, j para índices, x, y para
coordenadas. l, I y O no se usan nunca: en casi
toda tipografía se confunden con 1 y 0.
La forma del archivo
| Regla | Detalle |
|---|---|
| Sangría | 4 espacios, nunca tabulaciones. VS Code ya lo hace solo |
| Largo de línea | 79 caracteres (este curso usa 79 y afloja a 88 cuando gana claridad) |
| Líneas en blanco | 2 entre funciones de nivel superior, 1 entre métodos |
| Imports | Uno por línea, arriba de todo, en tres bloques: estándar · terceros · propios |
| Espacios | a = 1 y f(x, y); nunca f( x , y ) ni a=1 suelto |
| Argumentos por defecto | f(x, y=2) sin espacios alrededor del = |
Comparar con None | if x is None, nunca if x == None |
No hace falta memorizarlas. Ruff o flake8 las revisan solas
(pip install ruff y ruff check archivo.py), y VS Code puede
formatear al guardar con Black - que no pregunta y decide por vos, que es
exactamente su gracia.
Docstrings
NULOS = {"", "-", "N/A", "NaN", "null"}
def promedio(notas, politica="descartar"):
"""Promedio de las notas, segun que hacer con las ausentes.
Args:
notas: lista de cadenas tal como vienen del CSV.
politica: "descartar" saltea las ausentes; "cero" las cuenta
como 0. Cambia el denominador, no solo el numerador.
Returns:
El promedio redondeado a 2 decimales, o None si no queda
ninguna nota usable.
Raises:
ValueError: si la politica no es una de las dos previstas.
"""
if politica not in ("descartar", "cero"):
raise ValueError(f"politica desconocida: {politica!r}")
usables = []
for nota in notas:
crudo = nota.strip()
if crudo in NULOS:
if politica == "cero":
usables.append(0.0)
continue
usables.append(float(crudo))
if not usables:
return None
return round(sum(usables) / len(usables), 2)
CASO = ["9.7", "4.7", "-", "8.6", ""]
print("notas:", CASO)
print(" descartar ->", promedio(CASO), "(sobre 3 notas)")
print(" cero ->", promedio(CASO, "cero"), "(sobre 5)")
print()
print("Y esto es lo que ve quien la use, sin abrir el archivo:")
print("-" * 58)
print(promedio.__doc__)
print("-" * 58)
print()
print("Las reglas de PEP 257, que son cuatro:")
print(" 1. Va entre triple comilla, aunque sea de una linea.")
print(" 2. La primera linea resume todo, en una sola linea y con punto")
print(" final. PEP 257 la pide imperativa en ingles ('Return the...');")
print(" en espanol este curso usa una frase descriptiva, y lo que no")
print(" cambia es que sea UNA linea y que cierre con punto.")
print(" 3. Si hay mas, va una linea en blanco despues de la primera:")
print(" help() y los editores muestran solo esa primera linea.")
print(" 4. Dice QUE hace y QUE devuelve, no COMO. El como esta abajo.")
print()
print("La prueba de si un docstring sirve: ¿alcanza para usar la funcion")
print("sin leerle el cuerpo? Aca si, incluido el detalle que importa:")
print("que la politica cambia el DENOMINADOR, no solo el numerador.")
notas: ['9.7', '4.7', '-', '8.6', '']
descartar -> 7.67 (sobre 3 notas)
cero -> 4.6 (sobre 5)
Y esto es lo que ve quien la use, sin abrir el archivo:
----------------------------------------------------------
Promedio de las notas, segun que hacer con las ausentes.
Args:
notas: lista de cadenas tal como vienen del CSV.
politica: "descartar" saltea las ausentes; "cero" las cuenta
como 0. Cambia el denominador, no solo el numerador.
Returns:
El promedio redondeado a 2 decimales, o None si no queda
ninguna nota usable.
Raises:
ValueError: si la politica no es una de las dos previstas.
----------------------------------------------------------
Las reglas de PEP 257, que son cuatro:
1. Va entre triple comilla, aunque sea de una linea.
2. La primera linea resume todo, en una sola linea y con punto
final. PEP 257 la pide imperativa en ingles ('Return the...');
en espanol este curso usa una frase descriptiva, y lo que no
cambia es que sea UNA linea y que cierre con punto.
3. Si hay mas, va una linea en blanco despues de la primera:
help() y los editores muestran solo esa primera linea.
4. Dice QUE hace y QUE devuelve, no COMO. El como esta abajo.
La prueba de si un docstring sirve: ¿alcanza para usar la funcion
sin leerle el cuerpo? Aca si, incluido el detalle que importa:
que la politica cambia el DENOMINADOR, no solo el numerador.
Un docstring no es un comentario largo: es el texto que devuelve
help(funcion) y el que muestra el editor cuando pasás el mouse por encima.
Vive en funcion.__doc__ y es parte de la interfaz.
¿Alcanza para usar la función sin leerle el cuerpo? Si la respuesta es no, falta algo: casi siempre qué devuelve en el caso raro, o qué excepción levanta.
Y el detalle que separa un docstring útil de uno decorativo: dice las
consecuencias, no la mecánica. «La política cero cambia el
denominador, no solo el numerador» es información que el nombre del parámetro no
transmite.
Comentarios
import csv
with open("estudiantes_notas.csv", encoding="utf-8", newline="") as f:
filas = list(csv.DictReader(f))
# ---- COMENTARIOS QUE SOBRAN --------------------------------------
# Recorremos las filas
for fila in filas[:1]:
# Obtenemos el legajo
legajo = fila["legajo"]
# Incrementamos el contador en 1
contador = 1
print("Los tres de arriba repiten lo que el codigo ya dice.")
print("Cuando el codigo cambie, el comentario va a quedar mintiendo:")
print("nadie se acuerda de actualizar un comentario que no aporta.")
print()
# ---- COMENTARIOS QUE SALVAN --------------------------------------
NULOS = {"", "-", "N/A", "NaN", "null"}
usables = []
for fila in filas:
crudo = fila["nota_3"].strip()
# 'NaN' entra a esta lista a proposito: float('NaN') NO falla,
# devuelve nan, y un solo nan convierte el promedio entero en nan.
if crudo in NULOS:
continue
valor = float(crudo)
# El rango lo fija el reglamento de la materia, no los datos: hay
# una nota de -1 sembrada y es un valor imposible, no un atipico.
if not 0 <= valor <= 10:
continue
usables.append(valor)
print("notas usables:", len(usables), "de", len(filas))
print("promedio:", round(sum(usables) / len(usables), 2))
print()
print("Esos dos comentarios dicen POR QUE, no QUE. Ninguno de los dos")
print("se deduce leyendo el codigo, y los dos evitan que el que venga")
print("despues 'simplifique' la condicion y rompa el calculo.")
print()
print("La regla: si el comentario se puede borrar sin perder nada,")
print("sobraba. Si explica una decision, una unidad, un caso raro o")
print("un porque, se queda.")
print()
print("Y el mejor comentario suele ser un nombre:")
por_ciento_asistencia = 0.75
print(" umbral = 0.75 -> ¿de que?")
print(" por_ciento_asistencia =", por_ciento_asistencia,
"-> no hace falta comentario")
Los tres de arriba repiten lo que el codigo ya dice.
Cuando el codigo cambie, el comentario va a quedar mintiendo:
nadie se acuerda de actualizar un comentario que no aporta.
notas usables: 34 de 40
promedio: 6.23
Esos dos comentarios dicen POR QUE, no QUE. Ninguno de los dos
se deduce leyendo el codigo, y los dos evitan que el que venga
despues 'simplifique' la condicion y rompa el calculo.
La regla: si el comentario se puede borrar sin perder nada,
sobraba. Si explica una decision, una unidad, un caso raro o
un porque, se queda.
Y el mejor comentario suele ser un nombre:
umbral = 0.75 -> ¿de que?
por_ciento_asistencia = 0.75 -> no hace falta comentario
| Sobra | Se queda |
|---|---|
# Recorremos las filas | Por qué 'NaN' está en la lista de nulos: float('NaN') no falla |
# Incrementamos el contador | De dónde sale el rango 0-10: lo fija el reglamento, no los datos |
# Obtenemos el legajo | La unidad de una constante: # en minutos |
| Código viejo comentado | Un # TODO: con qué falta y por qué se dejó así |
La regla: si el comentario se puede borrar sin perder nada, sobraba. Y hay un motivo práctico además del estético: un comentario que repite el código queda mintiendo apenas el código cambie, porque nadie se acuerda de actualizar un comentario que no aporta. Un comentario falso es peor que ninguno.
Las convenciones de este curso
Además de PEP 8, el material de la materia sigue tres reglas propias que conviene conocer porque se ven en todos los ejemplos:
- Identificadores y comentarios sin acentos (
valorizar_stock, novalorización), pero el contenido de los datos sí los lleva:José Juárezes parte del ejercicio de codificación. - Todo en español: nombres, docstrings y comentarios. Mezclar idiomas dentro
de un mismo identificador (
get_estudiantes) es lo único peor que elegir cualquiera de los dos. - Las constantes de política arriba y en mayúscula:
NULOS,MOTIVOS,COLUMNAS. Si un valor decide algo, tiene que estar donde se lo vea, no enterrado en unif.
Dónde se rompe
Causa: se mezclaron tabulaciones y espacios, casi siempre al pegar código de una página web.
Arreglo: en VS Code, «Convert Indentation to Spaces» desde la paleta de comandos. Y 4 espacios, siempre.
if x == None: funciona pero no es lo que se usa
Causa: None es un objeto único, así que la pregunta correcta
es de identidad.
Arreglo: if x is None. Con ==, un objeto que
define __eq__ puede responder cualquier cosa.
=[]
Causa: el default se crea una sola vez, al definir la función, y se comparte entre todas las llamadas. La segunda llamada ve lo que agregó la primera.
Arreglo: def f(items=None): y adentro
if items is None: items = [].
Causa: no es un error de estilo, es de estructura, y ninguna herramienta lo detecta.
Arreglo: si no podés escribirle un docstring de una línea, hace más de una cosa.
Recetario
| Comando | Qué hace |
|---|---|
ruff check archivo.py | Señala lo que no cumple PEP 8, rapidísimo |
ruff check --fix archivo.py | Arregla solo lo que puede arreglar sin riesgo |
black archivo.py | Reformatea sin preguntar. No tiene opciones, y esa es la idea |
help(funcion) | Muestra el docstring |
funcion.__doc__ | El docstring como cadena |
| VS Code: Shift+Alt+F | Formatear el archivo |