How to prevent AttributeError for undeclared variables and methods and fix calling __getattr__ method multiple times?

class Foo(object):
     def __init__(self):
         self.a = 1
         self.c = 0
     def __getattr__(self, name):
         self.c += 1
         print('getattribute')
         if hasattr(self, name) is False:
             print("No")
             return None
         return super(Foo, self).__getattr__(self, name)

fo = Foo()
print(fo.a)
print(fo.b)
print(fo.c)

      

Running the above programs prints "getattribute" and "no" multiple times. __getattr__

called multiple times. 333 to be precise. self.c

prints 333.

I want to achieve a class that does not throw an error if the class of the class or method is not declared in the class.

What is the possible reason for this?

+3


source to share


4 answers


hasattr

just tries to get the attribute and returns False

if it can't. Whether defined in Python 3 is an attempt to throw AttributeError

, and in Python 2 it is an attempt to throw any error. (This includes RecursionError

, which is why it fails after 333 calls. Python 2 is not a sane language, if possible upgrade to 3.)

Instead, you can return the alternative value to AttributeError

yourself:



def __getattr__(self, name):
    try:
        return super(Foo, self).__getattr__(self, name)
    except AttributeError:
        return None

      

This could potentially hide other AttributeError

s, but is difficult to avoid, simply by the nature of Python.

+6


source


hasattr is a shortcut to call getattr and sees if it raises an exception (which means the attribute doesn't exist) or not (which means it does exist)

cf: https://docs.python.org/3/library/functions.html#hasattr

getattr calls __getattr__, so you make a recursive call

I think a possible workaround would be to use:



name in self.__dict__

      

instead:

hasattr(self, name)

      

+3


source


This is because the launch hasattr(self, name)

calls self.__getattr__(name)

(aka getattr(self, name)

) - link .

So, when executed hasattr(self, name)

internally, __getattr__

it calls self.__getattr__(name)

, unwanted recursion occurs here.

I would fix this with

class Foo(object):
     def __init__(self):
         self.a = 1
         self.c = 0
     def __getattr__(self, name):
         self.__dict__['c'] += 1
         print('getattribute')
         try:
             return self.__dict__[name]
         except KeyError:
             print('No')
             return None

fo = Foo()
print(fo.a)
print(fo.b)
print(fo.c)

      

+1


source


The problem arises from print(fo.b)

. Since b

it is not defined as a member Foo

, fo.b

results in a call fo.__getattr__('b')

.

Then hasattr(self, name)

, the equivalent hasattr(fo, 'b')

calls itself gettatr(fo, 'b')

as stated in the hte documentation .

Hence there is infinite recursion resulting RecursionError

in Python 3.


Since getting fo.b

doesn't make sense if you know you do Foo

n't have a member b

, the first fix I would suggest is to define that member.

class Foo(object):
    def __init__(self):
        ...
        self.b = 1
        ...

      

Then your code outputs

1
1
0

      

A smarter fix would be checking if the argument name

passed __getattr__

is equal 'b'

, or depending on your needs other than 'a'

and 'c'

. In this situation, you can force the __getattr__

definition of the requested unreasonable item.

class Foo(object):
    ...
    def __getattr__(self, name):
    if name == 'b':
        self.b = 2
        return self.b
    else:
        ...

      

As an alternative:

class Foo(object):
    ...
    def __getattr__(self, name):
    if name not in ('a', 'c'):
        self.b = 2
        return self.b
    else:
        ...

      

0


source







All Articles