2010-09-16 10 views
7

Hay un lenguaje Ruby útil que usa tap que le permite crear un objeto, hacer algunas operaciones en él y devolverlo (utilizo una lista aquí solo como ejemplo, mi código real es más complicado)):Ruby's tap idiom en Python

def foo 
    [].tap do |a| 
    b = 1 + 2 
    # ... and some more processing, maybe some logging, etc. 
    a << b 
    end 
end 

>> foo 
=> [1] 

con rieles hay un método similar llamado returning, para que pueda escribir:

def foo 
    returning([]) do |a| 
    b = 1 + 2 
    # ... and some more processing, maybe some logging, etc. 
    a << b 
    end 
end 

que habla por sí mismo. No importa cuánto procesamiento haga en el objeto, aún está claro que es el valor de retorno de la función.

En Python tengo que escribir:

def foo(): 
    a = [] 
    b = 1 + 2 
    # ... and some more processing, maybe some logging, etc. 
    a.append(b) 
    return a 

y me pregunto si hay una manera de puerto de este idioma de Ruby en Python. Mi primer pensamiento fue utilizar la declaración with, pero return with no es una sintaxis válida.

+3

es interesante tener en cuenta que el ejemplo pitón tiene uno menos la línea. ¿Podría darnos un ejemplo ligeramente más profundo que muestre mejor qué está mal con el modo python? – aaronasterling

+0

@ Aaron: ¿Tal vez para evitar crear una variable local explícitamente? – kennytm

+3

Es una línea menos debido al 'fin's de Ruby. La diferencia está más en la intención, que creo que Ruby describe mejor. En Ruby, el código dice: "Devolveré * este * objeto pero haré * esas * cosas primero", mientras que en Python el código dice muy imperativamente "Creo este objeto, le hago esas cosas y luego devuélvalo ", que cuando tienes más de una operación puede ser un poco más difícil de detectar. –

Respuesta

6

Puede ponerlo en práctica en Python de la siguiente manera:

def tap(x, f): 
    f(x) 
    return x 

Uso:

>>> tap([], lambda x: x.append(1)) 
[1] 

Sin embargo, no será tanto su uso en Python 2.x, ya que es en Ruby porque lambda las funciones en Python son bastante restrictivas. Por ejemplo, no puede alinear una llamada para imprimir porque es una palabra clave, por lo que no puede usarla para el código de depuración en línea. Puedes hacer esto en Python 3.x aunque no está tan limpio como la sintaxis de Ruby.

>>> tap(2, lambda x: print(x)) + 3 
2 
5 
+0

Lambda me permite agregar solo una sola expresión allí y no se ve muy bien. Lo ideal sería algo como esto: def foo(): de retorno con [] como: a.append (1) pero obviamente eso no funciona. :( –

+0

@ Michał Kwiatkowski: Puede evaluar expresiones múltiples poniéndolas en una lista: 'pulse ([], lambda x: [x.append (1), x.append (2)])', pero sí, estoy de acuerdo con usted que no se ve bien y no es práctico en Python debido a la sintaxis detallada y las restricciones de las expresiones lambda. –

6

Si desea que esta bastante malo, se puede crear un gestor de contexto

class Tap(object): 
    def __enter__(self, obj): 
     return obj 

    def __exit__(*args): 
     pass 

que se puede utilizar como:

def foo(): 
    with Tap([]) as a: 
     a.append(1) 
     return a 

No hay manera alrededor de la declaración return y with realmente no hace nada aquí Pero supongo que tienes Tap desde el principio que te indica en qué consiste la función. Es mejor que usar lambdas porque no estás limitado a las expresiones y puedes tener prácticamente lo que quieras en la declaración with.

En general, yo diría que si quiere tap tan malo, entonces quédese con ruby ​​y si necesita programar en python, use python para escribir python y no ruby. Cuando llegue a aprender ruby, tengo la intención de escribir ruby;)

+2

Las variables creadas debido a las declaraciones 'con' siguen siendo visibles en el código que sigue al bloque' with', por lo que podría tira de la 'devolución' hacia atrás un nivel de sangría. Y luego creo que tienes un 'toque' de Python - algún código cuyo único propósito es agregar una sobrecarga de llamada de función y otro nivel de sangría. – llasram

+1

@llasram, sí, esa es otra forma de hacerlo. Prefiero mantener mis referencias a las variables creadas como resultado de enunciados como 'with' y' for' en el bloque definido por esas declaraciones para la compatibilidad con versiones anteriores. Algún día, corregirán esa identidad incorrecta. – aaronasterling

+0

Tener el retorno al final p retty niega mucho este enfoque como una solución al "problema" (como lo hace con mi enfoque de decorador). Reemplazar el con 'Tap ([]) como una:' línea con 'a = []' tiene exactamente el mismo efecto. – Glenjamin

-1

En parte estoy de acuerdo con los demás en que no tiene mucho sentido implementar esto en Python. Sin embargo, en mi humilde opinión, el camino de Mark Byers es el camino, pero ¿por qué lambdas (y todo lo que viene con ellos)? ¿No puedes escribir una función separada para llamar cuando sea necesario?

Otra forma de hacer básicamente lo mismo podría

map(afunction(), avariable) 

pero esta característica hermosa no es un incorporada en Python 3, escucho.

+0

te refieres a 'map (afunction, avariable)' y esto supone que 'avariable' es un iterable que no está garantizado. – aaronasterling

+0

@ Aaron, ¡oops! eso estaba demasiado ansioso, ahora veo. EDITAR: engañado por los ejemplos de la lista :-) ¿entonces es correcto usar cualquier función en lugar de las lambdas de Mark, o fallé por completo? si es así, ¿puede tener una función "principal" con (un objeto y más de una función) como argumentos? eso haría el truco de una manera bastante pitonica – MattiaG

1

Casi ningún programador de Ruby usa tap de esta manera.De hecho, todos los mejores programadores de Ruby que conozco dicen que tap no tiene uso excepto en la depuración.

¿Por qué no hacer esto en su código?

[].push(1) 

y recordar Array soporta una interfaz fluida, lo que incluso puede hacer esto:

[].push(1).push(2) 
+1

Como se menciona en la pregunta utilicé una matriz solo como un ejemplo. Hay muchos métodos que no son encadenables, por lo tanto, la necesidad de tocar/regresar. –

+0

Aún así, no lo llamaría rubí 'idioma' ya que no muchos programadores usan el tap de esta manera. – horseyguy

+0

Hace 15 minutos Tuve que declarar una variable más inútil y escribir dos líneas más de código para un registro más simple simplemente porque Python no puede 'tocar'. – Nakilon

20

Respuesta corta: Rubí anima método de encadenamiento, Python no lo hace.

Supongo que la pregunta correcta es: ¿Para qué sirve Ruby's tap?

Ahora no sé mucho sobre Ruby, pero al buscar en Google tuve la impresión de que tap es conceptualmente útil como método de encadenamiento.

En Ruby, el estilo: SomeObject.doThis().doThat().andAnotherThing() es bastante idiomático. Se basa en el concepto de fluent interfaces, por ejemplo. Ruby's tap es un caso especial de esto donde en lugar de tener SomeObject.doThis() usted define doThis sobre la marcha.

¿Por qué estoy explicando todo esto? Porque nos dice por qué tap no tiene un buen soporte en Python. Con las debidas advertencias, Python no llama al encadenamiento.

Por ejemplo, los métodos de lista de Python generalmente devuelven None en lugar de devolver la lista de mutaciones. Las funciones como map y filter no son métodos de lista. Por otro lado, muchos métodos de matriz de Ruby devuelven la matriz modificada.

Aparte de ciertos casos como algunos ORM, el código Python no utiliza interfaces fluidas.

Al final es la diferencia entre el idiomático Ruby y el idiomático Python. Si va de un idioma a otro, necesita ajustarse.

+1

Si quiere encadenar el método en python, solo regrese. Muchas API hacen esto ... – fuzzyman

1

Tuve una idea para lograr esto usando decoradores de funciones, pero debido a la distinción en python entre expresiones y declaraciones, esto terminó requiriendo que el retorno esté al final.

La sintaxis de ruby ​​rara vez se utiliza en mi experiencia, y es mucho menos legible que el enfoque explícito de python. Si python tuviera retornos implícitos o una forma de envolver múltiples declaraciones en una sola expresión, entonces esto sería factible, pero no tiene ninguna de esas cosas por diseño.

Aquí está mi - un tanto insustancial - enfoque decorador, para referencia:

class Tapper(object): 
    def __init__(self, initial): 
     self.initial = initial 
    def __call__(self, func): 
     func(self.initial) 
     return self.initial 

def tap(initial): 
    return Tapper(initial) 

if __name__ == "__main__": 
    def tapping_example(): 
     @tap([]) 
     def tapping(t): 
      t.append(1) 
      t.append(2) 
     return tapping 

    print repr(tapping_example())