class User:
def __init__(self, name: str, address: "Address"):
self.name = name
self.address = address
# ^ because let's say for some reason we must have
# an address for each user
class Address:
def __init__(self, owner: User, address_line: str):
self.owner = owner
self.address_line = address_line
This approach is useful if you have objects with interdependencies, as in the above example. There is probably a more elegant way to untangle it, but at least you can provide ahead-of-time hints in the same namespace simply by providing the name of the object.
However, a better way to do this is to use a feature called deferred evaluation of annotations. You can use a special import to change the way annotations are resolved at the module level:
from __future__ import annotations
class User:
def __init__(self, name: str, address: Address):
self.name = name
self.address = address
# ^ because let's say for some reason we must have
# an address for each user
class Address:
def __init__(self, owner: User, address_line: str):
self.owner = owner
self.address_line = address_line
When you add from __future__ import annotations
at the top of a module, annotations are now resolved lazily for that module—just as if they were string hints, but the actual object is referred to instead of just its name. This allows linters to resolve things far more powerfully and completely, which is why it’s the recommended solution for this problem. You can still use string hints where they’re expected, but they should be phased out.