你可以在Python的核心types猴子补丁方法?
Ruby可以将方法添加到Number类和其他核心types以获得如下效果:
1.should_equal(1)
但是,似乎python不能做到这一点。 这是真的? 如果是这样,为什么? 这是否与types无法修改的事实有关?
更新:而不是谈论猴子补丁的不同定义,我想只关注上面的例子。 我已经得出结论说,这是不可能的,因为你们中的一些人已经回答了。 但是我想要更详细地解释为什么它不能完成,也许什么function,如果在Python中可用,将允许这样做。
回答你们中的一些人:我可能想要这样做的原因只是美学/可读性。
item.price.should_equal(19.99)
阅读更像英文,并清楚地指出哪个是testing值,哪个是期望值,如下所示:
should_equal(item.price,19.99)
这个概念是Rspec和其他一些Ruby框架的基础。
Monkey Patch在这里的意思是什么? 有几个略有不同的定义 。
如果你的意思是“你能在运行时改变一个类的方法吗?”,那么答案是肯定的:
class Foo: pass # dummy class Foo.bar = lambda self: 42 x = Foo() print x.bar()
如果你的意思是“你可以在运行时改变一个类的方法,并且让这个类的所有实例都事后改变吗? 那么答案是肯定的。 稍微改变一下订单:
class Foo: pass # dummy class x = Foo() Foo.bar = lambda self: 42 print x.bar()
但是你不能为特定的内置类,如int
或float
。 这些类的方法在C中实现,为了使实现更容易,更高效,牺牲了某些抽象。
我不清楚你为什么要改变内置数字类的行为。 如果你需要改变他们的行为,他们的子类!
你不能。 在Python中,C扩展模块(包括内build函数)中定义的所有数据(类,方法,函数等)都是不可变的。 这是因为C模块是由同一个进程中的多个解释器共享的,所以对它们进行monkeypatching也会影响不相关的解释器在同一个进程中。
然而,在Python代码中定义的类可能是单一的,因为它们是解释器本地的。
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")
玩的开心 ;)
你可以做到这一点,但需要一点点的黑客攻击。 幸运的是,现在有一个名为“Forbidden Fruit”的模块,可以非常简单地为内置types的方法提供补丁。 你可以find它
http://clarete.github.io/forbiddenfruit/?goback=.gde_50788_member_228887816
要么
https://pypi.python.org/pypi/forbiddenfruit/0.1.0
用原来的问题的例子,在你写了“should_equal”函数之后,你就可以做了
from forbiddenfruit import curse curse(int, "should_equal", should_equal)
你很好走! 还有一个“反向”function来删除修补方法。
正如其他用户所指出的那样,Python的核心types是不可变的,
>>> 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'
您当然可以通过创build子类来达到您描述的效果,因为Python中的用户定义types默认是可变的。
>>> class MyInt(int): ... def frobnicate(self): ... print 'frobnicating %r' % self ... >>> five = MyInt(5) >>> five.frobnicate() frobnicating 5 >>> five + 8 13
没有必要公开MyInt
子类, 我们可以直接在构造实例的函数或方法中直接定义它。
当然,有些情况下,能熟练使用这个习惯用法的Python程序员会考虑这样做的子类是正确的。 例如, os.stat()
返回一个tuple
子类,它添加了名字成员,正好是为了解决你在示例中引用的那种可读性问题。
>>> import os >>> st = os.stat('.') >>> st (16877, 34996226, 65024L, 69, 1000, 1000, 4096, 1223697425, 1223699268, 1223699268) >>> st[6] 4096 >>> st.st_size 4096
也就是说,在你给出的具体例子中,我不相信item.price
(或其他地方)中的子类float
很可能被认为是Pythonic的事情。 我很容易想象有人决定添加一个price_should_equal()
方法到item
如果这是主要的用例; 如果有人在寻找更一般的东西,那么使用命名的参数来使意图的意义更清楚,或许更有意义
should_equal(observed=item.price, expected=19.99)
或沿着这些线路的东西。 这有点冗长,但毫无疑问可以改进。 对于Ruby风格的猴子补丁,这种方法的一个可能的好处是, should_equal()
可以很容易地在任何types上执行它的比较,而不仅仅是int
或者float
。 但是,也许我已经陷入了你碰巧提供的具体例子的细节中。
你不能在Python中修补核心types。 但是,您可以使用pipe编写更易读的代码:
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)
下面是一个实现item.price.should_equal
的例子,虽然我在实际程序中使用Decimal而不是float:
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)
如果你真的真的想在Python中做一个猴子补丁,你可以用“import foo as bar”技术做一个(一些)破解。
如果你有一个类如TelnetConnection,并且你想扩展它,在一个单独的文件中将其子类化,并将其称为类似于TelnetConnectionExtended的东西。
然后,在您的代码的顶部,您通常会说:
import TelnetConnection
改变是:
import TelnetConnectionExtended as TelnetConnection
然后在您的代码中的任何地方,您引用TelnetConnection实际上是引用TelnetConnectionExtended。
可悲的是,这个假定你有权访问那个类,而且“as”只在这个特定的文件中运行(这不是全局重命名),但是我发现它有时是有用的。
不,可悲的是你不能在运行时扩展在C中实现的types。
你可以__new__
int类,尽pipe它不重要,你可能必须重载__new__
。
你也有一个语法问题:
1.somemethod() # invalid
然而
(1).__eq__(1) # valid
不,你不能用Python做到这一点。 我认为这是一件好事。
不,但是你有UserDict UserString和UserList,这正是考虑到这一点。
如果你谷歌,你会发现其他types的例子,但这是内置的。
一般来说,Python中的猴子修补比Ruby中less。
should_equal
做什么的? 它是一个返回True
或False
的布尔值? 在这种情况下,拼写如下:
item.price == 19.99
没有味道的会计,但没有一个普通的Python开发人员会说这比你的版本更不可读。
should_equal
是否设置了某种validation器? (为什么一个validation器被限制为一个值?为什么不设置这个值,在那之后不更新呢?)如果你想要一个validation器,那么这个工作根本无法工作,因为你打算修改一个特定的整数或所有的整数。 (需要18.99
到19.99
validation器总是会失败。)相反,你可以这样拼写:
item.price_should_equal(19.99)
或这个:
item.should_equal('price', 19.99)
并在项目的类或超类上定义适当的方法。
看来你真的想写的是:
assert item.price == 19.99
(当然,比较浮动平等,或使用浮动价格,是一个坏主意 ,所以你会写价值assert item.price == Decimal(19.99)
或任何数字类)。
你也可以使用像py.test这样的testing框架来获取testing中失败的断言的更多信息。
以下是我如何实现.should_something …行为:
result = calculate_result('blah') # some method defined somewhere else the(result).should.equal(42)
要么
the(result).should_NOT.equal(41)
我包含一个装饰器方法,用于在独立方法的运行时扩展此行为:
@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()
你必须知道一些关于内部,但它的工作。
这是来源:
https://github.com/mdwhatcott/pyspecs
它也在PyPy下的pyspecs。