7

Consider this contrived example;

@dataclass
class A:
    name: str = "John"
    ....

@dataclass
class B:
    name: str = "Doe"

Q: How do I type hint an object that has an attribute, such as the following?

def print_name(obj: HasAttr['name'])
    print(obj.name)

I understand the SO rule on showing what you have tried. The best I can offer is that I've searched the docs; Pep526, PythonSheets, Docs, and am aware of this SO Question. None seem to help (or maybe I missed it.)

[Edit] I recognize that you can get there with inheritance, but I don't want to go that route.

SteveJ
  • 3,034
  • 2
  • 27
  • 47
  • Also: [How can I use static checking to ensure an object has a certain method/attribute?](https://stackoverflow.com/q/64185810/7851470) – Georgy Dec 11 '20 at 09:05
  • Thanks, @Georgy - knowing what to search for is often the hardest. I'm tempted to leave this question up considering I couldn't find the other answers easily (and this isn't my first rodeo.) I suspect others might come upon this question before they might see the others. – SteveJ Dec 11 '20 at 17:32

1 Answers1

7

So, what you are describing is structural typing. This is distinct from the class-based nominal subtyping that the python typing system is based on. However, structural subtyping is sort of the statically typed version of Python's dynamic duck typing.

Python's typing system allows a form of this through typing.Protocol.

An example, suppose we have a Python module, test_typing.py:

from typing import Protocol
from dataclasses import dataclass

class Named(Protocol):
    name: str


@dataclass
class A:
    name: str
    id: int


@dataclass
class B:
    name: int

@dataclass
class C:
    foo: str


def frobnicate(obj: Named) -> int:
    return sum(map(ord, obj.name))


frobnicate(A('Juan', 1))
frobnicate(B(8))
frobnicate(C('Jon'))

Using mypy version 0.790:

(py38) juanarrivillaga@Juan-Arrivillaga-MacBook-Pro ~ % mypy test_typing.py
test_typing.py:28: error: Argument 1 to "frobnicate" has incompatible type "B"; expected "Named"
test_typing.py:28: note: Following member(s) of "B" have conflicts:
test_typing.py:28: note:     name: expected "str", got "int"
test_typing.py:29: error: Argument 1 to "frobnicate" has incompatible type "C"; expected "Named"
Found 2 errors in 1 file (checked 1 source file)
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172