Python: está utilizando “..% (var) s ..”% locals () ¿una buena práctica?

Descubrí este patrón (o anti-patrón) y estoy muy feliz 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 a 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? Sí / No, por favor explique.

    Está bien para aplicaciones pequeñas 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 de mantenimiento y muchos mantenedores, esta práctica puede llevar a problemas de mantenimiento, y creo que de ahí viene la respuesta de @ S.Lott. 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ías una cadena de formato codificada (o, si la tuvieras, 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 dichas aplicaciones) deben ser compatibles con la internacionalización (AKA i18n) y la localización (AKA L10n): desea que su aplicación pueda emitir “Hello Paul” en ciertos países y culturas, “Hola Paul” en algunos otros, “Ciao Paul” en otros todavía, y así sucesivamente. Por lo tanto, la cadena de formato se sustituye más o menos automáticamente con otra en tiempo de ejecución, dependiendo de la configuración de localización actual; En lugar de estar codificado, vive en algún tipo de base de datos. Para todos los propósitos, 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 puede verificar de forma trivial exactamente qué nombres locales va a utilizar el formato. Tendría que abrir y leer detenidamente la base de datos L10N, identificar las cadenas de formato que se van a utilizar aquí en diferentes configuraciones, verificarlas todas.

    Entonces, en la práctica, no sabe qué nombres locales se van a usar, lo que dificulta horriblemente el mantenimiento de la función. No se atreve a renombrar o eliminar ninguna variable local, ya que podría romper horriblemente la experiencia del usuario para los usuarios con alguna combinación oscura (para usted) de idioma, ubicación y preferencias

    Si tiene pruebas de integración / regresión excelentes, la ruptura se detectará antes del lanzamiento de la versión beta, pero el control de calidad le gritará y el lanzamiento se retrasará … y, seamos honestos, al mismo tiempo que apunta a una cobertura del 100% con pruebas unitarias es razonable, realmente no lo es con las pruebas de integración , una vez que considera la explosión combinatoria de configuraciones [[para L10N y por muchas más razones]] y versiones compatibles de todas las dependencias. Entonces, simplemente no se arriesga a arriesgarse a romperse porque “quedarán atrapados en el control de calidad” (si lo hace, no puede durar mucho en un entorno que desarrolle aplicaciones grandes o componentes reutilizables ;-).

    Por lo tanto, en la práctica, nunca quitará la variable local “nombre” aunque la gente de la experiencia del usuario haya cambiado ese saludo por un “Bienvenida, señor del terror” más apropiado. (y adecuadamente versiones L10n’ed de los mismos). Todo porque fuiste por los locals()

    Por lo tanto, está acumulando cruceros debido a la forma en que ha reducido su capacidad para mantener y editar su código, y tal vez esa variable local de “nombre” solo exista porque se haya recuperado de una base de datos o algo similar, por lo que conservarla (o Algunos otros locales no solo son cruceros, sino que también reducen su rendimiento. ¿Vale la pena la conveniencia superficial de los locals() ? -)

    ¡Pero espera, hay algo peor! Entre los muchos servicios útiles que un progtwig similar a una lint (como, por ejemplo, pylint ) puede hacer por usted, es advertirle sobre variables locales no utilizadas (ojalá lo hiciera también para globos globales no utilizados, pero para componentes reutilizables, eso es sólo un poco demasiado duro ;-). De esta manera, detectará faltas de ortografía ocasionales, como if ...: nmae = ... muy rápido y barato, en lugar de ver un descanso de prueba de unidad y hacer un trabajo de investigación para descubrir por qué se rompió (tiene obsesivo , pruebas de unidad generalizadas que podrían detectar esto finalmente, ¿no? -) – lint le informará acerca de una variable local no utilizada nmae y la arreglará de inmediato.

    Pero si tiene en su código un blah.format(**locals()) , o equivalente a blah % locals() … es SOL, amigo! -) ¿Cómo sabrá la pelusa pobre si nmae está en ¿de hecho una variable no utilizada, o en realidad se usa por cualquier función externa o método al que le esté pasando locals() ? No puede, ya sea que avisará de todos modos (provocando un efecto de “lobo llorón” que eventualmente lo llevará a ignorar o deshabilitar tales advertencias), o nunca avisará (con el mismo efecto final: no hay advertencias 😉 .

    Compare esto con la alternativa “explícito es mejor que implícito” …

     blah.format(name=name) 

    Allí, ninguna de las preocupaciones de mantenimiento, rendimiento y de pelusas de am-I-hampering se aplica; ¡felicidad! Deje en claro de inmediato a todos los interesados ​​(incluida la pelusa 😉 exactamente qué variables locales se están utilizando y con 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 con “tú mismo” me refiero a “el propósito y el scope de tu código”. Si se trata de un 1-off-or-theeaout abouty, nunca será i18n’d y L10n’d, casi no necesitará mantenimiento futuro, nunca se reutilizará en un contexto más amplio, etc, etc., luego siga adelante y use los locals() por su conveniencia pequeña pero limpia; Si sabe lo contrario, o incluso si no está completamente seguro, caiga en la precaución y haga las cosas más explícitas: sufra el pequeño inconveniente de explicar exactamente lo que está haciendo y disfrute de todas las ventajas resultantes.

    Por cierto, este es solo uno de los ejemplos en los que Python se esfuerza por respaldar tanto la progtwigción “pequeña, única, exploratoria, tal vez interactiva” (al permitir y respaldar conveniencias riesgosas que se extienden mucho más allá de lo locals() . eval , exec , y varias otras formas en que puede amontonar espacios de nombres y arriesgar los impactos de mantenimiento por conveniencia), así como aplicaciones y componentes “grandes, reutilizables, para empresas”. Puede hacer un buen trabajo en ambos, pero solo si se “conoce” y evita usar las partes de “conveniencia”, excepto cuando está absolutamente seguro de que puede pagarlas. La mayoría de las veces, la consideración clave es, “¿qué hace esto con mis espacios de nombres y la conciencia de su formación y uso por parte del comstackdor, la pelusa, los lectores y mantenedores humanos, etc.?”.

    Recuerde: “Los espacios de nombres son una gran idea, ¡vamos a hacer más de esos!” es cómo concluye el Zen de Python … 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, objectives y prácticas. Usa este poder responsablemente! -)

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

    Nunca escribo código que no necesito escribir; menos 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 entender.

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

    Es un misterio intencional. ¿Por qué cargar a tu organización con futuros dolores de cabeza como ese?

    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) 

    Lo uso mucho para los métodos de reproducción , por ejemplo,

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

    El "%(name)s" % o aún mejor, el "{name}".format() tiene el mérito de

    • siendo más legible que “% 0s”
    • ser independiente del orden del argumento
    • no obliga a usar todos los argumentos en la cadena

    Tendré a favor de 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 las vars([object]) integradas vars([object]) ( documentación ) puede hacer que el segundo se vea mejor para usted:

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

    El efecto es, por supuesto, el mismo.

    Ahora hay una forma oficial de hacer esto, a partir de Python 3.6.0: literales de cadena con formato .

    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:

    • Python 3.6 Lo nuevo
    • PEP 498
    • Descripción del análisis léxico