ruby legend教學 - 你可以在Python中的核心類型上修補補丁方法嗎?





matplotlib文字 matplotlib放大 (13)


這是一個實現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)

Ruby可以向Number類和其他核心類型添加方法來獲得這樣的效果:

1.should_equal(1)

但似乎Python無法做到這一點。 這是真的? 如果是這樣,為什麼? 是否與類型無法修改的事實有關?

更新:我不想談論猴子修補的不同定義,而是只關注上面的例子。 我已經得出結論,由於你們中的一些人已經回答,所以無法完成。 但是我想要更詳細地解釋為什麼不能這樣做,也許在Python中有什麼功能允許這樣做。

回答你們中的一些人:我可能想要這樣做的原因只是美學/可讀性。

 item.price.should_equal(19.99)

這更像是英語,並清楚地表明哪個是測試值,哪個是預期值,如下所示:

should_equal(item.price, 19.99)

這個概念就是Rspec和其他一些Ruby框架所基於的。




should_equal做什麼? 它是一個返回TrueFalse的布爾值嗎? 在這種情況下,拼寫:

item.price == 19.99

沒有考慮到品味,但沒有普通的python開發人員會說它的可讀性低於你的版本。

should_equal會設置某種驗證器嗎? (為什麼驗證器只能限制為一個值?為什麼不設置值而不在之後更新它?)如果你想要一個驗證器,這無論如何都無法工作,因為你提議修改一個特定的整數或全部整數。 (需要18.99等於19.99驗證器總是會失敗。)相反,你可以拼寫它:

item.price_should_equal(19.99)

或這個:

item.should_equal('price', 19.99)

並在item的類或超類上定義適當的方法。




你可以做到這一點,但它需要一點點黑客攻擊。 幸運的是,現在有一個名為“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)

你很高興去! 還有一個“反向”功能來刪除修補方法。




你不能。 在Python中,C擴展模塊(包括內置函數)中定義的所有數據(類,方法,函數等)都是不可變的。 這是因為C模塊在同一進程中的多個解釋器之間共享,因此monkeypatching它們也會影響同一進程中不相關的解釋器。

但是,Python代碼中定義的類可能是monkeypatched,因為它們是該解釋器的本地。




如果你真的真的想在Python中做一個猴子補丁,你可以使用“import foo as bar”技術進行(sortof)hack。

如果你有一個類,如TelnetConnection,並且你想擴展它,將它子類化為一個單獨的文件,並稱之為TelnetConnectionExtended。

然後,在代碼的頂部,您通常會說:

import TelnetConnection

改變是:

import TelnetConnectionExtended as TelnetConnection

然後你的代碼中你引用的所有TelnetConnection實際上都會引用TelnetConnectionExtended。

遺憾的是,這假設您可以訪問該類,並且“as”僅在該特定文件中運行(它不是全局重命名),但我發現它不時有用。




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



其他用戶指出,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 (或其他地方)中item.price float會很可能被認為是Pythonic要做的事情。 我可以很容易地想像有人決定在price_should_equal()添加price_should_equal()方法,如果這是主要的用例; 如果一個人正在尋找更一般的東西,也許使用命名參數使意圖更明確的意義可能更有意義,如

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

或類似的規定。 它有點冗長,但毫無疑問它可以改進。 這種方法相對於Ruby風格的猴子修補的一個可能的優點是, should_equal()可以很容易地在任何類型上執行它的比較,而不僅僅是intfloat 。 但也許我對你碰巧提供的特定例子的細節太過了解。




不,你不能用Python做到這一點。 我認為這是件好事。




這是我如何實現.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上。




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

玩的開心 ;)




不,遺憾的是,您無法在運行時擴展在C中實現的類型。

你可以__new__ int,雖然它是非平凡的,你可能必須覆蓋__new__

您還有語法問題:

1.somemethod()  # invalid

然而

(1).__eq__(1)  # valid



看來你真正想寫的是:

assert item.price == 19.99

(當然比較浮點數是否相等,或者使用浮點數來表示價格,這是一個壞主意 ,所以你要編寫assert item.price == Decimal(19.99)或者你用於價格的任何數字類。)

您還可以使用py.test類的測試框架來獲取有關測試中失敗斷言的更多信息。




Python中的靜態方法?

是否有可能在Python中有靜態方法,所以我可以在不初始化類的情況下調用它們,如:

ClassName.StaticMethod()

是的,可以像這樣創建靜態方法(儘管使用下劃線代替方法中的CamelCase會使用更多Pythonic ):

class ClassName(object):

    @staticmethod
    def static_method(kwarg1=None):
        '''return a value that is a function of kwarg1'''

以上使用裝飾器語法。 此語法等同於

class ClassName(object):

    def static_method(kwarg1=None):
        '''return a value that is a function of kwarg1'''

    static_method = staticmethod(static_method)

這可以像你所描述的那樣使用:

ClassName.static_method()

一個靜態方法的內置示例是Python 3中的str.maketrans() ,它是Python 2中string模塊的一個函數。

另一個可以用於描述的選項是classmethod ,不同之處在於classmethod將類作為隱式第一個參數獲取,如果是子類,則它將獲取該子類作為隱式第一個參數。

class ClassName(object):

    @classmethod
    def class_method(cls, kwarg1=None):
        '''return a value that is a function of the class and kwarg1'''

請注意, cls不是第一個參數的必需名稱,但大多數有經驗的Python編碼人員會認為如果您使用其他任何東西,它的操作會很糟糕。

這些通常用作替代構造函數。

new_instance = ClassName.class_method()

一個內置的例子是dict.fromkeys()

new_dict = dict.fromkeys(['key1', 'key2'])




python ruby programming-languages monkeypatching fluent-interface