微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Python - 从字符串执行 getter 链

如何解决Python - 从字符串执行 getter 链

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

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

我需要一个函数 f,这样 f(obj,getter_string) 会返回 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 对象,您必须向其构造函数传递一个值。访问属性树时将返回此值:

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

我们将仅使用 Dummy 来证明我们的代码正在运行。我假设您已经有一个具有此属性树的对象。

现在,您可以使用 ast 模块来解析 getter-string。在这种情况下,我考虑 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 时,它是否会返回在其构造函数中传递的值:

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。