Python方法包装器类型?

时间:2012-05-01 17:53:02

标签: types python-3.x

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类。类的方法的所有“默认实现”都只是这种类型的实例吗?

3 个答案:

答案 0 :(得分:23)

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

  

在Python 3000中,已删除未绑定方法的概念,表达式“A.spam”返回普通函数对象。事实证明,第一个参数必须是A的一个实例的限制在诊断问题时很少有用,并且经常成为高级用法的障碍 - 有些人称之为“鸭子打字自我”,这似乎是一个恰当的名称。   (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行)。包装器用于为方法提供上述功能。

答案 1 :(得分:18)

CPython似乎使用类型<method-wrapper ..>来实现在C代码中实现的方法。基本上该类型不包含另一种方法。相反,它将C实现的函数包装为绑定方法。这样<method-wrapper>就像<bound-method>一样,只不过它是用C实现的。

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

  • <slot wrapper>其中(至少)包装了C实现的函数。在CPython 2中表现得像<unbound method>(至少有时候)或在CPython 3中表现为<function>
  • <method-wrapper>其中包含C实现的函数作为绑定方法。此类型的实例具有__self__属性___,在调用它时将其用作第一个参数。

如果您有<slot wrapper>,则可以将其绑定到__get__的对象,以获得<method-wrapper>

# 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__来获取<bound method><method-wrapper>。注意这两种类型的__get__只会返回self。

Python 3

CPython 3中的object类型具有__ne____eq__以及任何其他比较运算符的C实现。因此,object.__ne__会为此运算符返回<slot wrapper>。同样,object().__ne__会返回<method-wrapper>,可用于比较此对象。

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

Python 2(未询问但已回答)

Python 2在这里表现出很大的不同。因为我们有未绑定方法的概念。要求您使用正确的第一个参数类型调用它们,<slot wrapper><method-wrapper>都有__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的一些不那么幸运的实现细节。

答案 2 :(得分:0)

下面是我的变量检查例程中的一些测试代码。定义的类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__,因此它通过__repr__默认为基类object上的method-wrapper并提供默认输出。 __str__被定义(因此是方法),因此测试hasattr(t5.__str__, '__code__')的结果为True。