【问题标题】:How the method resolution and invocation works internally in Python?方法解析和调用如何在 Python 内部工作?
【发布时间】:2010-10-25 12:52:59
【问题描述】:

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

确实,Python 中的 Python 方法解析可能比 Java 中的慢。 什么是后期绑定?

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

【问题讨论】:

标签: java python


【解决方案1】:

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

foo.bar()

method = foo.bar
method()

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

首先在obj的实例字典中查找“attr”,然后在方法中查找obj类的实例字典及其父类的字典“attr”的解析顺序。

通常,如果在实例上找到值,则返回该值。但是,如果对类的查找产生一个同时具有 __get__ 和 __set__ 方法的值(准确地说,如果对值类和父类的字典查找具有这两个键的值),那么类属性被视为某种东西称为“数据描述符”。这意味着调用该值的 __get__ 方法,传入发生查找的对象并返回该值的结果。如果类属性未找到或不是数据描述符,则返回实例字典中的值。

如果实例字典中没有值,则返回类查找中的值。除非它恰好是“非数据描述符”,即它具有 __get__ 方法。然后调用 __get__ 方法并返回结果值。

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

如果在实例及其类层次结构中没有找到值,并且 obj 的类具有 __getattr__ 方法,则调用该方法。

以下显示了用 Python 编码的算法,有效地完成了 getattr() 函数的工作。 (不包括任何漏掉的错误)

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__ 方法被调用并返回一个“绑定方法”对象。绑定方法只是函数对象的一个​​小包装器,它存储查找函数的对象,并在调用时将该对象添加到参数列表(通常用于方法的函数 self 论据是)。

这里有一些说明性代码:

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 算法。这里解释得太复杂了,有兴趣的可以看this article。除非你正在做一些非常时髦的继承层次结构(你不应该这样做),否则知道查找顺序是从左到右,深度优先,并且在搜索该类之前搜索一个类的所有子类就足够了。

【讨论】:

  • 很好的答案和示例代码的 +1。能谈谈 is_descriptor 和 is_data_descriptor 的用途吗?
【解决方案2】:

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

当在实例命名空间 (dict) 中找不到名称时,python 会按照方法解析顺序 (MRO) 查找类,然后查找基类。

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

您可以使用dis 模块来看看它是如何在字节码中发生的。

简单示例:

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)

打印出来的:

  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        

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

【讨论】:

    【解决方案3】:

    确实是python方法 Python 中的分辨率可能会变慢 在Java中。什么是后期绑定?

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

    相比之下,Python 绑定名称是“迟到的”。方法解析发生在运行时:解释器只是尝试找到具有正确签名的引用Foo 方法,如果不存在,则会发生运行时错误。

    有什么区别 这两个中的反射机制 语言?

    动态语言往往比静态语言具有更好的反射功能,而 Python 在这方面非常强大。尽管如此,Java 还是有很多方法可以了解类和方法的内部结构。尽管如此,您还是无法避开 Java 的冗长;与在 Python 中相比,您将在 Java 中编写更多代码来执行相同的操作。请参阅java.lang.reflect API。

    【讨论】:

      猜你喜欢
      • 2022-11-21
      • 1970-01-01
      • 2021-02-20
      • 1970-01-01
      • 2021-12-03
      • 1970-01-01
      • 2022-12-07
      • 1970-01-01
      • 2020-03-18
      相关资源
      最近更新 更多