-
Notifications
You must be signed in to change notification settings - Fork 237
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
Types for singleton objects #236
Comments
From the point of view of PEP 484 maybe it is better to have a more general construct that defines a type, variables of which can take only fixed set of values. In analogy with from typing import Value, Union
_empty = object()
def f(x: Union[int, None, Value[_empty]] = _empty) -> int:
... Or one can define a type alias Another argument for this is that I often use functions that can accept few such singletons, something like (this is almost my real code): task = object()
result = object()
def response(message):
if message is task:
...
elif message is result:
...
else:
... here |
I would love to find a way to do this that doesn't involve a PEP 484
update. Most solutions I can think of require defining a custom class,
which is kind of expensive, but otherwise would work:
class _empty: pass
def f(x: Union[int, None, Type[_empty]] = _empty) -> int:
...
|
@gvanrossum As I understand Jukka's point, time or memory are not the main problems here (these probably could be solved by if x is _empty:
... we are decomposing a union, then this workaround would be OK. Currently, if I use your way class _empty: pass
def f(x: Union[int, None, Type[_empty]] = _empty) -> int:
if x is _empty:
return 0
elif x is None:
return 1
else:
return x * 2 then mypy complains with
Jukka's idea with |
Why should we burden the user with specifying "internal" types?
|
@gvanrossum The @ilevkivskyi Yes, my main concerns are 1) how type checkers can detect that we are decomposing a union and 2) how easy and error-prone will it be to annotate or refactor existing code that uses the idiom. Performance is still a potential issue, but it's probably less likely to be a blocker. @markshannon Your suggestion would likely not work for more complex examples, and it's difficult for a type checker to decide if something is an internal thing that shouldn't be exposed in the type signature. Also, the special object doesn't need to be internal -- it could be part of a public API. Here are some use cases that I remember having seen in real code:
|
Maybe we can use an enum? Those can't be subclassed.
import enum
class Empty(enum.Enum):
token = 0
_empty = Empty.token
|
I like the idea with enums. It looks like it allows to not change the PEP and only change mypy. |
That sounds reasonable. Basically any enum value would then also be valid as a singleton type, in addition to being a regular value. It seems to be that this would still need a PEP change, as we'd need to declare enum values valid as types. Also, |
How does using enums allow the original example to be type annotated? If you ask people to change their APIs to fit the typechecker, then they'll just use |
The enum idea would still require a change to the original code, but it would be a more local change and wouldn't affect performance. However, if we'd still have to modify the PEP and |
On Wed, Jun 29, 2016 at 11:29 AM, Mark Shannon notifications@github.com
|
On Wed, Jun 29, 2016 at 11:57 AM, Jukka Lehtosalo notifications@github.com
If we write the code like I proposed, no change to typing.py should be --Guido van Rossum (python.org/~guido) |
They may well be willing to change it at Dropbox. Not everywhere is like Dropbox 😄 Using a unique sentinel object to represent the absence of an argument is a standard pattern, and there is nothing wrong with it. It is hard to claim that import enum
class Empty(enum.Enum):
token = 0
SENTINEL = Empty.token is an improvement over SENTINEL = object()
A good reason to keep typing as minimal as possible. IMO, the typing module should do nothing, apart from preventing NameErrors when using PEP 484 type annotations. |
|
You'd have to use `Union[Empty, int]`.
Of course if we don't like this we should just bite the bullet and change
the PEP and typing.py. It's just more work and I'm not sure what the
priority is.
|
I think that the idea proposed by @gvanrossum is a reasonable compromise:
from enum import Enum
from typing import Union
class Reason(Enum):
timeout = 1
error = 2
def process(response: Union[int, Reason] = 0) -> int:
if result is Reason.timeout:
return 1
elif result is Reason.error:
return 2
else:
# at this point we know that result could be only int
return x * 5 |
I agree that the final idea by @gvanrossum seems pretty reasonable, and not too difficult to add to type checkers. If it seems that users don't like it we can fall back to a more general solution, but it would be there for those who really need it without having to wait for a However, I'd expect that this wouldn't be intuitive for users, as this does not follow the established convention. Tools that support the new convention should likely document this explicitly and explain how to refactor code that uses the |
Yeah, this sounds like a good first step. @ilevkivskyi would you be interested in drafting something for the PEP? |
OK, sure, I will make a PR by Monday. |
Closed by #240. (We're still waiting for implementation in mypy: python/mypy#1803) |
Suggested here: python/typing#236 (comment)
I repeatedly encounter code that uses a singleton instance that is only used to mark some special condition. Here is an example:
Currently PEP 484 doesn't provide primitives for type checking such code precisely. Workarounds such as casts or declaring the type of the special object as
Any
are possible. We can't use the typeobject
in a union type, since that would cover all possible values, asobject
is the common base class of everything.We could refactor the code to use a separate class for the special object and use
isinstance
to check for the special value, but this may require changing the code in non-trivial ways for larger examples and carries some runtime overhead asisinstance
checks are slower thanis
operations. Example:A potentially better way is to support user-defined types that only include a single instance. This can be seen as a generalization of the
None
type. Here is a potential way to use this to type check the example:The final example is better than second example in a few ways:
The text was updated successfully, but these errors were encountered: