Skip to content

Commit 0173fbb

Browse files
committed
Add docs (also one test just in case)
1 parent efe6ed7 commit 0173fbb

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

docs/source/protocols.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,53 @@ the parameters are positional-only. Example (using the legacy syntax for generic
352352
copy_a = copy_b # OK
353353
copy_b = copy_a # Also OK
354354
355+
Binding of types in protocol attributes
356+
***************************************
357+
358+
All protocol attributes annotations are treated as externally visible types
359+
of those attributes. This means that for example callables are not bound,
360+
and descriptors are not invoked:
361+
362+
.. code-block:: python
363+
364+
from typing import Callable, Protocol, overload
365+
366+
class Integer:
367+
@overload
368+
def __get__(self, instance: None, owner: object) -> Integer: ...
369+
@overload
370+
def __get__(self, instance: object, owner: object) -> int: ...
371+
# <some implementation>
372+
373+
class Example(Protocol):
374+
foo: Callable[[object], int]
375+
bar: Integer
376+
377+
ex: Example
378+
reveal_type(ex.foo) # Revealed type is Callable[[object], int]
379+
reveal_type(ex.bar) # Revealed type is Integer
380+
381+
In other words, protocol attribute types are handled as they would appear in a
382+
``self`` attribute annotation in a regular class. If you want some protocol
383+
attributes to be handled as though they were defined at class level, you should
384+
declare them explicitly using ``ClassVar[...]``. Continuing previous example:
385+
386+
.. code-block:: python
387+
388+
from typing import ClassVar
389+
390+
class OtherExample(Protocol):
391+
# This style is *not recommended*, but may be needed to re-use
392+
# some complex callable types. Otherwise use regular methods.
393+
foo: ClassVar[Callable[[object], int]]
394+
# This may be needed to mimic descriptor access on Type[...] types,
395+
# otherwise use a plain "bar: int" style.
396+
bar: ClassVar[Integer]
397+
398+
ex2: OtherExample
399+
reveal_type(ex2.foo) # Revealed type is Callable[[], int]
400+
reveal_type(ex2.bar) # Revealed type is int
401+
355402
.. _predefined_protocols_reference:
356403

357404
Predefined protocol reference

test-data/unit/check-protocols.test

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4608,9 +4608,9 @@ from typing import Any, Protocol, overload, ClassVar, Type
46084608

46094609
class Desc:
46104610
@overload
4611-
def __get__(self, instance: None, owner: Any) -> Desc: ...
4611+
def __get__(self, instance: None, owner: object) -> Desc: ...
46124612
@overload
4613-
def __get__(self, instance: object, owner: Any) -> int: ...
4613+
def __get__(self, instance: object, owner: object) -> int: ...
46144614
def __get__(self, instance, owner):
46154615
pass
46164616

@@ -4628,3 +4628,21 @@ reveal_type(tt.x) # N: Revealed type is "__main__.Desc"
46284628
bad: P = C # E: Incompatible types in assignment (expression has type "type[C]", variable has type "P") \
46294629
# N: Following member(s) of "C" have conflicts: \
46304630
# N: x: expected "int", got "Desc"
4631+
4632+
[case testProtocolClassValCallable]
4633+
from typing import Any, Protocol, overload, ClassVar, Type, Callable
4634+
4635+
class P(Protocol):
4636+
foo: Callable[[object], int]
4637+
bar: ClassVar[Callable[[object], int]]
4638+
4639+
class C:
4640+
foo: Callable[[object], int]
4641+
bar: ClassVar[Callable[[object], int]]
4642+
4643+
t: P = C()
4644+
reveal_type(t.foo) # N: Revealed type is "def (builtins.object) -> builtins.int"
4645+
reveal_type(t.bar) # N: Revealed type is "def () -> builtins.int"
4646+
tt: Type[P] = C
4647+
reveal_type(tt.foo) # N: Revealed type is "def (builtins.object) -> builtins.int"
4648+
reveal_type(tt.bar) # N: Revealed type is "def (builtins.object) -> builtins.int"

0 commit comments

Comments
 (0)