optimize `@util.decorator`
### Description
util.decorator uses code generation + eval to create signature matching wrapper.
It consumes some CPU because we can not use pyc cache.
Additionally, each wrapped function has own globals for function annotations.
By stripping function annotations from eval-ed code, compile time and memory usage are saved.
```python
from sqlalchemy.util import decorator
from sqlalchemy import *
import timeit
import tracemalloc
import sqlalchemy.orm._orm_constructors
@decorator
def with_print(fn, *args, **kwargs):
res = fn(*args, **kwargs)
print(f"{fn.__name__}(*{args}, **{kwargs}) => {res}")
return res
# test
PI = 3.14
def f():
@with_print
def add(x: int|float, *, y: int|float=PI) -> int|float:
return x + y
return add
add = f()
add(1)
print(add.__annotations__)
# benchmark
print(timeit.timeit(f, number=1000)*1000, "us")
# memory
tracemalloc.start(1)
[f() for _ in range(1000)]
mem, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"{mem=}, {peak=}")
```
Result:
```
$ .venv/bin/python -VV
Python 3.14.0a6 (main, Mar 17 2025, 21:27:10) [Clang 20.1.0 ]
$ .venv/bin/python sample.py
add(*(1,), **{'y': 3.14}) => 4.
140000000000001
{'x': int | float, 'y': int | float, 'return': int | float}
35.
93937499681488 us
mem=
9252896, peak=
9300808
$ git switch -
Switched to branch 'opt-decorator'
$ .venv/bin/python sample.py
add(*(1,), **{'y': 3.14}) => 4.
140000000000001
{'x': int | float, 'y': int | float, 'return': int | float}
23.
32574996398762 us
mem=
1439032, peak=
1476423
```
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [ ] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [x] A short code fix
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
Closes: #12502
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/12502
Pull-request-sha:
34409cbbfd2dee65bf86a85a87e415c9af47dc62
Change-Id: I88b88eb6eb018608bc2881459f58564881d06641