1

I need to create a script that allows you to access object atributes dynamically from getattr() built-in function, but I'm having lots of troubles when trying to access the object methods, here is an example:

class Dog():
    def __init__(self):
        self.age = 12
        self.name = 'Bobby'
    
    def eat(self):
        print("I'm eating!")

If I then initialize the object and try to access their attributes:

mydog = Dog()

res = getattr(mydog, 'age')
print(res)
#--> return: 12

But if I try to access the eat method:

mydog = Dog()

res = getattr(mydog, 'eat()')
print(res)
#--> return: AttributeError: 'Dog' object has no attribute 'eat()'

I have already tried to write the method name without quotation marks, and the best solution that I found is to write the method without the parentheses and then access the function from res:

mydog = Dog()

res = getattr(mydog, 'eat')
res()
#--> return: I'm eating!

I could code a conditional that detects if res contains a variable or a function and call it in case it's a method, but is there a better solution to this?

Toribio
  • 3,963
  • 3
  • 34
  • 48
Stack
  • 1,028
  • 2
  • 10
  • 31
  • Because `'eat()'` isn't an attribute. `'eat'` is. What exactly are you even asking? – juanpa.arrivillaga Sep 14 '20 at 05:42
  • The attribute is called `eat` and it is a "bound method". If instead, you had `def eat(item): print("I am eating " + item)` then you could call `res('bone')` just like any other function. – Frank Yellin Sep 14 '20 at 05:43
  • It's not exactly what you want, but using the `@property` decorator will make it so that you can "retrieve" the results of `eat()` when you fetch the value. – Hampus Larsson Sep 14 '20 at 05:44

2 Answers2

2

Using a small one line condition that will detect it for you isn't bad and it's pretty short using callable:

attr = getattr(mydog, attr_name)
res = attr() if callable(attr) else attr

on Python3.8, you can even one line it:

res = attr() if callable(attr := getattr(mydog, attr_name)) else attr
Or Y
  • 2,088
  • 3
  • 16
1

Using the @property decorator on the method eat it will make it so that we can retrieve it without problems with getattr:

class Dog():
    def __init__(self):
        self.age = 12
        self.name = 'Bobby'

    @property
    def eat(self):
        return "I'm eating!"

mydog = Dog()
res1 = getattr(mydog, 'age')
res2 = getattr(mydog, 'name')
res3 = getattr(mydog, 'eat')
print(res1) #Output: 12
print(res2) #Output: Bobby
print(res3) #Output: I'm eating!

If we don't make eat into a property, then calling getattr(mydog, "eat") will return a reference to the method without calling it. However, when you set @property in a method, that will create a getter of eat that will be called when you try to call that attribute.

If we then want to extend the above, so that we can change the value of eat then we can also create a setter of eat:

class Dog():
    def __init__(self):
        self._eat = "I'm eating!"

    @property
    def eat(self):
        return self._eat
    
    @eat.setter
    def eat(self, eat_string):
        self._eat = eat_string

mydog = Dog()
print(mydog.eat) #Output: I'm eating!
mydog.eat = "Stop bothering me!"
print(mydog.eat) #Output: Stop bothering me!
Hampus Larsson
  • 3,050
  • 2
  • 14
  • 20