关于java:方法解析和调用如何在Python内部工作?

How the method resolution and invocation works internally in Python?

方法调用如何在Python中工作?我的意思是,Python虚拟机如何解释它。

在Python中,Python方法的分辨率可能在Java中较慢。什么是后期绑定?

这两种语言的反射机制有什么不同?在哪里可以找到解释这些方面的好资源?


python中的方法调用由两个不同的可分离步骤组成。首先完成属性查找,然后调用该查找的结果。这意味着以下两个片段具有相同的语义:

1
2
3
4
foo.bar()

method = foo.bar
method()

python中的属性查找是一个相当复杂的过程。假设我们正在对象obj上查找名为attr的属性,这在python代码中意味着以下表达式:obj.attr

首先搜索obj实例字典中的"attr",然后按方法解析顺序搜索obj类的实例字典及其父类的字典中的"attr"。

通常,如果在实例上找到一个值,则返回该值。但是,如果对类的查找导致同时具有"get"和"set"方法的值(确切地说,如果对值类和父类的字典查找同时具有这两个键的值),则类属性被视为"数据描述符"。这意味着调用该值的uu get_uuu方法,传递查找发生的对象,并返回该值的结果。如果找不到class属性或该属性不是数据描述符,则返回实例字典中的值。

如果实例字典中没有值,则返回类查找的值。除非它恰好是一个"非数据描述符",也就是说,它有"get"方法。然后调用uu get_uuu方法并返回结果值。

还有一种特殊情况,如果obj恰好是一个类(一个类型类型的实例),那么如果它是一个描述符,那么也会检查实例值,并相应地调用它。

如果在实例及其类层次结构上找不到值,并且obj的类有一个 getattr uuuu方法,则调用该方法。

下面显示了用python编码的算法,有效地执行了getattr()函数将要执行的操作。(不包括任何潜入的错误)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
NotFound = object() # A singleton to signify not found values

def lookup_attribute(obj, attr):
    class_attr_value = lookup_attr_on_class(obj, attr)

    if is_data_descriptor(class_attr_value):
        return invoke_descriptor(class_attr_value, obj, obj.__class__)

    if attr in obj.__dict__:
        instance_attr_value = obj.__dict__[attr]
        if isinstance(obj, type) and is_descriptor(instance_attr_value):
            return invoke_descriptor(instance_attr_value, None, obj)
        return instance_attr_value

    if class_attr_value is NotFound:
        getattr_method = lookup_attr_on_class(obj, '__getattr__')
        if getattr_method is NotFound:
            raise AttributeError()
        return getattr_method(obj, attr)

    if is_descriptor(class_attr_value):
        return invoke_descriptor(class_attr_value, obj, obj.__class__)

    return class_attr_value

def lookup_attr_on_class(obj, attr):
    for parent_class in obj.__class__.__mro__:
        if attr in parent_class.__dict__:
            return parent_class.__dict__[attr]
    return NotFound

def is_descriptor(obj):
    if lookup_attr_on_class(obj, '__get__') is NotFound:
        return False
    return True

def is_data_descriptor(obj):
    if not is_descriptor(obj) or lookup_attr_on_class(obj, '__set__') is NotFound :
        return False
    return True

def invoke_descriptor(descriptor, obj, cls):
    descriptormethod = lookup_attr_on_class(descriptor, '__get__')
    return descriptormethod(descriptor, obj, cls)

所有这些描述符的废话与你所要求的方法调用有什么关系?实际上,函数也是对象,它们恰好实现了描述符协议。如果属性查找在类上找到一个函数对象,则调用它的get方法并返回一个"bound method"对象。绑定方法只是围绕函数对象的一个小包装,它存储函数被查找到的对象,当被调用时,将该对象预处理到参数列表中(对于用于方法的函数,自变量通常是这样)。

下面是一些说明性代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Function(object):
    def __get__(self, obj, cls):
        return BoundMethod(obj, cls, self.func)
    # Init and call added so that it would work as a function
    # decorator if you'd like to experiment with it yourself
    def __init__(self, the_actual_implementation):
        self.func = the_actual_implementation
    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

class BoundMethod(object):
    def __init__(self, obj, cls, func):
        self.obj, self.cls, self.func = obj, cls, func
    def __call__(self, *args, **kwargs):
        if self.obj is not None:
             return self.func(self.obj, *args, **kwargs)
        elif isinstance(args[0], self.cls):
             return self.func(*args, **kwargs)
        raise TypeError("Unbound method expects an instance of %s as first arg" % self.cls)

对于方法解析顺序(在python的例子中,这实际上意味着属性解析顺序),python使用来自dylan的c3算法。这里的解释太复杂了,如果您感兴趣,请参阅本文。除非您正在执行一些非常时髦的继承层次结构(您不应该这样做),否则就足以知道查找顺序是从左到右、深度优先,并且在搜索类之前搜索类的所有子类。


名称(方法、函数、变量)都是通过查看命名空间来解析的。名称空间在cpython中实现为dict(散列映射)。

当在实例名称空间(dict中找不到名称时,python按照方法解析顺序(mro)依次为类和基类。

所有的解析都是在运行时进行的。

您可以使用dis模块来查看字节码中的情况。

简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import dis
a = 1

class X(object):
    def method1(self):
        return 15

def test_namespace(b=None):
    x = X()
    x.method1()
    print a
    print b

dis.dis(test_namespace)

印刷品:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  9           0 LOAD_GLOBAL              0 (X)
              3 CALL_FUNCTION            0
              6 STORE_FAST               1 (x)

 10           9 LOAD_FAST                1 (x)
             12 LOAD_ATTR                1 (method1)
             15 CALL_FUNCTION            0
             18 POP_TOP            

 11          19 LOAD_GLOBAL              2 (a)
             22 PRINT_ITEM          
             23 PRINT_NEWLINE      

 12          24 LOAD_FAST                0 (b)
             27 PRINT_ITEM          
             28 PRINT_NEWLINE      
             29 LOAD_CONST               0 (None)
             32 RETURN_VALUE

所有LOAD都是命名空间查找。


It's true that the python method
resolution could be slower in Python
that in Java. What is late binding?

后期绑定描述了一种策略,即特定语言的解释器或编译器如何决定如何将标识符映射到一段代码。例如,考虑用c_写obj.Foo()。编译时,编译器试图找到被引用的对象,并插入对运行时将调用的Foo方法位置的引用。所有这些方法解析都发生在编译时;我们说名称是"早期"绑定的。

相反,python绑定名称"late"。方法解析发生在运行时:解释器只是简单地试图找到具有正确签名的引用Foo方法,如果没有,则会发生运行时错误。

What are the differences on the
reflection mechanism in these two
languages?

动态语言往往比静态语言具有更好的反射功能,而Python在这方面非常强大。不过,Java有相当广泛的方法来获取类和方法的内部结构。然而,你不能绕过Java的冗长,你将编写更多的代码来完成与Java中相同的事情。参见java.lang.reflectAPI。