Let's use as example the attribute-tree getName().attr.full_name()[0]
.
First, we need to create a dummy object that has this tree:
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)
To create a Dummy
object, you must pass a value to its constructor. This value will be returnied when acessing the attribute-tree:
obj = Dummy(3.14)
print(obj.getName().attr.full_name()[0])
# Outputs 3.14
We'll only use Dummy
to demonstrate that our code is working. I'm assuming you already have an object with this attribute-tree.
Now, you can use the ast
module to parse the getter-string. In this case, I'm considering that the getter-string only contains properties, methods and indexes:
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
Now, let's create a Dummy
object and see if, when calling parse
with the attribute-tree given, it'll return the value passed in its constructor:
obj = Dummy(2.71)
print(parse(obj, 'getName().attr.full_name()[0]'))
# Outputs 2.71
So the parse
function is able to correctly parse the given attribute-tree.
I'm not familiar with ast
so there may be an easier way to do that.