#🔒 Implementing `__setitem__` for a Collection interface.

20 messages · Page 1 of 1 (latest)

north wing
#

Greetings there,

Hope all are well. I have written this interface for performing duck typing for mutable iterables, and would like to ask for some assistance in adding __setitem__ to this implementation.

As easy as it may seem, I did try the following :

from __future__ import annotations

__all__ = ['Collection']

from typing import (Iterator, overload, Protocol, TypeVar,
                    TypeAlias, Self, runtime_checkable)


T = TypeVar("T", covariant=True)

@runtime_checkable
class Collection(Protocol[T]):
  def __len__(self) -> int:
    ...
  def __iter__(self) -> Iterator[T]:
    ...
  @overload
  def __getitem__(self, idx: int) -> T:
    ...
  @overload
  def __getitem__(self, idx: slice) -> Self:
    ...
  @overload
  def __setitem__(self, idx: int, value: T) -> None:
    ...
  @overload
  def __setitem__(self, idx: slice, value: Self) -> None:
    ...
  def __add__(self, other: Self) -> Self:
    ...
  def __mul__(self, other: int) -> Self:
    ...

However, the first definition for __setitem__ raises the following Mypy error :

Covariant type variable "T" used in protocol where invariant one is expected Mypy(misc)

Which seems odd given it worked before for __getitem__ implementation. I'd appreciate any help in this matter.

pseudo emberBOT
#

@north wing

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

north wing
#

P.S : I understand that covariant and contravariant statuses basically apply to how a generic collection of the T would behave, which in my case is True given the following statement :

ashen gate
# north wing P.S : I understand that `covariant` and `contravariant` statuses basically apply...

The fact that you placed T as a parameter in one of the methods makes the type invariant, since parameters are contravariant (making it so that your type can no longer be covariant).

class A: ...
class B(A):
    def bar(self): ...

def foo(collection: MutableCollection[A], val: A):
    collection[0] = val

my_collection = list[B]()
foo(my_collection, A())   # Should not be allowed, since that would place a value that is not of type B into the list

# This code will cause a runtime error, and is statically caught only if MutableCollection[B] is not assignable to MutableCollection[A] in the above line
b = my_collection[0]
b.bar()
north wing
ashen gate
#

return values are covariant, parameter values are contravarient

#

if a type has both then it becomes invariant

north wing
#

I see.

ashen gate
#

that's how I quickly check whether a type is covariant, contravariant, or invariant

north wing
#

So I should make it invariant?

ashen gate
#

yes

north wing
#

I see, uno momento...

ashen gate
north wing
#

I understand this better now, thank you very much!

#

The one I was referring to was a bit less clear.

ashen gate
#

Shoutout to @halcyon ibex for making this

pseudo emberBOT
#
Python help channel closed

This help channel has been closed and it's no longer possible to send messages here. If your question wasn't answered, feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.