Skip to content

Changing order of isinstance checks breaks inferred type #4422

Closed
@pirate

Description

@pirate

This works fine:

def __add__(self, other: object) -> 'Currency':
    if isinstance(other, self.__class__) and isinstance(other, Currency):
        return self.__class__(other.amt + self.amt)
    return NotImplemented

but this does not:

def __add__(self, other: object) -> 'Currency':
    if isinstance(other, Currency) and isinstance(other, self.__class__):   # only expression order is flipped
        return self.__class__(other.amt + self.amt)
    return NotImplemented

throws: error: "object" has no attribute "amt"

Full code to reproduce it locally: https://gist.github.com/pirate/b3ef5a25449285dadc366dd926cebcea

The intention is to check that other is the same class as self (to prevent adding BTC to USD by accident). That check alone should imply that other is a subclass of Currency (since the __add__ method is defined on Currency), but it looks like I have to explicitly make both assertions, and make the Currency assertion after the self.__class__ one in order for mypy to narrow the type correctly.

Correct me if I'm mistaken, but I think there may be two bugs here:

  • The order of isinstance checks shouldn't matter (especially not in an and expression)
  • The isinstance(other, Currency) should not even be necessary, as it's implied that other is a Currency if other.__class__ == self.__class__

This issue discusses a vaguely similar type-narrowing problem, but I'm not sure if it's actually related: #2776

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions