0

Given the following code, I'd expect both a.x and a.y to be declared during instantiation. When class P doesn't inherit from Protocol both assertions pass. In the debugger, it doesn't seem that class P's constructor is ever being ran. I suspect this has to do with the MRO in some way, but my main question is whether I can still use super()__init__() while still having my base class inherit from Protocol


from typing import Protocol


class P(Protocol):
    y: str

    def __init__(self, y: str) -> None:
        self.y = y


class A(P):
    x: str

    def __init__(self, x: str, y: str) -> None:
        self.x = x
        super().__init__(y=y)


a = A("x", "y")
assert a.x == "x"
assert a.y == "y"  # AttributeError: 'A' object has no attribute 'y'
Jamie.Sgro
  • 821
  • 1
  • 5
  • 17
  • Please run the program again because it's running without error on my computer. – Maxwell D. Dorliea Dec 20 '22 at 20:12
  • Obviously related to https://stackoverflow.com/q/62768327/2442804 . – luk2302 Dec 20 '22 at 20:13
  • 1
    I get the expected `TypeError: Protocols cannot be instantiated` as opposed to any AttributeError. – luk2302 Dec 20 '22 at 20:14
  • 1
    `Protocol` isn't meant to provide executable code, only type signatures. It actively replaces `P.__init__` with another definition that prevents (in part) `P` from being instantiated directly. – chepner Dec 20 '22 at 20:14
  • You should call ```super().__init__(y=y)``` before ```self.x = x``` – Maxwell D. Dorliea Dec 20 '22 at 20:15
  • @MaxwellD.Dorliea That's not the problem (though it is generally the right thing to do). – chepner Dec 20 '22 at 20:15
  • Take a look at the value assigned to `P.__init__`; it's not the function you defined. – chepner Dec 20 '22 at 20:16
  • Thanks all. @chepner and luk2302 were on the money. I'm misusing the role of a Protocol and treating it more like an abstract class (with some implementation detail) than an interface (with no implementation detail). I can resolve this by instead creating a new class that sits between the Protocol and the child class. – Jamie.Sgro Dec 20 '22 at 20:19
  • Do you really need another class? The purpose of `P` is just to assert that a class has an `__init__` method that takes a string `y` as an argument, without saying anything about what it *does* with that argument. `A` doesn't have to *subclass* `P` in order for an instance of `A` to be considered an instance of `P`, for type checking purposes. – chepner Dec 20 '22 at 20:41
  • @chepner this Minimal Reproducible Example doesn't really get to the heart of the use case unfortunately. In reality I'd like to add a Protocol to (1) ensure devs are adding class/instance variables to the body of the class (it's part of our team convention to do so, Using Protocol will ensure it happens), and (2) to ensure a bunch of abstract classes that aren't part of this example actually have a concrete implementation in all of the child classes. Agreed though, in this example having three layers of inheritance is excessive. – Jamie.Sgro Dec 20 '22 at 20:46
  • Subclasses of `Protocol` are used to specify a sort of virtual superclass for use as a function argument, not to enforce any particular class definition. Given the definition of `P`, you can write `def foo(x: P): ...`, and you can pass an argument of any type to `foo`, as long as the `__init__` method of that type takes exactly one `str` argument. – chepner Dec 20 '22 at 20:49
  • 1
    In other words, a protocol allows for *structural subtyping* , rather than *nominal subtyping* (which is what subclassing provides). – chepner Dec 20 '22 at 20:50

1 Answers1

0

You can not instantiate type Protocol:

b= P("Y")

this would retun the error:

in _no_init_or_replace_init
    raise TypeError('Protocols cannot be instantiated')

see answer

Indiano
  • 684
  • 5
  • 19