strategy software patterns pattern patrones faif diseño python string design-patterns anti-patterns

software - Python: está usando "..%(var) s.."% locals() ¿una buena práctica?



python software patterns (7)

Ahora hay una forma oficial de hacerlo, a partir de Python 3.6.0: literales de cadena formateados .

Funciona así:

f''normal string text {local_variable_name}''

Por ejemplo, en lugar de estos:

"hello %(name)s you are %(age)s years old" % locals() "hello {name} you are {age} years old".format(**locals()) "hello {} you are {} years old".format(name, age)

solo haz esto:

f"hello {name} you are {age} years old"

Aquí está el ejemplo oficial:

>>> name = "Fred" >>> f"He said his name is {name}." ''He said his name is Fred.'' >>> width = 10 >>> precision = 4 >>> value = decimal.Decimal("12.34567") >>> f"result: {value:{width}.{precision}}" # nested fields ''result: 12.35''

Referencia:

Descubrí este patrón (o antipatrón) y estoy muy contento con él.

Siento que es muy ágil:

def example(): age = ... name = ... print "hello %(name)s you are %(age)s years old" % locals()

A veces uso su primo:

def example2(obj): print "The file at %(path)s has %(length)s bytes" % obj.__dict__

No necesito crear una tupla artificial y contar los parámetros y mantener las posiciones coincidentes% s dentro de la tupla.

¿Te gusta? ¿Lo usarías / lo usarías? Sí / No, por favor explique.


Con respecto al "primo", en lugar de obj.__dict__ , se ve mucho mejor con el nuevo formato de cadena:

def example2(obj): print "The file at {o.path} has {o.length} bytes".format(o=obj)

Utilizo esto mucho para métodos repr , p. Ej.

def __repr__(self): return "{s.time}/{s.place}/{s.warning}".format(s=self)


Creo que es un gran patrón porque está aprovechando la funcionalidad incorporada para reducir el código que necesita para escribir. Personalmente, lo encuentro bastante Ptónico.

Nunca escribo código que no necesito escribir; menos código es mejor que más código y esta práctica de usar locals() por ejemplo, me permite escribir menos código y también es muy fácil de leer y comprender.


El "%(name)s" % <dictionary> o incluso mejor, el "{name}".format(<parameters>) tienen el mérito de

  • siendo más legible que "% 0s"
  • siendo independiente del orden de los argumentos
  • no es convincente utilizar todos los argumentos en la cadena

Tiendo a favorecer a str.format (), ya que debería ser la forma de hacerlo en Python 3 (según PEP 3101 ), y ya está disponible desde 2.6. Sin embargo, con los locals() , tendrías que hacer esto:

print("hello {name} you are {age} years old".format(**locals()))


El uso de vars([object]) incorporados vars([object]) ( documentation ) puede hacer que el segundo te parezca mejor:

def example2(obj): print "The file at %(path)s has %(length)s bytes" % vars(obj)

El efecto es, por supuesto, lo mismo.


Está bien para pequeñas aplicaciones y supuestamente scripts "únicos", especialmente con la mejora de vars mencionada por @ kaizer.se y la versión .format mencionada por @RedGlyph.

Sin embargo, para aplicaciones grandes con una larga vida útil de mantenimiento y muchos mantenedores, esta práctica puede provocar dolores de cabeza por mantenimiento, y creo que de ahí viene la respuesta de @SLott. Permítanme explicar algunos de los problemas involucrados, ya que pueden no ser obvios para cualquiera que no tenga las cicatrices de desarrollar y mantener aplicaciones grandes (o componentes reutilizables para tales bestias).

En una aplicación "seria", no tendría su cadena de formato codificada, o, si la tuviera, sería de alguna forma, como _(''Hello {name}.'') , Donde _ viene de gettext o marcos i18n / L10n similares. El punto es que dicha aplicación (o módulos reutilizables que pueden usarse en tales aplicaciones) debe admitir la internacionalización (AKA i18n) y la localización (AKA L10n): quiere que su aplicación pueda emitir "Hello Paul" en ciertas aplicaciones. países y culturas, "Hola Pablo" en algunos otros, "Ciao Paul" en otros todavía, y así sucesivamente. Por lo tanto, la cadena de formato se sustituye de manera más o menos automática por otra en tiempo de ejecución, según la configuración de localización actual; en lugar de estar codificado, vive en una especie de base de datos. Para todos los efectos, imagine que la cadena de formato siempre es una variable, no una cadena literal.

Entonces, lo que tienes es esencialmente

formatstring.format(**locals())

y no se puede verificar trivialmente qué nombres locales va a usar el formato. Tendría que abrir y examinar la base de datos L10N, identificar las cadenas de formato que se usarán aquí en diferentes configuraciones, verificarlas todas.

Por lo tanto, en la práctica no se sabe qué nombres locales se van a utilizar, lo que arruina de forma horrible el mantenimiento de la función. No se atreve a cambiar el nombre ni a eliminar ninguna variable local, ya que podría romper horriblemente la experiencia del usuario para los usuarios que tienen una combinación oscura de idioma, lugar y preferencias.

Si tiene excelentes pruebas de integración / regresión, la rotura quedará atrapada antes de la versión beta, pero la garantía de calidad le gritará y el lanzamiento se retrasará ... y, seamos sinceros, mientras apunte a una cobertura del 100% con pruebas unitarias es razonable, realmente no es con las pruebas de integración , una vez que considera la explosión combinatoria de configuraciones [[para L10N y por muchas razones más]] y las versiones compatibles de todas las dependencias. Por lo tanto, simplemente no se arriesga a perder las roturas porque "quedarán atrapados en el control de calidad" (si lo hace, puede que no dure mucho en un entorno que desarrolle aplicaciones grandes o componentes reutilizables ;-).

Por lo tanto, en la práctica, nunca eliminará la variable local "nombre" aunque la gente de User Experience haya cambiado ese saludo a un "¡Bienvenido, Dread Overlord!" Más apropiado. (y, de manera adecuada, versiones de L10n). Todo porque fuiste por los locals() ...

Así que estás acumulando cruft debido a la forma en que has restringido tu capacidad de mantener y editar tu código, y tal vez esa variable local de "nombre" solo existe porque ha sido extraída de un DB o similar, por lo que se mantiene (o algún otro local) no solo es simple, también está reduciendo tu rendimiento. ¿Vale la pena la conveniencia de la superficie de los locals() ? -)

Pero espera, ¡hay cosas peores! Entre los muchos servicios útiles que un programa tipo lint (como, por ejemplo, pylint ) puede hacer por usted, es advertirle sobre las variables locales no utilizadas (ojalá pueda hacerlo también para los globales no utilizados, pero para los componentes reutilizables, eso es solo un poco demasiado duro ;-). De esta forma if ...: nmae = ... mayoría de los errores ortográficos ocasionales, como if ...: nmae = ... muy rápido y de forma barata, en lugar de ver un descanso de prueba de unidad y hacer un trabajo de detective para descubrir por qué se rompió (tienes obsesivo , pruebas unitarias generalizadas que atraparían esto eventualmente, ¿verdad? -) - pelusa le informará acerca de una variable local nmae no nmae y la arreglará de inmediato.

Pero si tienes en tu código un blah.format(**locals()) , o lo que es lo mismo, un blah % locals() ... ¡eres SOL, amigo! -) ¿Cómo sabrá la pelusa si nmae está en de hecho, una variable no utilizada, o en realidad se usa por cualquier función externa o método que esté pasando a locals() ? No puede - o va a advertir de todos modos (causando un efecto de "lobo grito" que eventualmente lo lleva a ignorar o deshabilitar tales advertencias), o nunca va a advertir (con el mismo efecto final: no hay advertencias ;-) .

Compare esto con la alternativa "explícita es mejor que implícita" ...

blah.format(name=name)

Ahí no se aplica ninguna de las preocupaciones sobre mantenimiento, rendimiento y am-I-obstaculización-pelusa; ¡felicidad! De inmediato se aclara a todos los interesados ​​(pelusa incluida ;-) exactamente qué variables locales se utilizan, y exactamente para qué fines.

Podría seguir, pero creo que esta publicación ya es bastante larga ;-).

Entonces, resumiendo: " γνῶθι σεαυτόν !" Hmm, quiero decir, "¡conócete a ti mismo!". Y por "ti mismo" realmente quiero decir "el propósito y alcance de tu código". Si se trata de una pieza de 1 fuera de lugar, que nunca se va a sacar ni a L10n, no necesitará mantenimiento en el futuro, nunca se reutilizará en un contexto más amplio, etc., y luego utilizará los locals() por su pequeña pero limpia conveniencia; si sabe lo contrario, o incluso si no está del todo seguro, eré por precaución y haga las cosas más explícitas: sufra las pequeñas molestias de explicar exactamente a qué va, y disfrute de todas las ventajas resultantes.

Por cierto, este es solo uno de los ejemplos donde Python se esfuerza por apoyar tanto la programación "pequeña, única, exploratoria, quizás interactiva" (permitiendo y apoyando conveniencias riesgosas que se extienden mucho más allá de los locals() - piense en import * , eval , exec , y varias otras formas en que puede acumular espacios de nombres e impactos de mantenimiento de riesgos por conveniencia), así como también aplicaciones y componentes "grandes, reutilizables, empresariales". Puede hacer un trabajo bastante bueno en ambos, pero solo si "te conoces a ti mismo" y evitas usar las partes de "conveniencia", excepto cuando estás absolutamente seguro de que de hecho puedes permitírtelo. La mayoría de las veces, la consideración clave es: "¿Qué significa esto para mis espacios de nombres y conocimiento de su formación y uso por parte del compilador, pelusa y c, lectores y mantenedores humanos, etc.?".

Recuerda: "Los espacios de nombres son una gran idea: ¡hagamos más de eso!" es cómo el Zen de Python concluye ... pero Python, como un "lenguaje para adultos que consienten", le permite definir los límites de lo que eso implica, como consecuencia de su entorno de desarrollo, objetivos y prácticas. ¡Usa este poder de manera responsable! -)


Ni en un millón de años. No está claro cuál es el contexto del formato: los locals podrían incluir casi cualquier variable. self.__dict__ no es tan vago. Perfectamente horrible para dejar a los futuros desarrolladores rascándose la cabeza sobre lo que es local y lo que no es local.

Es un misterio intencional. ¿Por qué ensillar a su organización con futuros dolores de cabeza por mantenimiento como ese?