-2

My intended behavior is:

>>> x = 0
>>> with (x := 1): print(x)
1
>>> print(x)
0

However I get the expected AttributeError: __enter__ error. Is there an easy way to achieve this, or something similar that lets me compensate for not having Lisp-style let expression?

P.S. I know I can do something like:

class Let(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
    def __enter__(self):
        return self
    def __exit__(self, *args):
        return None

And then:

>>> with Let(x=1) as let: print(let.x)

Or:

>>> with (let := Let(x=1)): print(let.x)

But having to say let.x as opposed to just x makes it too ugly!

Atonal
  • 430
  • 3
  • 12
  • You did not explain what problem you are trying to solve with that. – Klaus D. Feb 08 '20 at 08:24
  • I don’t think that’s possible, as `with` doesn’t introduce a new scope, and won’t change the behavior of variable scoping. – deceze Feb 08 '20 at 08:28
  • 1
    You're trying to use `with` statement just for variable assignment. – Olvin Roght Feb 08 '20 at 08:28
  • I can understand temporarily patching an attribute like `sys.stdout`, but why would you want to temporarily assign a local variable? – Alex Hall Feb 08 '20 at 08:30
  • Possible duplicate of https://stackoverflow.com/questions/541926/how-can-one-create-new-scopes-in-python – tripleee Feb 08 '20 at 08:48
  • @KlausD. I did. My desired behavior is the thing I was trying to achieve. Where I'm applying it is irrelevant. – Atonal Feb 08 '20 at 08:54
  • @AlexHall The use case is the same as :=. In assignment expression's PEP examples you see if statements and while loops, but anything more complicated than that, := wouldn't work. – Atonal Feb 08 '20 at 08:55
  • @tripleee Helpful explanation. But It's not a duplicate. Refer to my answer. – Atonal Feb 08 '20 at 08:55
  • if you feel that the nominated duplicate should have one more answer, then post it there. The point of duplicates is to collect information about one topic in one place. – tripleee Feb 08 '20 at 09:07
  • @tripleee If I felt it's a duplicate, I would've deleted this question and posted the answer there. – Atonal Feb 08 '20 at 09:15
  • @Atonal that answer doesn't make sense. The many use cases of := have nothing to do with temporary assignment. – Alex Hall Feb 08 '20 at 09:53

3 Answers3

0

:= operation isn't like let in JavaScript, it's a operation to assign a value to a variable and output the value at the same time. Loosely speaking, let is declare a local variable ie. you can't use this variable outside the scope.

I'm not sure what you mean by 'let expression', but := is different from scope differentiated assignment.

Peter
  • 52
  • 2
  • By `let expression`, I mean a lisp let expression (as it says in the question). But I'm not sure you understood the what I asked. I'm obviously not talking about Javascript, I want to define a variable in an inner scope which may or may not have been defined in the outer scope. – Atonal Feb 08 '20 at 08:47
0

Using your existing Let class, you could change the string representation of the class using __str__ (as per this answer). Then just call your instance ‘x’ and print(x) will work

-1

I don't know why I didn't find this before posting this question (https://nvbn.github.io/2014/09/25/let-statement-in-python/)

It behaves just like I want:

from contextlib import contextmanager
from inspect import currentframe, getouterframes

@contextmanager
def let(**bindings):
    frame = getouterframes(currentframe(), 2)[-1][0]
    locals_ = frame.f_locals
    original = {var: locals_.get(var) for var in bindings.keys()}
    locals_.update(bindings)
    yield
    locals_.update(original)


x = 0

with let(x=1):
    print(x)

print(x)

It needs tweaking a bit, though. For example undefined variables return None within let, as opposed to a NameError.

I still don't know how to do recursive lets without wrapping it in a function. I.e. something like:

>>> with let(x=1, y=x+1):
       print(x, y)
1 2

Inputs are appreciated!

Update

This answer won't work properly, because you can't update locals()... well, back to square one.

Atonal
  • 430
  • 3
  • 12
  • 1
    This doesn't work, as you can't update the locals dictionary in a function: https://repl.it/@alexmojaki/DoubleVacantParallelcompiler It only seems to work in your test because at the module level globals and locals are the same and the globals dictionary can be updated. – Alex Hall Feb 08 '20 at 09:51
  • Thanks! I didn't know I can't update the locals dictionary in Python 3.x. But it seems I can't even do `exec (compile('x = 1', '', 'single'), frame.f_locals, frame.f_globals)`. It seems there's really no way to do this. P.S. The code has one more error, the captured frame for your example was incorrect for me... which needs modifying the indices of getuterframes' output, based on the lineno. – Atonal Feb 08 '20 at 12:23