Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should a python type hint require that a value has a given attribute?

Let's say I have a simple function like this:

def foo(a: Any):
    return a.bar + a.baz

I would like to change the type hint from Any to one that requires (well, suggests, given that it is a type hint) that a provides the bar and baz attributes. What should it be changed to?

like image 671
Rob Gilton Avatar asked Sep 17 '19 10:09

Rob Gilton


2 Answers

This is exactly what Protocols are for. In short, Protocols let you use structural instead of nominal subtyping. With nominal subtyping, type A is a subtype of B if A explicitly inherits or extends B. With structural subtyping, type A is a subtype of B if it has the same method and attribute "signatures" as B (with some restrictions).

For example:

# If you're using Python 3.8+
from typing import Protocol

# If you need to support older versions of Python,
# pip-install the 'typing_extensions' module and do:
from typing_extensions import Protocol

class SupportsBarBaz(Protocol):
    bar: int
    baz: int

class MyUnrelatedClass1:
    def __init__(self, bar: int, baz: int) -> None:
        self.bar = bar
        self.baz = baz

class MyUnrelatedClass2:
    def __init__(self, bar: int, baz: int, blah: str) -> None:
        self.bar = bar
        self.baz = baz
        self.blah = blah

class MyUnrelatedClass3:
    def __init__(self, bar: str, baz: str, blah: str) -> None:
        self.bar = bar
        self.baz = baz
        self.blah = blah

def foo(a: SupportsBarBaz) -> int:
    return a.bar + a.baz

# These both type-check, even though there's no explicit relationship
# between 'SupportsBarBaz' and these two classes
foo(MyUnrelatedClass1(1, 2))
foo(MyUnrelatedClass2(1, 2, "abc"))

# But this doesn't type-check, since 'bar' and 'baz' are both strs here
foo(MyUnrelatedClass3("a", "b", "c"))

You can find more information about using Protocols in the mypy docs. The information in that page is all compliant with the PEP, so the info there should all apply to other type checkers, assuming they've finished implementing their own support for Protocols.

You can also find slightly more complex examples of using Protocols in typeshed, the repository of type hints for the Python standard library.

Though, I suppose this all matters only if you actually intend on using static analysis in your code. If not, you could maybe do something simpler and just define a custom type alias to Any, document what that alias is "supposed" to mean, and use that alias instead of a full-fledged protocol. That alias would be almost completely useless for the purposes of static analysis/autocompletion tools/etc, but humans generally have no issues reading comments.

like image 172
Michael0x2a Avatar answered Nov 16 '22 03:11

Michael0x2a


Type hints can only refer to a class, so create an abstract class

import abc

class MyType(abc.ABC):

    @abc.abstractproperty
    def foo(self):
        pass

    @abc.abstractproperty
    def bar(self):
        pass

And declare f(a: MyType)

like image 1
blue_note Avatar answered Nov 16 '22 02:11

blue_note