Uploaded image for project: 'Qt for Python'
  1. Qt for Python
  2. PYSIDE-652

Proposal for a new function annotation-based slot syntax for Python 3 users

XMLWordPrintable

    • Icon: Suggestion Suggestion
    • Resolution: Unresolved
    • Icon: P3: Somewhat important P3: Somewhat important
    • None
    • None
    • PySide
    • None

      To be clear, I don't think this should replace Slot(), but could be a handy syntax for people using PySide with Python 3

      Instead of this:

      @Slot(int, result=int)
      @Slot(str, result=str)
      def my_func(arg):
          pass
      

      You could annotate like this:

      @aslot
      def my_func(arg: int) -> int:
          pass
      
      @my_func.overload
      def my_func(arg: str) -> str: ...
      

      Additional support could hypothetically be made for dealing with things from typing, like handling List[str] for example.

      Here is a prototype pure-python implementation. It is ported over from a PyQt-based version I made back on the PySide2 GitHub repo, and haven't tested it too closely:

      from inspect import getfullargspec
      
      from PySide2.QtCore import Slot
      
      class _OverloadedSlotPlaceholder:
          def __init__(self, name, original_name):
              self.name = name
              self.original_name = original_name
      
          def __repr__(self):
              return ('<placeholder slot {name} for function {original_name}>'
                          .format(name=self.name, original_name=self.original_name))
              
      def _build_arguments(func, original_func=None):
          argspec = getfullargspec(func)
          args = argspec.args
          annotations = argspec.annotations
      
          slot_args = []
          slot_kwargs = {}
      
          if 'return' in annotations:
              slot_kwargs['result'] = annotations['return']
      
          if original_func and func.__name__ != original_func.__name__:
              slot_kwargs['name'] = func.__name__
      
          gap = False
      
          for index, arg in enumerate(args):
              # Let the first arg pass by unannotated, as it may be the 'self'
              # argument of a function. Static methods can't be used as slots anyways
              if index == 0 and arg not in annotations:
                  continue
      
              if arg in annotations and not gap:
                  slot_args.append(annotations[arg])
              elif arg not in annotations:
                  gap = True
              elif arg in annotations and gap:
                  raise TypeError('Type annotations must be in a continuous row - an argument before "{argument}" is missing'
                                      .format(argument=arg))
      
          return slot_args, slot_kwargs
      
      def slot(func):
          """
          A special Python 3-only Qt slot decorator, taking advantage of function 
          annotations to provide the types for arguments.
      
          It's just syntactic sugar for the slot decorators that already come with the
          Python Qt bindings.
      
          This has **no effect whatsoever** on the Python side of things - it does not
          do type checking, it only registers a function and the types its arguments 
          take with Qt's Meta Object system.
      
          Use like so:
      
              @slot
              def my_function(self, x: int, y: int) -> int:
                  return x ** y
      
          You can also overload a function by doing this:
      
              @slot
              def overloaded_func(self, output=None: str):
                  print(output)
      
              @overloaded_func.overload
              def overloaded_func(self, output: int): ...
      
              # And an overload for calling it with no arguments, so it will fall back
              # on the default argument.
              @overloaded_func.overload
              def overloaded_func(self): ...
      
          Note: you cannot set different default arguments in the overloads, you may
          only set a function's default arguments in the main declaration.
      
          The function body on overloads is completely ignored, so you can just use
          ellipses (`...`) or `pass` to fill in the body.
      
          """
      
          slot_args, slot_kwargs = _build_arguments(func)
      
          Slot(*slot_args, **slot_kwargs)(func)
      
          def overload(new_func):
              slot_args, slot_kwargs = _build_arguments(new_func, func)
      
              Slot(*slot_args, **slot_kwargs)(func)
      
              if new_func.__name__ != func.__name__:
                  return _OverloadedSlotPlaceholder(new_func.__name__, func.__name__)
              else:
                  return func
      
          func.overload = overload
      
          return func
      

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

            ctismer Christian Tismer
            empyrical empyrical
            Votes:
            1 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:

                There are no open Gerrit changes