Skip to content

TypingDict Default Arguments #12980

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
adam-grant-hendry opened this issue Jun 14, 2022 · 9 comments
Closed

TypingDict Default Arguments #12980

adam-grant-hendry opened this issue Jun 14, 2022 · 9 comments
Labels

Comments

@adam-grant-hendry
Copy link

adam-grant-hendry commented Jun 14, 2022

Feature

Issue #6131 request default types be added to TypedDict. To separate concerns, I am interested in being able to specify default arguments to TypedDict (as suggested in one of the comments).

Pitch

Default arguments are a feature of python. Currently, there's no support for default arguments in TypedDict. This would be helpful both for new and existing codebases.

Bonus: A common beginner blunder is to use mutable default arguments in functions and methods. AFAIK, linters (flake8, pylint) do not yet warn about this. It would be interesting if mypy (and other static type checkers) could detect this and provide a useful warning.

Syntax Idea

Borrowing from the comment suggestion, the following is intuitively what is trying to be achieved (though I don't know how this would be implemented):

class DefaultArgs(TypedDict):
    arg1: int = 1
    arg2: bool = True

The closest I am able to achieve currently is by using Annotated types, but it doesn't seem the intended use case for Annotated and I would prefer a more intuitive name like "Default":

from __future__ import annotations

from typing import Annotated

class DefaultArgs(TypedDict):
    arg1: Annotated[int, 1]
    arg2: Annotated[bool, True]
@JelleZijlstra
Copy link
Member

Could you give some examples of what you want to work? (Also, mutable defaults seem like an unrelated issue, so let's discuss that elsewhere.)

@adam-grant-hendry
Copy link
Author

adam-grant-hendry commented Jun 14, 2022

Could you give some examples of what you want to work? (Also, mutable defaults seem like an unrelated issue, so let's discuss that elsewhere.)

Of course. Assume I am tasked with type hinting a 3rd-party library that wraps a function using *args and **kwargs. The pytest-qt module is a good example:

@staticmethod
def keyClick(*args, **kwargs):
    qt_api.QtTest.QTest.keyClick(*args, **kwargs)

Here qt_api acts like qtpy, abstracting away differences between PyQt5, PySide2, PyQt6, and PySide6. (It predates qtpy).

The actual signatures of keyClick from the Qt C++ source are given here:

void keyClick(QWidget *widget, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1)
void keyClick(QWidget *widget, char key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1)
void keyClick(QWindow *window, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1)
void keyClick(QWindow *window, char key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1)

As you can see, two arguments here have default values.

I think currently default arguments might be possible using the Annotated type, but I don't think that was necessarily its intended purpose as specified in PEP 593 so it doesn't feel pythonic to use:

# python 3.8.10
from __future__ import annotations

from typing import Annotated, TypedDict

from qt_api import QtCore, QtGui, QtWidgets
from typing_extensions import Unpack

class KeyClickArgs(TypedDict):
    widget: QtWidgets.QWidget | QtGui.QWindow
    key: QtCore.Qt.Key | str
    modifier: Annotated[QtCore.Qt.KeyboardModifiers, QtCore.Qt.NoModifier]
    delay: Annotated[int, -1]

@adam-grant-hendry
Copy link
Author

adam-grant-hendry commented Jun 14, 2022

it doesn't feel pythonic to use

I'm sure I could also simply specify:
DefaultArg = Annotated

for clarity

Actually, I can't use a variable unless it's a type alias, so I would have to specify the indices to Annotated. I have to leave it as Annotated for now.

@adam-grant-hendry
Copy link
Author

adam-grant-hendry commented Jun 14, 2022

@JelleZijlstra In addition, is there a way to specify default arguments to `TypeVarTuple` types?

I have some C++ function wrappers that do not support keyword arguments (C++ doesn't natively), but do have default values.

See #12981

@adam-grant-hendry
Copy link
Author

adam-grant-hendry commented Jun 18, 2022

From PEP 593:

Specifically, a type T can be annotated with metadata x via the typehint Annotated[T, x]. This metadata can be used for either static analysis or at runtime.

and the Consuming annotation section

Ultimately, the responsibility of how to interpret the annotations (if at all) is the responsibility of the tool or library encountering the Annotated type.

It would be great if Unpack (or the language grammar change) could parse Annotated types specified in a TypeDict and use them to create default arguments. Ideally, it would be even better if TypedDict could support a syntax like

```python
class DefaultArgs(TypedDict):
    arg1: int = 1
    arg2: bool = True

and under the hood wrap them in a mechanism like Annotated to be used to generate default arguments.

@adam-grant-hendry
Copy link
Author

(Also, mutable defaults seem like an unrelated issue, so let's discuss that elsewhere.)

That's fine. That was just a thought really. It would be up to a linting tool to decide whether or not to warn for that anyway.

@adam-grant-hendry
Copy link
Author

As a compromise, perhaps rather than specifying a literal as the default value, we could adopt the ellipses instead to be used within the TypedDict and leave it up to the implementaion to define the default in the function signature:

class KeyClickArgs(TypedDict):
    widget: QtWidgets.QWidget | QtGui.QWindow
    key: QtCore.Qt.Key | str
    modifier: QtCore.Qt.KeyboardModifiers = ...
    delay: int = ...

@adam-grant-hendry
Copy link
Author

adam-grant-hendry commented Jun 19, 2022

As a compromise, perhaps rather than specifying a literal as the default value, we could adopt the ellipses instead to be used within the TypedDict and leave it up to the implementaion to define the default in the function signature:

class KeyClickArgs(TypedDict):
    widget: QtWidgets.QWidget | QtGui.QWindow
    key: QtCore.Qt.Key | str
    modifier: QtCore.Qt.KeyboardModifiers = ...
    delay: int = ...

Or perhaps instead, this is the reason why Required and NotRequired exist? Should instead I be using:

class KeyClickArgs(TypedDict):
    widget: Required[QtWidgets.QWidget | QtGui.QWindow]
    key: Required[QtCore.Qt.Key | str]
    modifier: NotRequired[QtCore.Qt.KeyboardModifiers]
    delay: NotRequired[int]

or perhaps even simply using the total argument, though it feels less explicit:

class KeyClickArgs(TypedDict, total=False):
    widget: QtWidgets.QWidget | QtGui.QWindow
    key: QtCore.Qt.Key | str
    modifier: QtCore.Qt.KeyboardModifiers  # Default argument
    delay: int  # Default argument

@erictraut
Copy link

I think we established in this discussion that default arguments don't make sense for TypedDict, which is a structural overlay on top of dict. This feature request was based on a misunderstanding of TypedDict. I recommend closing it to avoid future confusion.

@AlexWaygood AlexWaygood closed this as not planned Won't fix, can't repro, duplicate, stale Jun 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants