Why are print statements inside __getattribute__ being called an unusual number of times?

时间:2018-03-09 19:06:13

标签: python python-3.x printing override getattribute

Suppose we have the following class:

class Klass:
    def __init__(self):
        self.member_var1 = 1

    def __getattribute__(self, attr_name):
        print('Klass.__getattribute__') # STATEMENT OF INTEREST            
        return object.__getattribute__(self, attr_name)

# Now we instantiate the class, and access its members:

obj = Klass()
print('obj.member_var1 == ', obj.member_var1)

The Desired Behavior:

Only one print statement is to be executed. The terminal output should look like this:

Klass.__getattribute__

Reproducing the Error:

Run the code given at the beginning of this post at pythontutor.com

  1. copy and paste code into the online editor
  2. click the Visualize Execution button
  3. click the Last>> button

The terminal output looks like this:

Klass.__getattribute__
Klass.__getattribute__
Klass.__getattribute__
Klass.__getattribute__
[...]
Klass.__getattribute__
Klass.__getattribute__
Klass.__getattribute__
Klass.__getattribute__

One can also reproduce the error by running the code in the IDE named Spyder

OS               :  Windows
IDE              :  Spyder 3.2.7
Python           :  3.6.4 64bits
Qt               :  5.9.3
PyQt5            :  5.9.2
IPython >=4.0    :  6.2.1 (OK)
jedi >=0.9.0     :  0.11.1 (OK)
nbconvert >=4.0  :  5.3.1 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pyflakes >=0.6.0 :  1.6.0 (OK)
pygments >=2.0   :  2.2.0 (OK)
pylint >=0.25    :  1.8.2 (OK)
qtconsole >=4.2.0:  4.3.1 (OK)
rope >=0.9.4     :  0.10.7 (OK)
sphinx >=0.6.6   :  1.7.1 (OK)

Behavior can Difffer for Functions other than print

Consider the following:

class CounterStream:
    def __init__(self):
        self.d = dict()

    def __call__(self, *args):
        for arg in args:
            self.d[arg] = 1 + self.d.get(arg, 0)

    def report(self):
        print('CounterStream says:')
        for key in self.d:
            print(str(key).ljust(20), ' was sent to stream\n',
                  str(self.d[key]).rjust(5), ' times\n')

foo = CounterStream()

class Klass:
    def __init__(self):
        self.member_var1 = 1

    def __getattribute__(self, attr_name):
        #### print('Klass.__getattribute__')
        foo('Klass.__getattribute__')           
        return object.__getattribute__(self, attr_name)

# Now we instantiate the class, and access its members:

obj = Klass()
print('obj.member_var1 == ', obj.member_var1)   
foo.report() 

For pythontutor.com, the output is:

obj.member_var1 ==  1
CounterStream says:
Klass.__getattribute__  was sent to stream
   217  times

For my copy of Spyder, the output is:

obj.member_var1 ==  1
CounterStream says:
Klass.__getattribute__  was sent to stream
   1  times

1 个答案:

答案 0 :(得分:1)

That's not something your code is doing. That's pythontutor.com trying to access your object's attributes to provide visualization.