[Python-es] Análisis sobre Python vs ruby WAS: Python y/o ruby para no programadores

lasizoillo lasizoillo en gmail.com
Mar Feb 9 17:20:16 CET 2010


El día 9 de febrero de 2010 15:52, Olemis Lang (Simelix)
<olemis+py en gmail.com> escribió:
> Antes q todo, por lo menos mi intención no es comenzar un flamewar,
> sino analizar las potencialidades, facilidades o como quiera q le
> llamemos de ambos lenguajes . Aclaro q prefiero Python, pero q también
> me gusta Ruby ;o)
>

Claro, estamos hablando de gustos personales. Todo lo que se pueda
aprender bienvenido sea.

> 2010/2/9 lasizoillo <lasizoillo en gmail.com>:
>> El día 9 de febrero de 2010 14:09, Olemis Lang (Simelix)
>> <olemis+py en gmail.com> escribió:
>>> Solo una precisión ;o)
>>>
>>  - Pero los bloques no se limitan necesariamente a eso.
>>>     - Por ejemplo,
>>>       en el caso de IO asíncrona muy frecuentemente se utilizan
>>>       callbacks. En Python la única solución es escribir una
>>>       función o «clase invocable» (i.e. callable) y pasarla como parámetro
>>>       a alguna función o mecanismo de registro q tenga la API.
>>>       Esto puede ser engorroso. En Ruby los casos sencillos pueden
>>>       ser descritos con un bloque de código inline y queda más legible
>>>       y menos «verbose» (más conciso).
>>
>> #Asi a ojo y sin probar
>>
>> def block(code):
>>   def inner():
>>       exec code
>>   return inner
>>
>> cosa_asincrona.register(block("print 'no es para tanto'"))
>> otra_cosa.register(block("""
>>    for i in range(10):
>>        print 'no volvere a copiar en clase'
>> """))
>>
>> No lo he probado porque soy feliz definiendo funciones para las
>> llamadas asincronas.
>
> Sería curioso saber si cuando Ud lo ejecuta le funciona. Pongamos el
> ejemplo de cuando se hace el exec en un módulo con una cadena definida
> en otro, digamos porq Ud ofrece `block` en un módulo y especifica lo q
> hay q hacer en otro. En este caso el namespace global no coincide con
> el q Ud espera y entonces solo obtendrá un `NameError` tras otro y
> tendrá que apelar al `stack` para saber de dónde lo llamaron y, y ...
> y ya no sería tan simple (o podríamos forzar al cliente de la librería
> a estar declarando `block` en cada módulo q él haga, y si cambia algo
> en el framework, como hay gran dispersión del código «scattering», y
> como eso es casi el anti patrón más malo en la ingeniería de software,
> el mantenimiento del código no será nada divertido). E.g. me viene a
> la mente esto porq me sucedió con CASPy cuando declaraba «inline
> assertions».
>

Bastaria con pasarle los globals() y locals() al codigo a ser probado:
http://docs.python.org/reference/simple_stmts.html#the-exec-statement

> De todas formas no estoy en contra de un estilo ni de otro, solo q
> IMO, los bloques inline podrían aportar legibilidad en algunos casos
> ;o).

Estoy de acuerdo en que es una cuestión de estilo. Pero para mi un
estilo explicito es más conveniente para un lenguaje, aunque puede
resultar feo para las cosas creadas con un framework.


> ¿Qué es más legible? (ambos ejemplos escritos a la carrera y sin
> probar ;o) :
>
> {{{
> #!python
>
> # Antes de 2.7
> class TestCase
>  def _helper_method(self, x, b=33, c=12)
>     x = 0
>     x = b + c / x
>
>  def test_xxx(self):
>    self.assertRaises(ZeroDivisionError, self._helper_method, 1, c=4)
> }}}
>
> {{{
> #!python
>
> # Para 2.7
> class TestCase
>  def test_xxx(self):
>    with self.assertRaises(ZeroDivisionError):
>      x = 0
>      x = 33 + 4 / x
> }}}
>

Está claro que el with es más cómodo que hacer una función inline
(para un único caso se suelen usar funciones inline) para un único
caso. Pero tan poco es tan raro de ver que un test usa una función que
reutiliza algo de código:
http://bitbucket.org/neithere/pyrant/src/tip/nose_tests/test_query.py#cl-329

>
> Mi opinión es q las funciones surgieron, fundamentalmente, como un
> mecanismo de reutilización. Que puedan ser utilizadas para sustituir
> carencias del lenguaje, es válido, pero todavía las carencias están
> ahí
>

Yo no veo carencia en el tema de bloques en python. Quizá es que no
conozca los bloques de ruby. Pero por lo que he podido ver solo tienen
dos ventajas:
 * Azucar sintáctico: Te ahorras una o dos lineas de código a la hora
de no tener que escribir una función inline. Parecido al tema de
querer cargarse las funciones lambda porque no son necesarias.
 * Algunas cosas pueden resultar más legibles:
   * Versión pythonica: http://github.com/breily/juno
   * Versión ruby: http://github.com/bmizerany/sinatra/

La versión de ruby es algo más concisa que la pythonica. Pero no veo
nada más allá de azucar sintactico.

>
>>>    - Otro ejemplo, la solución al `case` o `switch` de Python basada en dict(s)
>>>      implica q a cada llave se le asigne algo q, al ejecutarlo, se realiza lo
>>>      q sea específico de esa alternativa. Pasa algo más o menos semejante,
>>>      en Python resulta engorroso escribir una función para cada alternativa,
>>>      sin el propósito de reutilizarla (sino solo para suplir una carencia del
>>>      lenguaje) y la legibilidad es pésima, porq todo está separado y
>>>      disperso y con un vistazo no se puede tener idea de lo
>>>      q pasa. Con bloques inline como los de Ruby se podría mejorar esto.
>>>
>>
>> Si el if/elif/else no te vale, igual si que deberias considerar usar
>> funciones separadas :-O
>>
>
> Ok, lo cual sería mucho más legible que algo como (en un Python
> Rubizadoribilistizado ;o)
>
> {
>  'k' : do
>          x = 1; y = 2
>        end,
>  'w' : do
>          x = 3; y = 4
>        end,
>  'g' : do
>          x = 5; y = 6
>        end,
>  'xxx' : do
>          x = 7; y = 8
>        end,
> }
>

Si viera un código que utilizara eso me cortaria las venas. De pronto
me encontraria sorprendido de que me aparezan mágicamente las
variables x e y. Recuerda que estarías invocando a una función
devuelta por un diccionario que puede estar definido muy lejos de
donde se está usando.

Donde algunos ven carencias, yo veo una feature del lenguaje. Esa
feature es como la de impedir asignaciones en un if.

> ... verdad ? Si se da cuenta, utilizando funciones tendríamos q hacer
> 4 que retornaran una tupla y se le asignaran a x y y. La otra cuestion
> es la de los parámetros de esas funciones. Generalmente se necesitan
> cosas en el namespace local para utilizarlas dentro del `switch` y
> estas hay q pasarlas como parámetros. Claro, se pueden usar las
> clausuras, pero reflexionemos todos los conceptos q se han mencionado
> y cuan similares son a lo q nosotros pensamos cuando vamos a tomar uno
> de varios caminos para tomar una decisión.
>

El código que se escribe se ha de leer muchas veces. En las
revisiones, mejoras, correcciones de bugs, ... Prefiero un código que
se tarda unos segundos más en escribirlo y se tarda varios minutos
menos en leerlo. Prefiero que uno no se vea tentado a sacrificar esa
legibilidad ni en los momentos de peor estrés.

>
>>> Claro, q en todos estos casos se pudiera utilizar un objeto de typo
>>> `code`, la función `compile`, o `exec`, pero el efecto no es el mismo
>>> ... IMO
>>
>> ¿Por el coloreado de sintaxis en el editor?
>>
>
> No, de hecho no uso nada eso, todo lo programo con VIm sin colorcitos :P

:sy on

Es lo que yo uso en vim para que me sea incluso más fácil de leer el
código python. Ese código que aunque escribí hace tiempo tengo que
leer una y otra vez ;-)

>
> Lo digo porq errores en el código se detectan al compilar hacia .PYC,
> mientras que errores dentro de una cadena ejecutada con exec se
> detectan al ejecutar la línea de código del exec (i.e. el hecho de q
> compile no quiere decir q no está roto ;o). Además, si las
> continuaciones son «perjudiciales como el goto», no creo q un exec sea
> más beneficioso, considerando que rompe la secuencia precondición,
> invariante, postcondición y las buenas prácticas de escritura del
> código q plantearon Dijkstra, CAR Hoare et al. basados en modelos como
> CSP, o luego DbC, ... Pero bueno, esa meta-tranca no la sigo para no
> ponerme demasiado OT
>

La generación de bytecodes (ficheros .pyc) sólo detecta errores sintácticos.

4c4-local:~ lasi$ python kk.py
hola
m4c4-local:~ lasi$ cat kk.py
def kk():
    print variable_que_no_existe

print "hola"
m4c4-local:~ lasi$ pyflakes kk.py
kk.py:2: undefined name 'variable_que_no_existe'


Usar pyflakes o pylint es mejor ;-)

>
>> Creo que esto se empieza a perecer a una lucha dialectica entre si la
>> tortilla de patata está más buena con cebolla o sin ella :-P
>
> Puede ser q ese sea el camino q otros quieran darle a la conversación,
> yo solo quiero explorar y análizar las capacidades del lenguaje
> tratando de estar lo menos prejuiciado posible por el hecho de q me
> gusta, para así poder ser medianamente objetivo y 30% acertado, y
> tener bien claro cómo es posible mejorarlo ;o)
>

Es objetivo que esa funcionalidad no está en el lenguaje. Es subjetivo
el hecho de que sea deseable. Yo personalmente no la deseo si el
precio a pagar puede ser la perdida de legibilidad.

Un saludo:

Javi



Más información sobre la lista de distribución Python-es