2

I have a function (here called analyze_data) that takes an object and a string as input, and uses the string to "extract" data via getattr. This works fine when the data are in the "frist" level of attributes (data1). However, I don't know what I can pass to the function to access the data when they are in a lower level (data2). Passing a dotted string (as shown below) does not work.

class a:
    pass

instance1 = a()

# Create 1st data set
instance1.data1 = [1,2,3]

# Create 2nd data set
instance1.subdata = a()
instance1.subdata.data2 = [4,5,6]


def analyze_data(myvar, dataname):
    data = getattr(myvar, dataname)
    # Do something with data, e.g.:
    print(str(data[0]))
    
analyze_data(instance1, 'data1')
analyze_data(instance1, 'subdata.data2')

What is the best way to access data2 without changing the existing function analyze_data too much?

Dave
  • 171
  • 2
  • 13

4 Answers4

4

Instead of using getattr you should use attrgetter which allows you to specify using the dot notation. This does require a minor change to your function as shown below.

from operator import attrgetter

def analyze_data(myvar, dataname):
    fn = attrgetter(dataname)
    data = fn(myvar)
    # Do something with data, e.g.:
    print(str(data[0]))

analyze_data(instance1, 'data1')
1

analyze_data(instance1, 'subdata.data2')
4
gold_cy
  • 13,648
  • 3
  • 23
  • 45
1

functools.reduce is another option.

>>> from functools import reduce
>>> reduce(getattr, 'subdata.data2'.split('.'), instance1)
[4, 5, 6]
timgeb
  • 76,762
  • 20
  • 123
  • 145
1

One way to achieve what you want without changing the function at all is this:

class a:
pass

instance1 = a()

# Create 1st data set
instance1.data1 = [1,2,3]

# Create 2nd data set
instance1.subdata = a()
instance1.subdata.data2 = [4,5,6]


def analyze_data(myvar, dataname):
    data = getattr(myvar, dataname)
    # Do something with data, e.g.:
    print(str(data[0]))

analyze_data(instance1, 'data1')
analyze_data(instance1.subdata, 'data2')
  • Very good answer! The only reason I did not do it this way is because in reality my `analyze_data` is more complex and does not take `instance1` directly as in argument, but a list of instances. – Dave Jan 13 '22 at 09:57
0

You can recurse into the data by splitting the dotted path and following it:

def analyze_data(myvar, dataname):
    for dn in dataname.split('.'):
        myvar = getattr(myvar, dn)
    # Do something with data, e.g.:
    print(str(myvar[0]))

This keeps updating myvar until you are at the end of the path.

vaizki
  • 1,678
  • 1
  • 9
  • 12
  • 2
    The answer does not deserve to be downvoted. It explicitly demonstartes the logic of the solution. That's how `attrgetter` does the job. –  Jan 12 '22 at 13:01
  • 1
    I tried to follow the brief and not modify the function "too much" instead of using a library or clever one-liner to obfuscate it. – vaizki Jan 12 '22 at 13:04