getter_string = 'getName().attr.full_name()[0]'
How to apply the above given getter string to any object?
I need a function f
such that f(obj, getter_string)
would return f.getName().attr.full_name()[0]
I had a look at Python Chain getattr as a string but it seems to be only for chaining attributes. I wish to chain methods as well as indices.
I know this can be done by writing a parser which would handle all the cases carefully, but is there a more compact way of doing this?
Its safe to assume that the methods in the getter string won't have any parameters for now to prevent this from becoming unecessarily complex.
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.