Thanks to visit codestin.com
Credit goes to github.com

Skip to content

random.Random.getvalue and random.Random.setvalue are unduly opinionated about their types and ill-suited for subclassing #6063

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

Closed
posita opened this issue Sep 22, 2021 · 6 comments
Labels
reason: inexpressible Closed, because this can't be expressed within the current type system

Comments

@posita
Copy link
Contributor

posita commented Sep 22, 2021

If i understand the docs correctly, this …

Class Random can also be subclassed if you want to use a different basic generator of your own devising: in that case, override the random(), seed(), getstate(), and setstate() methods. Optionally, a new generator can supply a getrandbits() method — this allows randrange() to produce selections over an arbitrarily large range.

random.getstate()
Return an object capturing the current internal state of the generator. This object can be passed to setstate() to restore the state.

random.setstate(state)
state should have been obtained from a previous call to getstate(), and setstate() restores the internal state of the generator to what it was at the time getstate() was called.

… strongly suggests that the return value of getstate() (and corresponding argument to setstate()) should be opaque and treated as implementation specific. However, typeshed seems to want to enforce that they should be Tuples.

This leads to warnings for subclasses who, e.g., capture state as bytes or a dict. E.G.:

…/foo.py:57: error: Return type "_RandState" of "getstate" incompatible with return type "Tuple[Any, ...]" in supertype "Random"
…/foo.py:57: error: Return type "_RandState" of "getstate" incompatible with return type "Tuple[int, ...]" in supertype "Random"

(Not sure what that second warning is or where it came from.)

@hauntsaninja
Copy link
Collaborator

Seems like another use case for defaulted generics.

@srittau
Copy link
Collaborator

srittau commented Sep 23, 2021

For now, the best we could do is to use Any to prevent false positives? But in this particular case, for now, I believe we should keep the stubs as is, and rely on users overriding these methods to use # type: ignore.

@srittau srittau added the reason: inexpressible Closed, because this can't be expressed within the current type system label Sep 23, 2021
@posita
Copy link
Contributor Author

posita commented Sep 23, 2021

How would others define an interface with an opaque state that could be emitted/passed back? Or is that the point of "defaulted generics" and you want to preserve specific type checking for the standard library implementation until that arrives?

Just curious, but if the state is supposed to be opaque (I'm assuming even for the standard library), why preserve type checking around implementation details that leave and return via getstate/setstate? Aren't those properly accounted for inside the implementation/tests rather than public interfaces?

@srittau
Copy link
Collaborator

srittau commented Sep 23, 2021

An opaque state could be defined in several ways: Either using the concrete type as done here. But a better way would probably be to use NewType, which forces the opaque type to match, without necessarily revealing its internals. This doesn't help when overriding the class, though.

@posita
Copy link
Contributor Author

posita commented Sep 23, 2021

Right, so why not strive to keep implementation typing separate from interface typing?

from collections.abc import Mapping as MappingC
from typing import Dict, Mapping, NewType, Protocol

_OpaqueT = NewType("_OpaqueT", object)

class Foo(Protocol):
  def getblob(self) -> _OpaqueT:
    ...
  def setblob(self, blob: _OpaqueT) -> None:
    ...

class FooImpl(Foo):
  def __init__(self):
    self._blob: Dict = {}
  @property
  def foo_impl_blob(self) -> Mapping:
    return self._blob
  def getblob(self) -> _OpaqueT:
    return _OpaqueT(self._blob)
  def setblob(self, blob: _OpaqueT) -> None:
    assert isinstance(blob, MappingC)
    self._blob = dict(blob)

foo = FooImpl()
blob: _OpaqueT = foo.getblob()
assert "missing" not in blob  # Unsupported right operand type for in ("_OpaqueT") 🙌 🎉
foo.setblob(blob)
assert "missing" not in foo.foo_impl_blob  # totally cool

Or are you saying that the current implementation of random.Random leaks into the interface and implementation maintainers depend on those leaks, so there's little typeshed can do on the side to paper over that dysfunction?

@srittau
Copy link
Collaborator

srittau commented Sep 27, 2021

As this is not something we can express in typeshed right now, but could be improved if typing improves, I've marked this as inexpressable and am closing this per our policy. This can be revisited if typing improves.

@srittau srittau closed this as completed Sep 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
reason: inexpressible Closed, because this can't be expressed within the current type system
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants