Search code examples
pythonpython-3.xpyside2

Why is getattr(self, "x") not the same as self.x in this example?


I found a case where getattr(self, "x") behaves different from self.x. Sorry about the length of the post - I didn't know how to reproduce the behavior without the PySide2 around. So with this example.qml file:

import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
import QtQuick.Controls.Material 2.3

ApplicationWindow {
    id: applicationWindow
    Material.theme: Material.Dark
    title: qsTr("Test Invoke")
    visible: true

    width: 600
    height: 500

    Slider {
        id: slider1
        x: 69
        y: 215
        value: Manager.xyz
        property bool updateValueWhileDragging: true
        onMoved: Manager.xyz = value
    }
}

and this example.py file:

import sys
import os

from PySide2.QtCore import Qt, QObject, Signal, Slot, Property
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QSlider, QPushButton, QCheckBox

class Manager(QObject):
    xyz_slot = Signal()
    def __init__(self):
        QObject.__init__(self)
        self.__xyz = None
        self.xyz_slot.connect(self.on_xyz_changed)
        self.xyz = 0.3

    @Property(float, notify=xyz_slot) # (6)
    def xyz(self):
        # (1) This works
        # return self.__xyz
        # (2) This doesn't work and produces the error:
        # Error: 'Manager' object has no attribute '__xyz'
        return getattr(self, "__xyz")

    @xyz.setter
    def set_xyz(self, val):
        if self.__xyz == val:
            return
        self.__xyz = val
        self.xyz_slot.emit()

    @Slot()
    def on_xyz_changed(self):
        print(self.__xyz)

if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    manager = Manager()
    ctx = engine.rootContext()
    ctx.setContextProperty("Manager", manager)
    engine.load('example.qml')
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())

using Python 3.7.

Why is the getattr (2) version not working?

Note that I need a version with the getattr because I want to decorate other classes. In that case it is not possible to use the dot-calling version. In the documentation, there is an example that claims both to be the same. I can imagine that maybe the C++ code of PySide2 causes the difference. However I don't know how to proceed from here.


Solution

  • Because you are using double-underscore name mangling.

    From the docs

    Notice that code passed to exec() or eval() does not consider the classname of the invoking class to be the current class; this is similar to the effect of the global statement, the effect of which is likewise restricted to code that is byte-compiled together. The same restriction applies to getattr(), setattr() and delattr(), as well as when referencing __dict__ directly.

    You probably don't need to use double-underscore name mangling so just don't use it.