1

I have discovered today this bug in our code base (simplified example):

from abc import ABC, abstractmethod


class Interface(ABC):
    @abstractmethod
    def method(self, variable: str) -> str:
        pass


class Implementation(Interface):
    def method(self, variable_changed: str) -> str:
        return "A"


Implementation().method("d")  # works
Implementation().method(variable="d")  # error
Implementation().method(variable_changed="d")  # works

Here we have a class implementing an interface. That's all good, but the implementation change the name of the first argument of the method. If I run mypy, I would expect to get an error, because implementation is not following the contract defined by the Interface. To my surprise, this is not the case at all.

I'm not sure if this is intended or not, but I would like to detect those kinds of mismatchs as soon as possible. Any idea how to fix mypy or how to enable this kind of detection in case it's not an error?

khelwood
  • 55,782
  • 14
  • 81
  • 108
Antonio Gamiz Delgado
  • 1,871
  • 1
  • 12
  • 33
  • Mypy treats args as positional by default, which means the name doesn’t matter. Do you want this arg to always be named? – Samwise Feb 01 '23 at 11:06
  • Mm, if that forces mypy to throw an error then the implementation does not match the interface then yes, always named. – Antonio Gamiz Delgado Feb 01 '23 at 11:10
  • Put a `*` in the param list right after `self` to specify that all the remaining parameters are named rather than positional. See: https://docs.python.org/3/reference/compound_stmts.html#function-definitions and https://stackoverflow.com/questions/14301967/bare-asterisk-in-function-parameters – Samwise Feb 01 '23 at 11:12
  • Mmm, but then I need to add that to all my interfaces. And I would need to remember it every time I add a new interface. I would prefer to make mypy fail instead. – Antonio Gamiz Delgado Feb 01 '23 at 11:22
  • https://github.com/python/mypy/issues/6709 – Samwise Feb 01 '23 at 11:34
  • oh so I suppose it's not possible yet then :( . I'm tempted to learn enough mypy internals and make a PR lol hahaha. Could you please write your thoughts in an answer to mark it as accepted? :) – Antonio Gamiz Delgado Feb 01 '23 at 14:25

1 Answers1

2

Mypy treats parameters as positional if they aren’t explicitly specified as named. Your Interface therefore only requires that method accepts one argument, not that it have a particular name.

To specify it as a named parameter (forcing implementations to match the name) you’d do:

def method(self, *, variable: str) -> str:

to say that variable is a named parameter (and must always be called as such).

For discussion on changing this behavior in mypy see the related GitHub issue: https://github.com/python/mypy/issues/6709

Samwise
  • 68,105
  • 3
  • 30
  • 44
  • The "official" names are position-or-keyord parameters (for the traditional parameter than can be set using positional or keyword arguments) and keyword-only parameters (for those that can only be set by keyword arguments). See https://docs.python.org/3/glossary.html#term-parameter. – chepner Feb 01 '23 at 15:21