Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should int be compatible with float and mypy's @ducktype decorator #48

Closed
JukkaL opened this issue Jan 17, 2015 · 6 comments
Closed

Should int be compatible with float and mypy's @ducktype decorator #48

JukkaL opened this issue Jan 17, 2015 · 6 comments

Comments

@JukkaL
Copy link
Contributor

JukkaL commented Jan 17, 2015

Mypy treats int as compatible with float (and float as compatible with complex) even though the types aren't ordinary subtypes of each other. This is implemented via a @ducktype class decorator that allows specifying that a class should be treated essentially as a subclass of another class, even though there is no concrete subclass relationship and even if the interfaces aren't actually fully compatible. The mypy stub for int looks like this (a bit simplified):

@ducktype(float)
class int(... a bunch of ABCs ...):
    ...

This could also be useful for things like UserString (which is like str but not a subclass) and mock classes in tests.

At the least, we should specify int as compatible with float so that people won't have to write code like this:

Float = Union[int, float]

def myfunc(x: Float) -> float:
    return x * 5.0 - 2.0

Of course, all stubs for built-in modules would have to use unions everywhere for floating point arguments for the above to work in practice, and I'd like to avoid that as it seems like extra complexity with little benefit. The above would not let use some float methods for values with the union type (such as hex() which is only defined for float objects but not int objects).

If using the ducktype approach (or special casing some classes to be compatible and not making ducktype public) all float methods would be available, but they might fail at runtime if the actual object is an int. I think this is reasonable since these methods are pretty rarely used.

As a bonus, the ducktype decorator would effectively make all classes abstract, hopefully helping address the abstract vs. concrete type problem (see #12, for example).

@gvanrossum
Copy link
Member

There's also the number module which defines ABCs corresponding to complex, float, int (named Complex, Real, Integral -- this was introduced in PEP 3141). So while int isn't a subclass of float, it's a subclass of number.Real. And we worked hard in Python 3 to make sure that an int is always acceptable where a float is expected, and an int or float is acceptable where a complex is expected. (This is why int division changed!) IOW I agree that we should somehow make sure that the type checker supports this. And I think it's acceptable despite the lack of int.hex(). (Maybe it's a bug?)

I'm not sure that @ducktype is the right term, but I'm out of imagination. I think it should only be supported in stub modules. I think UserString is a losing proposition; the 3.x docs say it's deprecated. I'm not sure what to do about mocks -- they seem to open a whole different can of worms that I'd rather keep closed.

@abarnert
Copy link

I think there's a very limited set of such relationships that isn't extensible, so maybe instead of an extensible feature like @ducktype there should just be a hardcoded set of rules, at least in 3.5:

  • Numbers can be promoted up the tower (int for float, float for complex).
  • A bytearray can be used as a bytes (and maybe a set as a frozenset?).

Are there any other such cases? Is there any reason a third-party library might want to add any?

If you do want such a feature, maybe @promote or something similar is a better name. After all, the paradigm case is C-style automatic numeric promotion. (For the other two potential cases, I think there is actually a clear subtype relationship, even if it isn't reflected in the language.)

@JukkaL
Copy link
Contributor Author

JukkaL commented Mar 28, 2015

I recently removed mypy's @ducktype decorator (at least from the public interface), and replaced it with a hard coded set of promotions (int -> float and float -> complex).

What about promoting from bytearray to bytes, as mentioned by @abarnert ? This wouldn't be generally valid, esp. for C modules (e.g., os.path.join doesn't accept bytearray arguments), but we might still want it.

Another similar issue is int -> decimal.Decimal. Mypy currently requires using something like Union[Decimal, int] (potentially with a type alias) to support this. This case may be rare enough that using a union may be a reasonable solution for this.

@gvanrossum
Copy link
Member

I like promoting from bytearray to bytes; can we also promote from memoryview to bytes? Maybe we can even fix os.path.join()?

I propose not to bother with Decimal -- it intentionally (if awkwardly) lives outside the numeric tower defined by PEP 3141. We'll see if the users push back.

@gvanrossum gvanrossum added the bug label May 18, 2015
gvanrossum pushed a commit that referenced this issue May 18, 2015
gisle added a commit to gisle/typing-consistency that referenced this issue Feb 5, 2020
@johnthagen
Copy link

For what it's worth, I found it surprising that when I created a dataclass like:

@dataclass(frozen=True)
class Position:
    x: float
    y: float

mypy didn't catch this typo I made:

p = Position(0.0, 0)

@gvanrossum
Copy link
Member

@johnthagen mypy considers int a subclass of float. You can read about it in PEP 484. Most people would consider this correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants