你能在 Python 中的核心类型上猴子修补方法吗?

发布于 2024-07-06 16:37:07 字数 637 浏览 5 评论 0原文

Ruby 可以向 Number 类和其他核心类型添加方法来获得如下效果:

1.should_equal(1)

但 Python 似乎无法做到这一点。 这是真的? 如果是这样,为什么? 这是否与type无法修改有关?

我不想讨论猴子修补的不同定义,而是只想关注上面的示例。 正如你们几个人的回答,我已经得出结论,这是不可能的。 但我想要更详细的解释为什么它不能完成,也许什么功能(如果在 Python 中可用)可以允许这样做。

回答你们中的一些人:我可能的原因想要做到这一点只是为了美观/可读性。

 item.price.should_equal(19.99)

这读起来更像英语,清楚地表明哪个是测试值,哪个是预期值,正如所假设的:

should_equal(item.price, 19.99)

这个概念是什么 Rspec 以及其他一些 Ruby 框架都是基于的。

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?

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

清醇 2024-07-13 16:37:07

你不能。 在Python中,C扩展模块(包括内置模块)中定义的所有数据(类、方法、函数等)都是不可变的。 这是因为 C 模块在同一进程中的多个解释器之间共享,因此对它们进行猴子修补也会影响同一进程中不相关的解释器。 (通过 C API 可以在同一进程中使用多个解释器,并且已经一些努力使它们可用于Python 级别。)

但是,Python 代码中定义的类可能会被猴子修补,因为它们对于该解释器来说是本地的。

No, you cannot. In Python, all data (classes, methods, functions, etc) defined in C extension modules (including builtins) are immutable. This is because C modules are shared between multiple interpreters in the same process, so monkeypatching them would also affect unrelated interpreters in the same process. (Multiple interpreters in the same process are possible through the C API, and there has been some effort towards making them usable at Python level.)

However, classes defined in Python code may be monkeypatched because they are local to that interpreter.

§普罗旺斯的薰衣草 2024-07-13 16:37:07

这里的猴子补丁到底是什么意思? 有几个略有不同的定义

如果您的意思是,“您可以在运行时更改类的方法吗?”,那么答案绝对是肯定的:如果

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

但是对于某些内置类,例如 intfloat,您无法执行此操作。 这些类的方法是用 C 实现的,为了使实现更容易、更高效,牺牲了某些抽象。

我不太清楚为什么你想要改变内置数字类的行为。 如果您需要改变它们的行为,请将它们子类化!

What exactly do you mean by Monkey Patch here? There are several slightly different definitions.

If you mean, "can you change a class's methods at runtime?", then the answer is emphatically yes:

class Foo:
  pass # dummy class

Foo.bar = lambda self: 42

x = Foo()
print x.bar()

If you mean, "can you change a class's methods at runtime and make all of the instances of that class change after-the-fact?" then the answer is yes as well. Just change the order slightly:

class Foo:
  pass # dummy class

x = Foo()

Foo.bar = lambda self: 42

print x.bar()

But you can't do this for certain built-in classes, like int or float. These classes' methods are implemented in C and there are certain abstractions sacrificed in order to make the implementation easier and more efficient.

I'm not really clear on why you would want to alter the behavior of the built-in numeric classes anyway. If you need to alter their behavior, subclass them!!

往日 2024-07-13 16:37:07

您可以做到这一点,但这需要一些技巧。 幸运的是,现在有一个名为“禁果”的模块,它使您能够非常简单地修补内置类型的方法。 您可以在 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)

即可! 还有一个“反向”功能可以删除修补的方法。

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.

潦草背影 2024-07-13 16:37:07
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")

玩得开心 ;)

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 ;)

小嗷兮 2024-07-13 16:37:07

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

也就是说,在您给出的具体示例中,我不认为在 item.price (或其他地方)中子类化 float 很可能被认为是 Pythonic 的东西去做。 我可以很容易想象有人决定向item添加price_should_equal()方法(如果这是主要用例); 如果人们正在寻找更通用的东西,也许使用命名参数来使预期含义更清晰可能更有意义,就像 in

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

或类似的东西。 虽然有点冗长,但毫无疑问可以改进。 与 Ruby 风格的猴子修补相比,这种方法的一个可能优点是 should_equal() 可以轻松地对任何类型执行比较,而不仅仅是 intfloat。 但也许我太关注你碰巧提供的特定示例的细节了。

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.

浅紫色的梦幻 2024-07-13 16:37:07

你不能在 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)

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)
和我恋爱吧 2024-07-13 16:37:07

如果你真的真的想在Python中做一个猴子补丁,你可以使用“import foo as bar”技术进行(某种)黑客攻击。

如果您有一个类(例如 TelnetConnection),并且想要扩展它,请在单独的文件中将其子类化,并将其命名为 TelnetConnectionExtended 之类的名称。

然后,在代码顶部,您通常会说:

import TelnetConnection

将其更改为:

import TelnetConnectionExtended as TelnetConnection

然后在代码中引用 TelnetConnection 的所有地方实际上都会引用 TelnetConnectionExtended。

遗憾的是,这假设您有权访问该类,并且“as”仅在该特定文件中运行(它不是全局重命名),但我发现它有时很有用。

If you really really really want to do a monkey patch in Python, you can do a (sortof) hack with the "import foo as bar" technique.

If you have a class such as TelnetConnection, and you want to extend it, subclass it in a separate file and call it something like TelnetConnectionExtended.

Then, at the top of your code, where you would normally say:

import TelnetConnection

change that to be:

import TelnetConnectionExtended as TelnetConnection

and then everywhere in your code that you reference TelnetConnection will actually be referencing TelnetConnectionExtended.

Sadly, this assumes that you have access to that class, and the "as" only operates within that particular file (it's not a global-rename), but I've found it to be useful from time to time.

赢得她心 2024-07-13 16:37:07

下面是实现 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)

Here's an example of implementing item.price.should_equal, although I'd use Decimal instead of float in a real program:

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)
梦里南柯 2024-07-13 16:37:07

看来您真正想写的是:(

assert item.price == 19.99

当然,比较浮点数是否相等,或使用浮点数进行价格,是 一个坏主意,因此您可以编写 assert item.price == Decimal(19.99) 或您使用的任何数字类价格。)

您还可以使用测试框架,例如 py.test获取有关测试中失败断言的更多信息。

It seems what you really wanted to write is:

assert item.price == 19.99

(Of course comparing floats for equality, or using floats for prices, is a bad idea, so you'd write assert item.price == Decimal(19.99) or whatever numeric class you were using for the price.)

You could also use a testing framework like py.test to get more info on failing asserts in your tests.

北方的韩爷 2024-07-13 16:37:07

不,但是您有 UserDict UserString 和 UserList ,它们正是考虑到这一点而制作的。

如果你用谷歌搜索,你会找到其他类型的示例,但这是内置的。

一般来说,猴子补丁在 Python 中的使用比在 Ruby 中少。

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.

一绘本一梦想 2024-07-13 16:37:07

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)

并在项目的类或超类上定义适当的方法。

What does should_equal do? Is it a boolean returning True or False? In that case, it's spelled:

item.price == 19.99

There's no accounting for taste, but no regular python developer would say that's less readable than your version.

Does should_equal instead set some sort of validator? (why would a validator be limited to one value? Why not just set the value and not update it after that?) If you want a validator, this could never work anyway, since you're proposing to modify either a particular integer or all integers. (A validator that requires 18.99 to equal 19.99 will always fail.) Instead, you could spell it like this:

item.price_should_equal(19.99)

or this:

item.should_equal('price', 19.99)

and define appropriate methods on item's class or superclasses.

缪败 2024-07-13 16:37:07

不,遗憾的是您无法在运行时扩展 C 中实现的类型。

您可以对 int 进行子类化,尽管这很重要,但您可能必须重写 __new__

您还存在语法问题:

1.somemethod()  # invalid

但是

(1).__eq__(1)  # valid

No, sadly you cannot extend types implemented in C at runtime.

You can subclass int, although it is non-trivial, you may have to override __new__.

You also have a syntax issue:

1.somemethod()  # invalid

However

(1).__eq__(1)  # valid
丿*梦醉红颜 2024-07-13 16:37:07

这是我制作自定义字符串/int/float...等的方法。 方法:

class MyStrClass(str):

    def __init__(self, arg: str):
        self.arg_one = arg

    def my_str_method(self):
        return self.arg_one

    def my_str_multiple_arg_method(self, arg_two):
        return self.arg_one + arg_two

class MyIntClass(int):

    def __init__(self, arg: int):
        self.arg_one = arg

    def my_int_method(self):
        return self.arg_one * 2


myString = MyStrClass("StackOverflow")
myInteger = MyIntClass(15)

print(myString.count("a"))  # Output: 1
print(myString.my_str_method())  # Output: StackOverflow
print(myString.my_str_multiple_arg_method(" is cool!"))  # Output: StackOverflow is cool!
print(myInteger.my_int_method())  # Output: 30

这可能不是最好的解决方案,但效果很好。

Here is how I made custom string/int/float...etc. methods:

class MyStrClass(str):

    def __init__(self, arg: str):
        self.arg_one = arg

    def my_str_method(self):
        return self.arg_one

    def my_str_multiple_arg_method(self, arg_two):
        return self.arg_one + arg_two

class MyIntClass(int):

    def __init__(self, arg: int):
        self.arg_one = arg

    def my_int_method(self):
        return self.arg_one * 2


myString = MyStrClass("StackOverflow")
myInteger = MyIntClass(15)

print(myString.count("a"))  # Output: 1
print(myString.my_str_method())  # Output: StackOverflow
print(myString.my_str_multiple_arg_method(" is cool!"))  # Output: StackOverflow is cool!
print(myInteger.my_int_method())  # Output: 30

It's maybe not the best solution, but it works just fine.

懒猫 2024-07-13 16:37:07

不,你不能在 Python 中做到这一点。 我认为这是一件好事。

No, you can't do that in Python. I consider it to be a good thing.

高速公鹿 2024-07-13 16:37:07

以下是我如何实现 .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

它也在 PyPI 上的 pyspecs 下。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文