getting rid of the recursion in __getattribute__

Peter Otten __peter__ at web.de
Thu May 25 03:59:03 EDT 2023


On 24/05/2023 15:37, A KR wrote:
> It is perfectly explained in the standards here [1] saying that:
>
> <quote>
> In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).
> </quote>
>
> Therefore, I wrote a code following what the standard says:
>
> <code>
> class Sample():
>      def __init__(self):
>          self.a = -10
>
>      def __getattribute__(self, name):
>          if name == 'a':
>              return object.__getattribute__(self, name)
>
>          raise AttributeError()
>
> s = Sample()
> result = s.a
> print(result)
> </code>
> I did not fall into recursion, and the output was
> -10

While this works it's not how I understand the recommended pattern. I'd
rather treat "special" attributes first and then use the
__getattribute__ method of the base class as a fallback:

 >> class Demo:
	def __getattribute__(self, name):
		if name == "answer":
			return 42
		return super().__getattribute__(name)

That way your special arguments,

 >>> d = Demo()
 >>> d.answer
42


missing arguments

 >>> d.whatever
Traceback (most recent call last):
   File "<pyshell#13>", line 1, in <module>
     d.whatever
   File "<pyshell#10>", line 5, in __getattribute__
     return super().__getattribute__(name)
AttributeError: 'Demo' object has no attribute 'whatever'

and "normal" arguments are treated as expected

 >>> d.question = "What's up?"
 >>> d.question
"What's up?"

Eventual "special" arguments in the superclass would also remain accessible.


> However, when I try the code without deriving from a class:
>
> class AnyClassNoRelation:
>      pass
>
> class Sample():
>      def __init__(self):
>          self.a = -10
>
>      def __getattribute__(self, name):
>          if name == 'a':
>              return AnyClassNoRelation.__getattribute__(self, name)
>
>          raise AttributeError()
>
> s = Sample()
>
> result = s.a
> print(result)
> and calling __getattribute__ via any class (in this example class AnyClassNoRelation) instead of object.__getattribute__(self, name) as the standard says call using the base class, I get the same output: no recursion and -10.
>
> So my question:
>
> How come this is possible (having the same output without using the base class's __getattribute__? Although the standards clearly states that __getattribute__ should be called from the base class.


AnyClassNoRelation does not override __getattribute__, so

 >>> AnyClassNoRelation.__getattribute__ is object.__getattribute__
True


There is no sanity check whether a method that you call explicitly is
actually in an object's inheritance tree,

 >>> class NoRelation:
	def __getattribute__(self, name):
		return name.upper()


 >>> class Demo:
	def __getattribute__(self, name):
		return "<{}>".format(NoRelation.__getattribute__(self, name))


 >>> Demo().some_arg
'<SOME_ARG>'

but the only purpose I can imagine of actually calling "someone else's"
method is to confuse the reader...
> <quote>
> In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).
> </quote>
>
> Literally, I can call __getattribute__ with anyclass (except Sample cause it will be infinite recursion) I define and it works just fine. Could you explain me why that happens?




More information about the Python-list mailing list