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

Ruby puede agregar métodos a la clase Número 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 diferentes definiciones de parches de monos, me gustaría centrarme en el ejemplo anterior. Ya he llegado a la conclusión de que no se puede hacer ya que 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, lo permita.

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 lee más como en inglés e indica claramente cuál es el valor probado y cuál es el valor esperado, como se supone:

 should_equal(item.price, 19.99) 

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

Related of "¿Puedes simular métodos de parche en los tipos de núcleo en Python?"

¿Qué quieres decir con “mono parche” 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 enfáticamente sí:

 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 del hecho ?” entonces la respuesta es sí también. Solo cambia el orden un poco:

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

Pero no puedes 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 que la implementación sea más fácil y eficiente.

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

No, no puedes. En Python, todos los datos (clases, métodos, funciones, etc.) definidos en los módulos de extensión C (incluidos los incorporados) son inmutables. Esto se debe a que los módulos de C se comparten entre varios intérpretes en el mismo proceso, por lo que la aplicación de parches también afectaría a los intérpretes no relacionados en el mismo proceso. (Múltiples intérpretes en el mismo proceso son posibles a través de la API de C , y ha habido algunos esfuerzos para hacerlos utilizables a nivel de Python).

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

 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 😉

Puedes hacer esto, pero se necesita un poco de piratería. Afortunadamente, ahora hay un módulo llamado “Fruta Prohibida” que le da la posibilidad de parchear métodos de tipos integrados de manera muy simple. Lo puedes encontrar 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 la pregunta original, después de escribir la función “should_equal”, simplemente harías

 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.

Los tipos básicos de Python son inmutables por diseño, como han señalado otros usuarios:

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

Ciertamente, podría lograr el efecto que describe creando una subclase, ya que los tipos definidos por el usuario en Python son mutables de forma predeterminada.

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

Tampoco es necesario hacer pública la subclase MyInt ; Uno podría definirlo en línea directamente en la función o método que construye la instancia.

Ciertamente, hay algunas situaciones en las que los progtwigdores de Python que dominan el idioma consideran que este tipo de subclase es lo correcto. Por ejemplo, os.stat() devuelve una subclase de tuple que agrega miembros nombrados, 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 usted da, no creo que la float subclases en item.price (o en cualquier otro lugar) sea muy probablemente considerada como algo pythonico. 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 estuviera buscando algo más general, tal vez tendría más sentido utilizar 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 tal enfoque sobre el parche de monos 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 usted proporcionó.

No puedes parchear los tipos de núcleo en Python. Sin embargo, podrías 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) 

Aquí hay un ejemplo de implementación de item.price.should_equal , aunque usaría Decimal en lugar de float en un progtwig 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) 

Si realmente quieres hacer un parche de mono en Python, puedes hacer un “hackeo” con la técnica “importar foo as bar”.

Si tiene una clase como TelnetConnection y desea extenderla, haga una subclase en un archivo separado y llámelo como TelnetConnectionExtended.

Luego, en la parte superior de tu código, donde normalmente dirías:

 import TelnetConnection 

cambiar eso para ser:

 import TelnetConnectionExtended as TelnetConnection 

y luego, en todas partes del código al que hace referencia a TelnetConnection, se hará referencia a TelnetConnectionExtended.

Lamentablemente, esto supone que usted tiene 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.

No, pero tiene UserDict UserString y UserList que se hicieron con esto exactamente en mente.

Si busca en Google, encontrará ejemplos para otros tipos, pero esto está integrado.

En general, el parcheo de monos se usa menos en Python que en Ruby.

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

Puede __new__ subclase de int, aunque no es trivial, es posible que tenga que reemplazar __new__ .

También tienes un problema de syntax:

 1.somemethod() # invalid 

sin embargo

 (1).__eq__(1) # valid 

No, no puedes hacer eso en Python. Considero que es una buena cosa.

¿Qué should_equal hacer? ¿Es un booleano que vuelve True o False ? En ese caso, se deletrea:

 item.price == 19.99 

No se tienen en cuenta los gustos, pero ningún desarrollador regular de Python diría que es menos legible que tu versión.

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

 item.price_should_equal(19.99) 

o esto:

 item.should_equal('price', 19.99) 

y definir métodos apropiados en la clase o superclases de los ítems

Parece que lo que realmente querías escribir es:

 assert item.price == 19.99 

(Por supuesto, comparar los flotadores para la igualdad, o usar los flotadores para los precios, es una mala idea , por lo que escribiría assert item.price == Decimal(19.99) o la clase numérica que estuviera usando para el precio).

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

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 de decoración 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 lo interno pero funciona.

Aquí está la fuente:

https://github.com/mdwhatcott/pyspecs

También está en PyPI bajo pyspecs.