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
所以可能有更简单的方法。
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
所以可能有更简单的方法。