Skip to content

Commit 2070452

Browse files
committed
enh: namelambda: be more FP, return a modified copy instead of mutating the original function object
1 parent 1cdb41e commit 2070452

File tree

3 files changed

+12
-9
lines changed

3 files changed

+12
-9
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2426,6 +2426,8 @@ assert tuple(myzip(lol)) == ((1, 3, 5), (2, 4, 6))
24262426
24272427
*Changed in v0.13.0.* Now supports renaming any function object (``isinstance(f, (types.LambdaType, types.FunctionType))``), and will rename a lambda even if it has already been named.
24282428
2429+
*Changed in v0.13.1.* Now the return value is a modified copy; the original function object is not mutated.
2430+
24292431
For those situations where you return a lambda as a closure, call it much later, and it happens to crash - so you can tell from the stack trace *which* of the *N* lambdas in the codebase it is.
24302432
24312433
For technical reasons, ``namelambda`` conforms to the parametric decorator API. Usage:
@@ -2440,7 +2442,7 @@ kaboom = namelambda("kaboom")(lambda: some_typoed_name)
24402442
kaboom() # --> stack trace, showing the function name "kaboom"
24412443
```
24422444
2443-
The first call returns a *foo-renamer*, which takes a function object and renames it to have the name *foo*.
2445+
The first call returns a *foo-renamer*, which takes a function object and returns a copy that has its name changed to *foo*.
24442446
24452447
Technically, this updates ``__name__`` (the obvious place), ``__qualname__`` (used by ``repr()``), and ``__code__.co_name`` (used by stack traces).
24462448

macro_extras/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,6 @@ In the second example, returning ``x`` separately is redundant, because the assi
524524

525525
### ``namedlambda``: auto-name your lambdas
526526

527-
*Changed in v0.13.0.* Env-assignments are now processed lexically, just like regular assignments. Added support for let-bindings.
528-
529527
Who said lambdas have to be anonymous?
530528

531529
```python
@@ -544,11 +542,11 @@ with namedlambda:
544542
foo = let[(f7, lambda x: x) in f7] # let-binding: name as "f7"
545543
```
546544

547-
Lexically inside a ``with namedlambda`` block, any literal ``lambda`` that is assigned to a name using one of the supported assignment forms is named to have the name of the LHS of the assignment. The name is captured at macro expansion time. Naming modifies the original function object.
545+
Lexically inside a ``with namedlambda`` block, any literal ``lambda`` that is assigned to a name using one of the supported assignment forms is named to have the name of the LHS of the assignment. The name is captured at macro expansion time.
548546

549547
Decorated lambdas are also supported, as is a ``curry`` (manual or auto) where the last argument is a lambda. The latter is a convenience feature, mainly for applying parametric decorators to lambdas. See [the unit tests](../unpythonic/syntax/test/test_lambdatools.py) for detailed examples.
550548

551-
The naming is performed using the function ``unpythonic.misc.namelambda``, which will update ``__name__``, ``__qualname__`` and ``__code__.co_name``.
549+
The naming is performed using the function ``unpythonic.misc.namelambda``, which will return a renamed copy with its ``__name__``, ``__qualname__`` and ``__code__.co_name`` changed.
552550

553551
Supported assignment forms:
554552

@@ -560,6 +558,10 @@ Supported assignment forms:
560558

561559
Support for other forms of assignment might or might not be added in a future version.
562560

561+
*Changed in v0.13.0.* Env-assignments are now processed lexically, just like regular assignments. Added support for let-bindings.
562+
563+
*Changed in v0.13.1.* Now the return value of ``namelambda``, which this uses internally, is a modified copy; the original function object is not mutated.
564+
563565

564566
### ``quicklambda``: combo with ``macropy.quick_lambda``
565567

unpythonic/misc.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from types import LambdaType, FunctionType, CodeType
77
from time import time
8+
from copy import copy
89

910
from .regutil import register_decorator
1011
from .lazyutil import mark_lazy, lazycall, force
@@ -246,9 +247,6 @@ def p(loop, item, acc):
246247
def namelambda(name):
247248
"""Rename a function. Decorator.
248249
249-
The original function object is modified in-place; for convenience,
250-
the object is returned.
251-
252250
This can be used to give a lambda a meaningful name, which is especially
253251
useful for debugging in cases where a lambda is returned as a closure,
254252
and the actual call into it occurs much later (so that if the call crashes,
@@ -260,7 +258,7 @@ def namelambda(name):
260258
foo = namelambda("foo")(lambda ...: ...)
261259
262260
The first call returns a *foo-renamer*, and supplying the lambda to that
263-
actually modifies the lambda to have the name *foo*.
261+
actually returns a lambda that has the name *foo*.
264262
265263
This is used internally by some macros (``namedlambda``, ``let``, ``do``),
266264
but also provided as part of unpythonic's public API in case it's useful
@@ -283,6 +281,7 @@ def namelambda(name):
283281
def rename(f):
284282
if not isinstance(f, (LambdaType, FunctionType)):
285283
return f
284+
f = copy(f)
286285
# __name__ for tools like pydoc; __qualname__ for repr(); __code__.co_name for stack traces
287286
# https://stackoverflow.com/questions/40661758/name-of-a-python-function-in-a-stack-trace
288287
# https://stackoverflow.com/questions/16064409/how-to-create-a-code-object-in-python

0 commit comments

Comments
 (0)