【问题标题】:Python method-wrapper type?Python方法包装器类型?
【发布时间】:2012-05-11 05:31:16
【问题描述】:

Python 3 中的方法包装器类型是什么?如果我这样定义一个类:

class Foo(object):
    def __init__(self, val):
        self.val = val
    def __eq__(self, other):
        return self.val == other.val

然后做:

Foo(42).__eq__

我明白了:

<bound method Foo.__eq__ of <__main__.Foo object at 0x10121d0>>

但如果我这样做(在 Python 3 中):

Foo(42).__ne__

我明白了:

<method-wrapper '__ne__' of Foo object at 0x1073e50>

什么是“方法包装器”类型?

编辑:抱歉更准确地说:class method-wrapper__ne__ 的类型,好像我一样:

>>> type(Foo(42).__ne__)
<class 'method-wrapper'>

__eq__ 的类型是:

>>> type(Foo(42).__eq__)
<class 'method'>

此外,method-wrapper 似乎是类上任何未定义的魔法方法的类型(因此,__le____repr____str__ 等如果未明确定义也将具有这种类型)。

我感兴趣的是 Python 如何使用 method-wrapper 类。类上方法的所有“默认实现”都只是这种类型的实例吗?

【问题讨论】:

    标签: types python-3.x


    【解决方案1】:

    这是因为 Python 3 中不存在“未绑定方法”。

    在 Python 3000 中,未绑定方法的概念已被移除,表达式“A.spam”返回一个普通的函数对象。事实证明,第一个参数必须是 A 的实例的限制对诊断问题几乎没有帮助,并且经常成为高级用法的障碍——有些人称其为“duck typing self”,这似乎是一个合适的名称。 (Source)

    在 Python 2.x 中,我们有绑定方法和未绑定方法。绑定方法被绑定到一个对象,这意味着当它被调用时,它将对象实例作为第一个变量传递(通常是self)。未绑定方法是指函数是一个方法,但没有它所属的实例 - 如果将对象实例以外的其他内容传递给该方法,则会引发错误。

    现在,在 3.x 中,这已更改。代替绑定/未绑定方法,当您请求对象的方法时,它返回函数对象,但包装在一个将实例变量传入的包装函数中。这样就无需区分绑定和未绑定方法- 绑定的方法被包装,未绑定的没有。

    澄清区别:

    2.x:

    a = A()
    f = A.method # f is an unbound method - you must pass an instance of `A` in to it as the first argument.
    f = a.method # f is a bound method, bound to the instance `a`.
    

    3.x:

    a = A()
    f = A.method # f is a function
    f = a.method # f is a wrapped function with it's first argument filled with `a`.
    

    a.method 可以认为是:

    def method-wrapper():
        A.method(a)
    

    有关这方面的更多信息,请查看 Guido 的博客 - the history of Python

    编辑:

    所以,这一切都适用的原因是,在这里,__ne__() 没有被覆盖 - 它处于默认状态,即身份检查 implemented in C(第 980 行)。包装器用于为方法提供上述功能。

    【讨论】:

    • 我不确定我是否关注,那么method-wrapper 类型是什么?它只是 Python 3 相当于一个未绑定的函数吗?但是你调用它的方式与调用常规包装函数的方式相同(如上面的 __ne__ 示例),所以我不明白。
    • 不,Python 3 中不存在未绑定的方法——这只是一个函数。 (未绑定的方法阻止您传入第一个参数,该参数不是该方法所来自的类的类型,但这被认为是人为的限制并被删除)。 Wrapped-method 是一个绑定方法——它知道它属于哪个实例,并且该方法被包装以提供该实例。
    • 抱歉,我仍然认为我的问题没有得到解答:method-wrapper 类型是什么?对于 __eq____ne__ 方法,类型不同,但它们都接收隐式的第一个参数。您是说method-wrapper 类型的方法是不知道它属于哪个实例的方法吗?但如果是这样的话,那么someinst.__ne__(otherinst) 是如何工作的(因为它绑定到了someinst 实例)?
    • @AdamParkin 我在最后添加了一点,试图更清楚为什么这是相关的。
    • @Marcin 是的,但由于答案中描述的原因,不在这种情况下。
    【解决方案2】:

    似乎&lt;method-wrapper ..&gt; 类型被 CPython 用于在 C 代码中实现的方法。基本上该类型不包装另一种方法。相反,它将 C 实现的函数包装为绑定方法。这样&lt;method-wrapper&gt;&lt;bound-method&gt; 完全一样,只是它是用C 实现的。

    在 CPython 中有两种与此相关的特殊类型。

    • &lt;slot wrapper&gt; 其中(至少)包装了一个 C 实现的函数。行为类似于 CPython 2 中的 &lt;unbound method&gt;(至少有时)或 CPython 3 中的 &lt;function&gt;
    • &lt;method-wrapper&gt; 将 C 实现的函数包装为绑定方法。这种类型的实例有一个__self__ 属性__,它在被调用时用作第一个参数。

    如果您有 &lt;slot wrapper&gt;,则将其绑定到带有 __get__ 的对象以获取 &lt;method-wrapper&gt;

    # returns a <slot_wrapper> on both CPython 2 and 3
    sw = object.__getattribute__  
    
    # returns a <method-wrapper>
    bound_method = sw.__get__(object()) 
    
    # In this case raises AttributeError since no "some_attribute" exists.
    bound_method("some_attribute")  
    

    您可以在 Python 中的任何类似函数的对象上调用 __get__ 以获取 &lt;bound method&gt;&lt;method-wrapper&gt;。请注意,这两种类型的 __get__ 都会简单地返回 self。

    Python 3

    CPython 3 中的object 类型具有__ne____eq__ 以及任何其他比较运算符的C 实现。因此object.__ne__ 为该运算符返回&lt;slot wrapper&gt;。同样object().__ne__ 返回一个&lt;method-wrapper&gt; 可以用来比较这个对象。

    由于你没有在你的类中定义__ne__,你会得到一个绑定方法(如&lt;method-wrapper&gt;),它是对象实例(包括派生实例)的C 实现函数。我敢打赌,这个 C 函数将检查您是否定义了任何 __eq__,调用它,然后 not 结果。

    Python 2(未询问但已回答)

    Python 2 的行为在这里明显不同。因为我们有未绑定方法的概念。这要求您使用正确的第一个参数类型调用它们,&lt;slot wrapper&gt;&lt;method-wrapper&gt; 都有一个 __objclass__,这是第一个参数必须是实例的类型。

    此外:Python 2 没有针对 object 类型的比较运算符的任何实现。因此object.__ne__ 不是比较对象的函数。更有趣的是,type 类型是 object 的元类,它确实有一个 C 实现的 __ne__ 运算符。因此,您从 object.__ne__ 获得了一个绑定方法,该方法将尝试将类型 object 与任何其他类型(或对象)进行比较。

    因此object().__ne__ 实际上会以AttributeError 失败,因为object 没有定义任何此类方法。鉴于object() == object() 确实有效(给出 False),我猜想 CPython 2 在解释器中有用于比较对象的特殊情况。 我们再次看到 CPython 3 已经清理了 Python 2 的一些不太幸运的实现细节。

    【讨论】:

    • 谢谢! &lt;slot wrapper&gt;&lt;method-wrapper&gt; 的对象是可调用对象还是描述符?
    • 两者都是可调用的,但只有 是一个描述符(即有一个__get__ 方法)。这与纯python-3 代码不同,其中types.MethodType 也是一个描述符。你这可能只是因为在 Python 2 上 types.MethodType 代表未绑定和绑定的方法,因此需要 __get__ 让您将未绑定的方法绑定到对象。
    • Slot-wrapper 和 method-wrapper 对象在 Python 3 上也有 __objclass__。另外,slot-wrapper 和 method-wrapper 仅用于 C 槽对应的方法;还有许多其他 C 方法类型,最突出的是方法描述符和内置函数或方法,方法描述符用于 list.append 等方法,而内置函数或方法用于 @ 等绑定方法987654369@(也适用于函数,如max)。
    【解决方案3】:

    下面是我的变量检查例程中的一些测试代码。定义类 Tst,它有一个__str__ 方法。要确定某个类的实例是否定义了__repr____str__,您可以测试hasattr(obj.__str__, '__code__')

    例子:

    class Tst(object):
        """ Test base class.
        """
        cl_var = 15
    
        def __init__(self, in_var):
            self.ins_var = in_var
    
        def __str__(self):
            # Construct to build the classname ({self.__class__.__name__}).
            # so it works for subclass too.
            result = f"{self.__class__.__name__}"
            result += f"(cl_var: {self.__class__.cl_var}, ins_var: {self.ins_var})"
            return result
    
        def show(self):
            """ Test method
            """
            print(self.ins_var)
    
    
    t5 = Tst(299)
    
    
    print('\n\n----- repr() ------')
    print(f"obj.repr(): {repr(t5)}")
    print(f"obj.repr.class type: {type(t5.__repr__.__class__)}")
    print(f"obj.repr.class.name: {t5.__repr__.__class__.__name__}")
    print(f"obj.__repr__ has code: {hasattr(t5.__repr__, '__code__')}")
    
    print('\n\n----- str() ------')
    print(f"obj.str(): {str(t5)}")
    print(f"obj.str.class type: {type(t5.__str__.__class__)}")
    print(f"obj.str.class.name: {t5.__str__.__class__.__name__}")
    print(f"obj.__str__ has code: {hasattr(t5.__str__, '__code__')}")
    

    返回:

    ----- repr() ------
    obj.repr(): <__main__.Tst object at 0x107716198>
    obj.repr.class type: <class 'type'>
    obj.repr.class.name: method-wrapper
    obj.__repr__ has code: False
    
    
    ----- str() ------
    obj.str(): Tst(cl_var: 15, ins_var: 299)
    obj.str.class type: <class 'type'>
    obj.str.class.name: method
    obj.__str__ has code: True
    

    由于__repr__ 没有在类上定义,它通过method-wrapper 默认为基类object 上的__repr__,并提供默认输出。 __str__ 已定义(因此是一个方法),因此测试 hasattr(t5.__str__, '__code__') 结果为 True。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-14
      • 1970-01-01
      相关资源
      最近更新 更多