Ruby可以向Number类和其他核心类型添加方法来获得这样的效果:
1.should_equal(1)
但似乎Python无法做到这一点。这是真的?如果是这样,为什么?是否与 type 无法修改的事实有关?
更新:我不想谈论猴子修补的不同定义,而是只关注上面的例子。我已经得出结论,由于你们中的一些人已经回答,所以无法做到。但是我想更详细地解释为什么不能这样做,也许是什么功能,如果在Python中可用,将允许这样做。
回答你们中的一些人:我可能想要这样做的原因只是美学/可读性。
item.price.should_equal(19.99)
这更像是英文,清楚地表明哪个是测试值,哪个是预期值,应该是:
should_equal(item.price, 19.99)
这个概念是Rspec和其他一些Ruby框架所基于的。
答案 0 :(得分:68)
但是,Python代码中定义的类可能是monkeypatched,因为它们是该解释器的本地类。
答案 1 :(得分:39)
Monkey Patch在这里究竟是什么意思?有several slightly different definitions。
如果你的意思是“你能在运行时更改类的方法吗?”,那么答案显然是肯定的:
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中实现,并且为了使实现更容易和更有效,牺牲了某些抽象。
我不太清楚为什么你想要改变内置数值类的行为。如果你需要改变他们的行为,请将它们子类化!!
答案 2 :(得分:29)
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")
玩得开心;)
答案 3 :(得分:23)
你可以这样做,但需要一点点黑客攻击。幸运的是,现在有一个名为“Forbidden Fruit”的模块,它可以让您非常简单地修补内置类型的方法。你可以在
找到它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)
你很高兴去!还有一个“反向”功能来删除修补方法。
答案 4 :(得分:13)
Python的核心类型在设计上是不可变的,正如其他用户所指出的那样:
>>> 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'
你当然可以通过创建子类来实现你所描述的效果,因为默认情况下Python中的用户定义类型是可变的。
>>> 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
那就是说,在你给出的具体例子中,我不相信float
(或其他地方)中的item.price
子类很可能被认为是Pythonic要做的事情。我可以轻易想象有人决定将price_should_equal()
方法添加到item
,如果这是主要用例的话;如果一个人正在寻找更一般的东西,也许使用命名参数使意图更明确的意义更有意义,如
should_equal(observed=item.price, expected=19.99)
或类似的东西。它有点冗长,但毫无疑问它可以改进。这种方法相对于Ruby式猴子修补的一个可能的优点是should_equal()
可以轻松地对任何类型进行比较,而不仅仅是int
或float
。但也许我对你碰巧提供的特定例子的细节太过了解。
答案 5 :(得分:7)
你不能在python中修补核心类型。 但是,您可以使用管道编写更易读的代码:
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 :(得分:4)
这是一个实现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)
答案 7 :(得分:3)
如果你真的真的想在Python中做一个猴子补丁,你可以用“import foo as bar”技术做一个(sortof)hack。
如果您有一个类,如TelnetConnection,并且您想扩展它,请将其子类化为一个单独的文件,并将其称为TelnetConnectionExtended。
然后,在代码的顶部,您通常会说:
import TelnetConnection
将其更改为:
import TelnetConnectionExtended as TelnetConnection
然后您的代码中引用TelnetConnection的所有地方实际上都会引用TelnetConnectionExtended。
可悲的是,这假设您可以访问该类,并且“as”仅在该特定文件中运行(它不是全局重命名),但我发现它不时有用。 / p>
答案 8 :(得分:2)
没有,但你有UserDict UserString和UserList,这是在考虑到这一点。
如果你谷歌你会找到其他类型的例子,但这是内置的。
一般来说,猴子补丁在Python中的使用比在Ruby中少用。
答案 9 :(得分:1)
不,你不能用Python做到这一点。我认为这是件好事。
答案 10 :(得分:1)
should_equal
做什么?它是一个返回True
或False
的布尔值吗?在那种情况下,拼写是:
item.price == 19.99
没有考虑到品味,但没有普通的python开发人员会说它的可读性低于你的版本。
should_equal
是否设置了某种验证器? (为什么验证器会被限制为一个值?为什么不设置值而不在之后更新它?)如果你想要一个验证器,那么无论如何这都无法工作,因为你建议修改一个特定的整数或全部整数。 (要求18.99
等于19.99
的验证器将始终失败。)相反,您可以这样拼写:
item.price_should_equal(19.99)
或者这个:
item.should_equal('price', 19.99)
并在项目的类或超类上定义适当的方法。
答案 11 :(得分:1)
你真正想写的似乎是:
assert item.price == 19.99
(当然比较浮点数是否相等,或者使用浮点数来定价,是a bad idea,所以你要写assert item.price == Decimal(19.99)
或者你用来代价的任何数字类。)
您还可以使用像py.test这样的测试框架来获取有关测试中失败断言的更多信息。
答案 12 :(得分:1)
不,遗憾的是,您无法在运行时扩展在C中实现的类型。
您可以将int子类化,虽然它非常重要,但您可能必须覆盖__new__
。
您还有语法问题:
1.somemethod() # invalid
然而
(1).__eq__(1) # valid
答案 13 :(得分:0)
以下是我如何实现.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
它也在pyspecs下的PyPI上。