Python - 从字符串执行 getter 链

Python - Execute getter chains from string

getter_string = 'getName().attr.full_name()[0]'

如何将上面给定的 getter 字符串应用到任何对象?

我需要一个函数 f 这样 f(obj, getter_string) 将 return f.getName().attr.full_name()[0]

我看过 Python Chain getattr as a string 但它似乎只适用于链接属性。我希望链接方法和索引。

我知道这可以通过编写一个能够仔细处理所有情况的解析器来完成,但是有更紧凑的方法吗?

可以安全地假设 getter 字符串中的方法现在没有任何参数,以防止这变得不必要的复杂。

让我们以属性树为例 getName().attr.full_name()[0]

首先,我们需要创建一个包含这棵树的虚拟对象:

class Dummy:
    def __init__(self, value):
        self.value = value
            
    class A:
        def __init__(self, value):
            self.value = value
        
        def full_name(self):
            return [self.value]
            
    class B:
        def __init__(self, value):
            self.value = value
            
        @property
        def attr(self):
            return Dummy.A(self.value)
            
    def getName(self):
        return Dummy.B(self.value)

要创建一个 Dummy 对象,您必须将一个值传递给它的构造函数。访问属性树时,此值将 returnied:

obj = Dummy(3.14)
print(obj.getName().attr.full_name()[0])
# Outputs 3.14

我们只会使用 Dummy 来证明我们的代码可以正常工作。我假设您已经有一个具有此属性树的对象。

现在,您可以使用 ast 模块来解析 getter 字符串。在这种情况下,我认为 getter-string 只包含属性、方法和索引:

import ast

def parse(obj, getter_str):
    # Store the current object for each iteration. For example,
    #    - in the 1st iteration, current_obj = obj
    #    - in the 2nd iteration, current_obj = obj.getName()
    #    - in the 3rd iteration, current_obj = obj.getName().attr
    current_obj = obj

    # Store the current attribute name. The ast.parse returns a tree that yields
    #    - a ast.Subscript node when finding a index access
    #    - a ast.Attribute node when finding a attribute (either property or method)
    #    - a ast.Attribute and a ast.Call nodes (one after another) when finding a method
    #
    # Therefore, it's not easy to distinguish between a method and a property.
    # We'll use the following approach for each node:
    #    1. if a node is a ast.Attribute, save its name in current_attr
    #    2. if the next node is a ast.Attribute, the current_attr is an attribute
    #    3. otherwise, if the next node is a ast.Call, the current_attr is a method
    current_attr = None

    # Parse the getter-string and return only
    #    - the attributes (properties and methods)
    #    - the callables (only methods)
    #    - the subscripts (index accessing)
    tree = reversed([node 
            for node in ast.walk(ast.parse('obj.' + getter_str))
            if isinstance(node, (ast.Attribute, ast.Call, ast.Subscript))])
            
    for node in tree:
        if isinstance(node, ast.Call):
            # Method accessing
            if current_attr is not None:
                current_obj = getattr(current_obj, current_attr)()
                current_attr = None
        
        elif isinstance(node, ast.Attribute):
            # Property or method accessing
            if current_attr is not None:
                current_obj = getattr(current_obj, current_attr)
                
            current_attr = node.attr
        
        elif isinstance(node, ast.Subscript):
            # Index accessing
            current_obj = current_obj[node.slice.value.value]
            
    return current_obj

现在,让我们创建一个 Dummy 对象,看看在使用给定的属性树调用 parse 时,它是否会 return 在其构造函数中传递的值:

obj = Dummy(2.71)
print(parse(obj, 'getName().attr.full_name()[0]'))
# Outputs 2.71

因此 parse 函数能够正确解析给定的属性树。

我不熟悉 ast 所以可能有更简单的方法。