"Name already defined" for PySide6 Property
Bug Report
When using PySide6.QtCore.Property, I get a [no-redef] error on the property setter that says it's already defined. If this is a problem that PySide6 needs to fix I will create a bug report there instead.
To Reproduce
from PySide6 import QtCore, QtWidgets
class MyWidget(QtWidgets.QWidget):
myNum: int = 0
@QtCore.Property(int)
def myProperty(self) -> int:
return self.myNum
@myProperty.setter
def myProperty(self, value: int) -> None:
self.myNum = value
Expected Behavior
If you use a python property, it gives no errors. Using QtCore.Property should also give no errors.
Actual Behavior
test.py:10: error: Name "myProperty" already defined on line 6 [no-redef]
Found 1 error in 1 file (checked 1 source file)
Your Environment
- Mypy version used: 1.10.0
- Mypy command-line flags: None
- Mypy configuration options from
mypy.ini(and other config files): None - Python version used: 3.11.9
- PySide6 version used: 6.7.1 Link to PySide6: https://doc.qt.io/qtforpython-6/
Also when I try to assign a value to the property in one of my python files, I get error: Cannot assign to a method [method-assign]. However, I cannot reproduce this with another file.
bump, I still have the same issue on latest version of pyside and mypy, with the third party type stubs
As a workaround, you can try using something like
from typing import TYPE_CHECKING
from PySide6 import QtCore, QtWidgets
if TYPE_CHECKING:
QtIntProperty = property
else:
QtIntProperty = QtCore.Property(int)
class MyWidget(QtWidgets.QWidget):
myNum: int = 0
@QtIntProperty
def myProperty(self) -> int:
return self.myNum
@myProperty.setter
def myProperty(self, value: int) -> None:
self.myNum = value
Mypy special-cases @property. I'm not sure mypy could support arbitrary property-like decorators, but maybe there is some heuristic we can use to improve this situation.
cc @sterliakov since you did work on property checking recently
Huh, this is hard. Unfortunately, properties implementation predates proper descriptors support, and it's one big pile of hack. If there was a decorator that is a drop-in replacement for property (like functools.cached_property with setter), it could've been supported by hardcoding another alias or exposing --property-decorator-fullnames flag. However, @QtCore.Property is substantially different - at least it needs an argument.
I'd say your workaround is the best we can offer right now.
With a minor tweak (below) we could also support a generalization of this pattern - using a bit of runtime magic to avoid defining alias for every property (make __class_getitem__ a constructor in if not TYPE_CHECKING), but that's borderline an antipattern I don't want to recommend in public... I'd only go that way if I needed a lot of different property types. If we start recognizing type applications in refers_to_fullname...
diff --git a/mypy/semanal.py b/mypy/semanal.py
index 431c5ec04..fb70cc64a 100644
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -7630,6 +7630,8 @@ def refers_to_fullname(node: Expression, fullnames: str | tuple[str, ...]) -> bo
if not isinstance(fullnames, tuple):
fullnames = (fullnames,)
+ if isinstance(node, IndexExpr):
+ node = node.base
if not isinstance(node, RefExpr):
return False
if node.fullname in fullnames:
... the following would start working:
from typing import TYPE_CHECKING, TypeAlias
if TYPE_CHECKING:
type QtProperty[T] = property
else:
from PySide6 import QtCore
class QtProperty(QtCore.Property):
def __class_getitem__(cls, t):
# Immediately forget about this class and return whatever runtime should've done
return QtCore.Property(t)
class MyWidget:
myNum: int = 0
@QtProperty[int]
def myProperty(self) -> int:
return self.myNum
@myProperty.setter
def myProperty(self, value: int) -> None:
self.myNum = value
reveal_type(MyWidget.myProperty)
reveal_type(MyWidget().myProperty)
Again, this is a bit of a hack, and I'm not sure it is worth supporting that way. How common is this Property? Can it be trivially replaced with builtins.property?
The property cannot be replaced with the builtins.property. It is almost the same, with the addition that it is also a Qt property, which the builtin property does not do. QtCore.Property is very common, and is a intergral part of Qt, since it provides a few Qt-specific features like notifications, etc. Further reading: PySide6 Properties doc page
Hm, okay, is the solution above acceptable? We can make it work with low efforts, while proper support will be quite challenging.
That seems like a good idea, but how would we make it work with a situation like this?
@Property(bool, notify=playingStatusChanged)
def isPlaying(self):
return self._playingStatus == PlayingStatus.PLAYING
This is the main way to use the a Property.