python ruby programming-languages monkeypatching fluent-interface

¿Puedes aplicar métodos de parche mono en los tipos de núcleo en Python?



ruby programming-languages (14)

Ruby puede agregar métodos a la clase Number y otros tipos de núcleo para obtener efectos como este:

1.should_equal(1)

Pero parece que Python no puede hacer esto. ¿Es esto cierto? Y si es así, ¿por qué? ¿Tiene algo que ver con el hecho de que el tipo no se puede modificar?

Actualización: En lugar de hablar sobre las diferentes definiciones de parche de mono, me gustaría centrarme en el ejemplo anterior. Ya he concluido que no se puede hacer como algunos de ustedes han respondido. Pero me gustaría una explicación más detallada de por qué no se puede hacer, y tal vez qué característica, si está disponible en Python, permitiría esto.

Para responder a algunos de ustedes: La razón por la que podría querer hacer esto es simplemente estética / legibilidad.

item.price.should_equal(19.99)

Esto se parece más al inglés e indica claramente cuál es el valor probado y cuál es el valor esperado, como se supone que debe:

should_equal(item.price, 19.99)

Este concepto es en lo que se Rspec y algunos otros frameworks de Ruby.


¿Qué hace should_equal ? ¿Es un booleano que devuelve True o False ? En ese caso, está deletreado:

item.price == 19.99

No hay ninguna explicación para el gusto, pero ningún desarrollador regular de Python diría que es menos legible que tu versión.

¿ should_equal en should_equal lugar establecer algún tipo de validador? (¿Por qué un validador se limitaría a un valor? ¿Por qué no simplemente establecer el valor y no actualizarlo después de eso?) Si desea un validador, esto nunca podría funcionar de todos modos, ya que está proponiendo modificar un entero particular o todo enteros. (Un validador que requiere 18.99 para igualar 19.99 siempre fallará). En cambio, podría deletrearlo así:

item.price_should_equal(19.99)

o esto:

item.should_equal(''price'', 19.99)

y define los métodos apropiados en la clase o superclases del elemento.


¿Qué quieres decir exactamente con Monkey Patch aquí? Hay varias definiciones ligeramente diferentes .

Si te refieres a "¿puedes cambiar los métodos de una clase en tiempo de ejecución?", Entonces la respuesta es rotundamente afirmativa:

class Foo: pass # dummy class Foo.bar = lambda self: 42 x = Foo() print x.bar()

Si quieres decir, "¿puedes cambiar los métodos de una clase en tiempo de ejecución y hacer que todas las instancias de esa clase cambien después de los hechos ?" entonces la respuesta es sí también. Simplemente cambie el orden ligeramente:

class Foo: pass # dummy class x = Foo() Foo.bar = lambda self: 42 print x.bar()

Pero no puede hacer esto para ciertas clases incorporadas, como int o float . Los métodos de estas clases se implementan en C y se sacrifican ciertas abstracciones para facilitar y hacer más eficiente la implementación.

No tengo muy claro por qué querrías alterar el comportamiento de las clases numéricas integradas de todos modos. ¡Si necesitas alterar su comportamiento, subclases!


Aquí hay un ejemplo de implementación item.price.should_equal , aunque usaría Decimal en lugar de float en un programa real:

class Price(float): def __init__(self, val=None): float.__init__(self) if val is not None: self = val def should_equal(self, val): assert self == val, (self, val) class Item(object): def __init__(self, name, price=None): self.name = name self.price = Price(price) item = Item("spam", 3.99) item.price.should_equal(3.99)


Así es como logro el comportamiento .should_something ...

result = calculate_result(''blah'') # some method defined somewhere else the(result).should.equal(42)

o

the(result).should_NOT.equal(41)

Incluí un método decorador para extender este comportamiento en tiempo de ejecución en un método independiente:

@should_expectation def be_42(self) self._assert( action=lambda: self._value == 42, report=lambda: "''{0}'' should equal ''5''.".format(self._value) ) result = 42 the(result).should.be_42()

Tienes que saber un poco sobre las partes internas, pero funciona.

Aquí está la fuente:

https://github.com/mdwhatcott/pyspecs

También está en PyPI bajo pyspecs.


Los tipos de núcleo de Python son inmutables por diseño, como otros usuarios han señalado:

>>> int.frobnicate = lambda self: whatever() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can''t set attributes of built-in/extension type ''int''

Ciertamente podría lograr el efecto que describe haciendo una subclase, ya que los tipos definidos por el usuario en Python son mutables por defecto.

>>> class MyInt(int): ... def frobnicate(self): ... print ''frobnicating %r'' % self ... >>> five = MyInt(5) >>> five.frobnicate() frobnicating 5 >>> five + 8 13

No es necesario hacer pública la subclase MyInt tampoco; uno podría definirlo inline directamente en la función o método que construye la instancia.

Ciertamente, hay algunas situaciones en las que los programadores de Python que dominan el idioma consideran que este tipo de subclases es lo correcto. Por ejemplo, os.stat() devuelve una subclase tuple que agrega miembros con nombre, precisamente para abordar el tipo de problema de legibilidad al que se refiere en su ejemplo.

>>> import os >>> st = os.stat(''.'') >>> st (16877, 34996226, 65024L, 69, 1000, 1000, 4096, 1223697425, 1223699268, 1223699268) >>> st[6] 4096 >>> st.st_size 4096

Dicho esto, en el ejemplo específico que das, no creo que subclassing float en item.price (o en otro lugar) sea muy probable que se lo considere algo pitónico. Puedo imaginar fácilmente a alguien decidiendo agregar un método price_should_equal() al item si ese fuera el caso de uso principal; si uno busca algo más general, tal vez tenga más sentido usar argumentos con nombre para aclarar el significado deseado, como en

should_equal(observed=item.price, expected=19.99)

O algo por el estilo. Es un poco detallado, pero sin duda podría mejorarse. Una posible ventaja de este enfoque sobre el parche de mono estilo Ruby es que should_equal() podría realizar fácilmente su comparación en cualquier tipo, no solo int o float . Pero tal vez estoy demasiado atrapado en los detalles del ejemplo particular que proporcionó.


No puede parchar los tipos de núcleo en Python. Sin embargo, puede usar pipe para escribir un código legible más humano:

from pipe import * @Pipe def should_equal(obj, val): if obj==val: return True return False class dummy: pass item=dummy() item.value=19.99 print item.value | should_equal(19.99)


No, lamentablemente no se pueden extender los tipos implementados en C en tiempo de ejecución.

Puede subclase int, aunque no es trivial, es posible que deba sobrescribir __new__ .

También tienes un problema de sintaxis:

1.somemethod() # invalid

sin embargo

(1).__eq__(1) # valid


No, no puedes hacer eso en Python. Considero que es algo bueno.


No, no puedes. En Python, todos los datos (clases, métodos, funciones, etc.) definidos en los módulos de extensión C (incluidos los builtins) son inmutables. Esto se debe a que los módulos C se comparten entre varios intérpretes en el mismo proceso, por lo que el parcheo de ellos también afectaría a los intérpretes no relacionados en el mismo proceso.

Sin embargo, las clases definidas en el código Python pueden ser parcheadas porque son locales para ese intérprete.


No, pero tiene UserDict UserString y UserList que fueron hechos exactamente con esto en mente.

Si busca en google encontrará ejemplos para otros tipos, pero esto está incluido.

En general, el parche de mono se usa menos en Python que en Ruby.


Parece que lo que realmente querías escribir es:

assert item.price == 19.99

(Por supuesto, comparar floats por igualdad, o usar flotadores por precios, es una mala idea , entonces escribiría assert item.price == Decimal(19.99) o cualquier clase numérica que estuviese usando por el precio).

También puede usar un marco de prueba como py.test para obtener más información sobre py.test en sus pruebas.


Puedes hacer esto, pero lleva un poco de piratería. Afortunadamente, hay un módulo ahora llamado "Fruta prohibida" que le da el poder de parchar métodos de tipos incorporados de manera muy simple. Puedes encontrarlo en

http://clarete.github.io/forbiddenfruit/?goback=.gde_50788_member_228887816

o

https://pypi.python.org/pypi/forbiddenfruit/0.1.0

Con el ejemplo de pregunta original, después de escribir la función "should_equal", simplemente lo haría

from forbiddenfruit import curse curse(int, "should_equal", should_equal)

y eres bueno para ir! También hay una función "inversa" para eliminar un método parcheado.


Si realmente quieres hacer un parche de mono en Python, puedes hacer un truco (sortof) con la técnica "importar foo como barra".

Si tiene una clase como TelnetConnection y desea ampliarla, subclass en un archivo separado y llámalo algo así como TelnetConnectionExtended.

Luego, en la parte superior de tu código, donde normalmente dices:

import TelnetConnection

cambiar eso para ser:

import TelnetConnectionExtended as TelnetConnection

y luego, en cualquier parte de su código que haga referencia a TelnetConnection, hará referencia a TelnetConnectionExtended.

Lamentablemente, esto supone que tienes acceso a esa clase, y el "como" solo funciona dentro de ese archivo en particular (no es un cambio de nombre global), pero he encontrado que es útil de vez en cuando.


def should_equal_def(self, value): if self != value: raise ValueError, "%r should equal %r" % (self, value) class MyPatchedInt(int): should_equal=should_equal_def class MyPatchedStr(str): should_equal=should_equal_def import __builtin__ __builtin__.str = MyPatchedStr __builtin__.int = MyPatchedInt int(1).should_equal(1) str("44").should_equal("44")

Que te diviertas ;)