Skip to content

Instantly share code, notes, and snippets.

@MattOates
Last active February 17, 2020 16:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MattOates/dc6464cd27b035721c36fb2679aa812c to your computer and use it in GitHub Desktop.
Save MattOates/dc6464cd27b035721c36fb2679aa812c to your computer and use it in GitHub Desktop.
The general problem is that mypy doesn't really understand that a descriptor on an attribute is going to have the return value of __get__ not __call__/__init__
"""
If you run the following with mypy you will get the following type error
mypy descriptor_typing_problem.py
descriptor_typing_problem.py:36: error: Incompatible types in assignment (expression has type "attr_descriptor", variable has type "int")
"""
from enum import Enum
from typing import Type, Any
Sentinels = Enum("Sentinels", "NO_INIT")
class attr_descriptor:
def __init__(self, default=Sentinels.NO_INIT):
self.default = default
def __set_name__(self, owner, name):
self.owner = owner
self.name = name
if hasattr(owner, "__annotations__"):
self.value_type = owner.__annotations__.get(name, None)
self.value_type = Sentinels.NO_INIT
def __get__(self, instance: 'BaseClass', typevar: Type['BaseClass']) -> Any:
if instance is None:
return self
if self.name in instance.__dict__:
return instance.__dict__[self.name]
if self.default != Sentinels.NO_INIT:
return self.default
if self.value_type != Sentinels.NO_INIT:
return self.value_type()
return None
class BaseClass:
thing: int = attr_descriptor(default=10)
def __init__(self, thing):
if thing:
self.thing = thing
BaseClass(thing=2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment