typing icon indicating copy to clipboard operation
typing copied to clipboard

Optional class and protocol fields and methods

Open srittau opened this issue 5 years ago • 6 comments

Sometimes, implementations check for the existence of a method on an object before using it. Take this example from Python 2.7's urllib:

class addbase:
    def __init__(self, fp):
        self.fp = fp
        self.read = self.fp.read
        self.readline = self.fp.readline
        if hasattr(self.fp, "readlines"): self.readlines = self.fp.readlines
        if hasattr(self.fp, "fileno"):
            self.fileno = self.fp.fileno
        else:
            self.fileno = lambda: None
        if hasattr(self.fp, "__iter__"):
            self.__iter__ = self.fp.__iter__
            if hasattr(self.fp, "next"):
                self.next = self.fp.next

Currently this is best modeled by leaving these methods out from a protocol. But this means that their signature is unknown, and mypy will complain about the non-existing attribute. It would be useful to be able to model this somehow, for example by using a decorator for such methods.

srittau avatar Dec 17 '18 15:12 srittau

This was explicitly deferred in PEP 544. On the other hand this would be not hard to implement now. Also another structural type in mypy -- TypedDict -- supports this. So maybe we should also support total=False for protocols?

@JukkaL what do you think?

ilevkivskyi avatar Dec 18 '18 00:12 ilevkivskyi

I don't think a total=False attribute would be sufficient. In the example above - in fact in all cases where I have encountered this so far - there are required members as well as optional ones. A per-attribute flag (a decorator?) would serve these cases much better.

srittau avatar Dec 18 '18 15:12 srittau

I don't think a total=False attribute would be sufficient

It is always sufficient. Whether it is convenient is a different question.

ilevkivskyi avatar Dec 18 '18 15:12 ilevkivskyi

I mentioned this again on typing-sig. It could reuse PEP 655's NotRequired class:

class MyProto:
    x: NotRequired[int]
    @not_required
    def foo(self) -> None: ...

It would also make sense for non-protocols:

class Foo:
    x: NotRequired[int]
    def setup(self) -> None:
        self.x = some_calc()

(Not a design I would recommend, but I have seen this from time to time in the wild.)

srittau avatar Nov 04 '21 12:11 srittau

I think this could be helpful for solving https://github.com/python/typing/discussions/1498 by marking _my_property as a non-required attribute. What do you think?

vnmabus avatar Oct 27 '23 08:10 vnmabus

See also:

  • https://github.com/falconry/falcon/issues/1820#issuecomment-1912779512

davetapley avatar Jan 27 '24 23:01 davetapley