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?
source to share
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.
source to share
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)
source to share
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)
source to share
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:
...
source to share