pythonclasstuplesvariable-assignment

How would I return an assigned variable of a class (monitor = Monitor()) as a tuple for a match case comparison?


I have a Dell 2-in-1 computer that I can use as a tablet, and I'm working on a script to resize the window of an application base on the orientation of the display. What I'm trying to do is get an assigned variable monitor = Monitor() to return itself as a tuple in a match case comparison.

from win32api import GetMonitorInfo, MonitorFromPoint
from typing import Optional

class Monitor:
    def __init__(self):
        self._monitor: Optional[tuple] = None

        self.update()

    @property
    def Width(self) -> int:
        return self._monitor[0]

    @property
    def Height(self) -> int:
        return self._monitor[1]

    def update(self):
        self._monitor = self()

    def __call__(self) -> tuple:
        return GetMonitorInfo(MonitorFromPoint((0, 0)))['Monitor'][2:]

    def __iter__(self):
        return iter(self._monitor)

    def __next__(self):
        return tuple(self._monitor)

    def __getitem__(self, index):
        return self._monitor[index]

    def __eq__(self, other):
        return self._monitor == other

    def __str__(self):
        return str(self._monitor)

    def __repr__(self):
        return str(self._monitor)


if __name__ == '__main__':
    monitor = Monitor()
    print(f'monitor: {monitor}')

    match monitor:
        case (1440, 900):
            print((1440, 900))
        case (900, 1440):
            print((900, 1440))

For several days, I've read through posts here on stackoverflow.com, and I've been searching through Google search looking for code examples, but I haven't found anything. I do know that the class.__call__ will give me what I want, but in this class, the __call__ is to get the current resolution of the monitor so I can compare the class against itself monitor == monitor(). With Windows 11 Tablet Mode, the touch screen will rotate with the computer the same way a touch screen does on an Android device.


Solution

  • To match a sequence pattern (which is what you're getting with case (1440, 900) and the reverse), you need your class to inherit from collections.abc.Sequence (see this footnote in the docs for match statement), or be registered as a subclass without actually inheriting (with collections.abc.Sequence.register(Monitor)). Just implementing the __getitem__ method is not enough. (Note, you also need a __len__ method to actually be a valid sequence.)

    If you're willing to adjust your patterns, you could make the current form of your class work. Try something like this:

    case Monitor(Width=1440, Height=900)):
    

    Note that class patterns like this are written as if each named attribute can be passed as an argument when constructing an instance. This isn't actually true for your class, but it doesn't matter that the actual __init__ method doesn't take Width or Height, the match will still work.

    That said, the monitor == monitor() design seems pretty weird. It might make more sense to create a second instance, rather than calling the first one to get an updated resolution tuple. Then compare the two monitor instances:

     old_monitor = Monitor()
    
     # later, when you want to check if the orientation has changed:
     new_monitor = Monitor()
     if old_monitor != new_monitor:
         # rotate...