1

What would be the proper way to type the print_before function decorator, so that the wrapped function has the proper type but I can't use the decorator on a class that would not work?

thank you

def print_before(func):
    def func_wrapper(self, *args):
        self.print_hi()
        return func(self, *args)
    return func_wrapper


class PrintThings:
    def print_hi(self):
        print("hi")

    @print_before
    def add_nums(self, a: int, b: int) -> int:
        return a + b


pt = PrintThings()
pt.add_nums(5, 4)


class ShouldNotWork:
    @print_before
    def add_nums(self, a: int, b: int) -> int:
        return a + b


snw = ShouldNotWork()
snw.add_nums(4, 5)
Archaeron
  • 767
  • 1
  • 7
  • 15

1 Answers1

1

The decorator has a very general type as written. The only thing you know about the wrapper is that it accepts at least one argument of some unknown type, and returns some unknown type. As for the decorator itself, you only know that it returns something with the same type as its argument.

Actually, there is one more thing we know. Whatever the type of self is, it must have a print_hi method. We can represent that using a Protocol.

We also introduce a type variable T, which represents the same class (or a subclass thereof) each time it is used in the signature of print_before.

from typing import Any, Tuple, Protocol, TypeVar

class PrintsHi(Protocol):
    def print_hi(self):
        pass


T = TypeVar('T', bound=PrintsHi)


def print_before(func: Callable[[T, Tuple[Any,...]], Any]) -> Callable[[T, Tuple[Any,...]], Any]:
    def func_wrapper(self: T, *args: Tuple[Any,...]):
        self.print_hi()
        return func(self, *args)
    return func_wrapper
chepner
  • 497,756
  • 71
  • 530
  • 681
  • but I also know that self cannot be `Any`, since it needs to have a function `print_hi` defined – Archaeron Nov 17 '20 at 14:25
  • Fixed that, but it's still not quite right: we can also assume that the return value involves the same class (though possibly a subclass) used by the argument. – chepner Nov 17 '20 at 14:27
  • I _think_ it's fixed now, but I can't test it just now. – chepner Nov 17 '20 at 14:29
  • thank you for your help! the newest iteration disallows both usages now :D Argument of type "(self: PrintThings, a: int, b: int) -> int" cannot be assigned to parameter "func" of type "(p0: T, p1: Tuple[Any, ...]) -> Any" in function "print_before" ... "Tuple[Any, ...]" is incompatible with "int" Function accepts too few positional parameters; expected 3 but received 2 – Archaeron Nov 17 '20 at 14:32
  • from what I understand I believe this is needed to solve my problem: https://www.python.org/dev/peps/pep-0612/ and it will only come with python 3.10 in a year – Archaeron Nov 19 '20 at 21:17