How can the outside modification of an instance variable be hindered in Python?

时间:2019-01-18 18:25:17

标签: python matplotlib private

My understanding was that there are not really private methods/attributes in Python, meaning that as long as you know the name of the method or attribute you will always be able to access and/or modify it (I might be understanding wrongly the concept of privacy).

Taking into account the framework described above, I came across with a situation I cannot quite wrap my head around. When creating a matplotlib.pyplot plot:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib

fig, ax = plt.subplots()

x = np.arange(100)
ax.plot(x, np.sin(x), label='sinus')
ax.legend()

I get a matplotlib.legend.Legend object holding a list of all matplotlib.text.Text objects that resemble the text entries displayed in the legend:

>>> fig.axes[0].legend()
<matplotlib.legend.Legend at 0x28f6aa4d898>
>>> fig.axes[0].legend().get_texts()
<a list of 1 Text objects>
>>> fig.axes[0].legend().get_texts()[0]
<matplotlib.text.Text at 0x28f6aa17198>
>>> print(fig.axes[0].legend().get_texts()[0])
Text(0,0,'sinus')

According to the source code, seems to me that the list returned by the method matplotlib.legend.Legend.get_texts() is filled with the content of the matplotlib.legend.Legend instance variable self.texts, which is itself a list. Accessing and modifying this instance variable does not work:

>>> fig.axes[0].legend().texts
[<matplotlib.text.Text at 0x28f6420db38>]
>>> print(fig.axes[0].legend().texts[0])
Text(0,0,'sinus')
>>> fig.axes[0].legend().texts = [matplotlib.text.Text(0,0,'foo')]
>>> print(fig.axes[0].legend().texts[0])
Text(0,0,'sinus')

I am not an expert in Python, but I was not expecting this behavior, and I do not understand how this happens at an object/property level, and I haven't followed deeper if there are any special constraints in the construction of the matplotlib.legend.Legend.texts instance variable.

The only general reason for this to happen I could come up with has been that at some level of depth there is some kind of "ghost" setter that just does not modify a class property:

class Foo:

    def __init__(self, value):
        self._x = value

    @property
    def x(self): 
        return self._x

    @x.setter
    def x(self, value):
        # This setter blocks outside access
        pass

This setter blocks control from the outside to modify the variable value:

>>> f = Foo(3)
>>> f.x
3
f.x = 5
>>> f.x
3

Nevertheless, the matplotlib.legend.Legend instance variable self.texts seems defined like a regular instance variable and I have not been able to find it defined as a property or any getters or setters related to it.

So, given this situation, I have three questions:

1) Is this little class example an accepted/legit way of getting "some sort" of privacy, in the sense that the attribute cannot be modified?

2) Are there some other/equivalent ways of blocking access/control over attributes or methods in Python?

3) How does it work or is it arranged in the particular case of matplotlib.legend.Legend objects (focus on the self.texts instance variable)?

Considerations:

1) I have tried to find answers to these questions and have not succeeded. Particularly the question to how is this behavior implemented in matplotlib.legend.Legend objects remains yet unanswered to me.

2) I have taken a look at the matplotlib.legend.Legend source code and I can see that the function matplotlib.legend.Legend.get_texts() returns a silent_list object, which apparently is just a list object with an overridden __repr__ method. Still does not help me get the understanding that I am missing.

3) I am aware of the bunch of higher-level methods in matplotlib.legend.Legend objects that allow me to modify the legend in many/all possible ways. I though just want to understand how the mechanism works under the hood.

Thanks very much in advance for your time and help.

1 个答案:

答案 0 :(得分:2)

This has nothing to do with the attribute machinery. You're just getting different objects from the legend() call each time. These objects have different attributes, so screwing with one legend() return value doesn't automatically affect another.