Can you monkey patch methods on core types in Pyth

2019-01-01 12:55发布

Ruby can add methods to the Number class and other core types to get effects like this:

1.should_equal(1)

But it seems like Python cannot do this. Is this true? And if so, why? Does it have something to do with the fact that type can't be modified?

Update: Rather than talking about different definitions of monkey patching, I would like to just focus on the example above. I have already concluded that it cannot be done as a few of you have answered. But I would like a more detailed explanation of why it cannot be done, and maybe what feature, if available in Python, would allow this.

To answer some of you: The reason I might want to do this is simply aesthetics/readability.

 item.price.should_equal(19.99)

This reads more like English and clearly indicates which is the tested value and which is the expected value, as supposed to:

should_equal(item.price, 19.99)

This concept is what Rspec and some other Ruby frameworks are based on.

14条回答
永恒的永恒
2楼-- · 2019-01-01 13:25
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")

Have fun ;)

查看更多
初与友歌
3楼-- · 2019-01-01 13:25

Python's core types are immutable by design, as other users have pointed out:

>>> 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'

You certainly could achieve the effect you describe by making a subclass, since user-defined types in Python are mutable by default.

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

There's no need to make the MyInt subclass public, either; one could just as well define it inline directly in the function or method that constructs the instance.

There are certainly a few situations where Python programmers who are fluent in the idiom consider this sort of subclassing the right thing to do. For instance, os.stat() returns a tuple subclass that adds named members, precisely in order to address the sort of readability concern you refer to in your example.

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

That said, in the specific example you give, I don't believe that subclassing float in item.price (or elsewhere) would be very likely to be considered the Pythonic thing to do. I can easily imagine somebody deciding to add a price_should_equal() method to item if that were the primary use case; if one were looking for something more general, perhaps it might make more sense to use named arguments to make the intended meaning clearer, as in

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

or something along those lines. It's a bit verbose, but no doubt it could be improved upon. A possible advantage to such an approach over Ruby-style monkey-patching is that should_equal() could easily perform its comparison on any type, not just int or float. But perhaps I'm getting too caught up in the details of the particular example that you happened to provide.

查看更多
旧时光的记忆
4楼-- · 2019-01-01 13:26

You can do this, but it takes a little bit of hacking. Fortunately, there's a module now called "Forbidden Fruit" that gives you the power to patch methods of built-in types very simply. You can find it at

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

or

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

With the original question example, after you write the "should_equal" function, you'd just do

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

and you're good to go! There's also a "reverse" function to remove a patched method.

查看更多
有味是清欢
5楼-- · 2019-01-01 13:27

You can't patch core types in python. However, you could use pipe to write a more human readable code:

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)
查看更多
像晚风撩人
6楼-- · 2019-01-01 13:32

Here's how I achieve the .should_something... behavior:

result = calculate_result('blah') # some method defined somewhere else

the(result).should.equal(42)

or

the(result).should_NOT.equal(41)

I included a decorator method for extending this behavior at runtime on a stand-alone method:

@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()

You have to know a bit about the internals but it works.

Here's the source:

https://github.com/mdwhatcott/pyspecs

It's also on PyPI under pyspecs.

查看更多
谁念西风独自凉
7楼-- · 2019-01-01 13:33

No but you have UserDict UserString and UserList which were made with exactly this in mind.

If you google you will find examples for other types, but this are builtin.

In general monkey patching is less used in Python than in Ruby.

查看更多
登录 后发表回答