5

With the type hinting syntax specified in PEP 484 and 585, is there any way to indicate that a function's parameter should be a mutable reference that would be modified by the function?

For instance, C# has ref paramters, so in Python, is there any equivalent? e.g.

>>> def foo(spam: "Mutable[List[int]]"):
...     spam.append(sum(spam))
...
>>> a = [1, 2, 3]
>>> foo(a)
>>> a
[1, 2, 3, 6]

or if not, how could I define such a type without causing the inspection logic to think that it was a special Mutable class instead of a List[int]? Obviously this would be used as a tool for the developer to understand a method more easily, instead of one that would be used to fundamentally change the program.

For clarity, I'm aware that Lists by definition are mutable, but I'm wondering if there is a way to define when it will be mutated, for example

>>> def bar(sandwich: Mutable[List[str]], fridge: List[str]):
...     sandwich.extend(random.sample(fridge, k=3))
Sam Rockett
  • 3,145
  • 2
  • 19
  • 33
  • 2
    all lists are invariably mutable correct? – Bhosale Shrikant Jan 08 '20 at 11:24
  • @BhosaleShrikant yes, just like sets, dicts and custom objects. – BramAppel Jan 08 '20 at 11:27
  • this is something the documentation of the function can reflect, since python doesn't create a new value when passing it to a function in a sense it's always by reference – AntiMatterDynamite Jan 08 '20 at 11:28
  • 2
    AFAIK, a ref parameter in C# is *passed by reference*, it doesn't mean that it *will necessarily be mutated in the function*. Since python *never supports call by reference*, then there is no equivalent. But you seem to want some type annotation that states that a mutable type *will be mutated*, but there is no such type annotation – juanpa.arrivillaga Jan 08 '20 at 11:42
  • Thanks for letting me know. I suppose I should now go research how to build types such that CustomType[RealType] is interpreted by type checkers as RealType then huh. – Sam Rockett Jan 08 '20 at 11:48
  • @SamRockett huh? What do you mean by `CustomType[RealType]`??? what's a "real type" as opposed to a "custom type"? – juanpa.arrivillaga Jan 08 '20 at 11:49
  • Yeah, I'm not sure whether any typing system exists that checks for that, although I think one could, but the fanciest types systems tend to be for languages that don't have mutable state at all! – juanpa.arrivillaga Jan 08 '20 at 11:50
  • There is also an interesting answer for further reading about how method arguments are passed in Python: https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference – amanb Jan 08 '20 at 12:03
  • @juanpa.arrivillaga Last time I tried to hack something together that would work for how I need it (which ultimately was named `Greedy[int]` indicating it wanted to collect as many contiguous ints from a list of args until a ValueError happened,) the type checker kept on crying about how the passed value was expected to be of type Greedy, not of type List, so I'd need to work out how to have `Mutating[List]` accept a List instead of a Mutating, if that makes sense... – Sam Rockett Jan 08 '20 at 12:06

2 Answers2

3

Lists are mutable in Python and thus an explicit Mutable class reference is not required:

In [3]: from typing import List

In [7]: def foo(spam:List[int]):
   ...:     spam.append(sum(spam))
   ...:     return spam  

In [8]: a = [1,2,3]   

In [9]: foo(a)

Out[9]: [1, 2, 3, 6]
amanb
  • 5,276
  • 3
  • 19
  • 38
  • Yeah, I'm aware this is a given, but most of the time when I accept a list as the input, its to use its values without mutating it, so I'm wondering if there is an explicit way to state if the list will be mutated vs if it will remain as is. e.g. difference between `def foo(a: List, b: List): a.extend(b)` and `def foo(a: Mutable[List], b: List): a.extend(b)` – Sam Rockett Jan 08 '20 at 11:33
  • 1
    @SamRockett no there isn't. – juanpa.arrivillaga Jan 08 '20 at 11:42
  • 1
    if mutable use list else uses a tuple ...that's what i learnt – Bhosale Shrikant Jan 08 '20 at 11:46
1

Lists, by default, are considered to always be mutable. So if you want to indicate some list will never be changed, it's better to indicate that explicitly by using some read-only interface or protocol such as typing.Sequence or typing.Collection instead.

These two types are meant to have the same semantics as the corresponding collections.abc types. I suppose you can kind of think of them as roughly the same as C#'s IReadOnlyCollection.

from typing import Sequence, overload

def foo(seq: Sequence[int]) -> None:
    # This type checks
    for item in seq:
        print(seq)

    # ...but this does not. Mypy reports a
    # '"Sequence[int]" has no attribute "append" error', for example
    seq.append(5)

# This type checks, since lists satisfy the Sequence protocol
foo([1, 2, 3, 4])

# Same thing with tuples
foo((1, 2, 3, 4))

class CustomSequence(Sequence[int]):
    @overload
    def __getitem__(self, i: int) -> int: ...
    @overload
    def __getitem__(self, s: slice) -> Sequence[int]: ...
    def __getitem__(self, x: Union[int, slice]) -> Union[int, Sequence[int]]:
        if isinstance(x, int):
            return 1
        else:
            return [1, 2]

    def __len__(self) -> int:
        return 1

# Or any other kind of Sequence, actually.
foo(CustomSequence())

And if you want a general-purpose mutable sequence, use MutableSequence. Note that lists satisfy both the Sequence and MutableSequence protocols.

Michael0x2a
  • 58,192
  • 30
  • 175
  • 224