[Python-Dev] PEP 544 (Protocols): adding a protocol to a class post-hoc

Ivan Levkivskyi levkivskyi at gmail.com
Sun Jul 1 08:06:13 EDT 2018

On 30 June 2018 at 23:54, Tin Tvrtković <tinchester at gmail.com> wrote:

> [...]
> An attrs class has a special class-level field, __attrs_attrs__, which
> holds the attribute definitions. So maybe we can define a protocol:
> class AttrsClass(Protocol):
>     __attrs_attrs__: ClassVar[Tuple[Attribute, ...]]
> then we could define asdict as (simplified):
> def asdict(inst: AttrsClass) -> Dict[str, Any]:
>     ...
> and it should work out. My question is how to actually add this protocol
> to attrs classes. Now, we currently have an attrs plugin in mypy so maybe
> some magic in there could make it happen in this particular case.

Just add a Var with an appropriate name and type to the TypeInfo. This is
literary a dozen lines of code, you can ask on mypy tracker or typing
Gitter chat for more help.

> My second use case is a small library I've developed for work, which
> basically wraps attrs and generates and sticks methods on a class for
> serialization/deserialization. Consider the following short program, which
> does not typecheck on the current mypy.
> class Serializable(Protocol):
>     def __serialize__(self) -> int:
>         ...
> def make_serializable(cl: Type) -> Type:
>     cl = attr.s(cl)
>     cl.__serialize__ = lambda self: 1
>     return cl
> @make_serializable
> class A:
>     a: int = attr.ib()
> def serialize(inst: Serializable) -> int:
>     return inst.__serialize__()
> serialize(A(1))
> error: Argument 1 to "serialize" has incompatible type "A"; expected
> "Serializable"
> error: Too many arguments for "A"
> I have no desire to write a mypy plugin for this library. So I guess what
> is needed is a way to annotate the class decorator, telling mypy it's
> adding a protocol to a class. It seems to have trouble getting to this
> conclusion by itself.

A proper solution for this would be to introduce intersection types, and
type your decorator as following:

T = TypeVar('T')
def make_serializable(cls: Type[T]) -> Type[Intersection[T, Serializable]]:

However, intersection types are unlikely to appear in mypy this year. In
best case they could appear around mid-2019, so you are better with writing
a plugin for now.

> (The second error implies the attrs plugin doesn't handle wrapping attr.s,
> which is unfortunate but a different issue.)

 Your decorator is typed as (Type) -> Type, thats it, the function is a
black box for mypy (with few special exceptions), if some effect of a
function is not declared in its signature, then it is lost forever.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20180701/d0d84cc4/attachment.html>

More information about the Python-Dev mailing list