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

Opt frozen dataclass #272

Merged
merged 3 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
schedule:
- cron: '17 3 * * 0'

concurrency:
group: ${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
ruff:
name: Ruff
Expand Down
50 changes: 49 additions & 1 deletion pytools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
TypeVar,
)

from typing_extensions import dataclass_transform


# These are deprecated and will go away in 2022.
all = builtins.all
Expand Down Expand Up @@ -191,6 +193,11 @@
.. autofunction:: unique_intersection
.. autofunction:: unique_union

Functionality for dataclasses
-----------------------------

.. autofunction:: opt_frozen_dataclass

Type Variables Used
-------------------

Expand Down Expand Up @@ -903,7 +910,7 @@ def new_inner(*args: P.args, **kwargs: P.kwargs) -> R:
return new_inner


class keyed_memoize_in(Generic[P, R]): # noqa: N801
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not needed? I thought it constrained the __call__ inputs

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem I encountered is that R does not get bound at construction time, and so mypy helpfully binds it to Never, which it expects as the return value of the function. pyright seemed to agree that R would not get bound.

__call__ is still an R-generic function.

class keyed_memoize_in(Generic[P]): # noqa: N801
"""Like :class:`memoize_in`, but additionally uses a function *key* to
compute the key under which the function result is memoized.

Expand Down Expand Up @@ -2973,6 +2980,47 @@ def unique_union(*args: Iterable[T]) -> Collection[T]:
# }}}


@dataclass_transform(frozen_default=True)
def opt_frozen_dataclass(
*,
init: bool = True,
repr: bool = True,
eq: bool = True,
order: bool = False,
unsafe_hash: bool = False,
match_args: bool = True,
kw_only: bool = False,
slots: bool = False,
# Added in 3.11.
# weakref_slot: bool = False
) -> Callable[[type[T]], type[T]]:
"""Like :func:`dataclasses.dataclass`, but marks the dataclass frozen
only if :data:`__debug__` is active. Frozen dataclasses have a ~20%
cost penalty (from having to call :meth:`object.__setattr__`) that
this decorator avoid when the interpreter runs with "optimization"
enabled.

.. versionadded:: 2024.1.18
"""
def map_cls(cls: type[T]) -> type[T]:
from dataclasses import dataclass
return dataclass(
init=init,
repr=repr,
eq=eq,
order=order,
unsafe_hash=unsafe_hash,
frozen=__debug__,
match_args=match_args,
kw_only=kw_only,
slots=slots,
# Added in 3.11.
# weakref_slot=weakref_slot,
)(cls)

return map_cls


def _test():
import doctest
doctest.testmod()
Expand Down
Loading