Использование выражений присваивания Python 3.8 в качестве выражения let?

Мое предполагаемое поведение:

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

Однако я получаю ожидаемую ошибку AttributeError: __enter__. Есть ли простой способ добиться этого или что-то подобное, что позволяет мне компенсировать отсутствие выражения let в стиле Lisp?

P.S. Я знаю, что могу сделать что-то вроде:

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

А потом:

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

Or:

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

Но необходимость говорить let.x, а не просто x, делает это слишком уродливым!


person Atonal    schedule 08.02.2020    source источник
comment
Вы не объяснили, какую проблему вы пытаетесь решить с помощью этого.   -  person Klaus D.    schedule 08.02.2020
comment
Я не думаю, что это возможно, поскольку with не вводит новую область видимости и не меняет поведение области видимости переменных.   -  person deceze♦    schedule 08.02.2020
comment
Вы пытаетесь использовать оператор with только для назначения переменной.   -  person Olvin Roght    schedule 08.02.2020
comment
Я могу понять временное исправление атрибута, такого как sys.stdout, но зачем вам временно назначать локальную переменную?   -  person Alex Hall    schedule 08.02.2020
comment
Возможный дубликат stackoverflow .com/questions/541926/   -  person tripleee    schedule 08.02.2020
comment
@Клаус Д. Я сделал. Мое желаемое поведение — это то, чего я пытался достичь. Где я применяю это не имеет значения.   -  person Atonal    schedule 08.02.2020
comment
@AlexHall Вариант использования такой же, как :=. В примерах выражения присваивания PEP вы видите операторы if и циклы while, но что-то более сложное, чем это, := не будет работать.   -  person Atonal    schedule 08.02.2020
comment
@tripleee Полезное объяснение. Но это не дубликат. Обратитесь к моему ответу.   -  person Atonal    schedule 08.02.2020
comment
если вы чувствуете, что в назначенном дубликате должен быть еще один ответ, то опубликуйте его там. Суть дубликатов в том, чтобы собрать информацию по одной теме в одном месте.   -  person tripleee    schedule 08.02.2020
comment
@tripleee Если бы я чувствовал, что это дубликат, я бы удалил этот вопрос и разместил там ответ.   -  person Atonal    schedule 08.02.2020
comment
@Atonal, этот ответ не имеет смысла. Многие варианты использования := не имеют ничего общего с временным назначением.   -  person Alex Hall    schedule 08.02.2020


Ответы (3)


:= операция не похожа на let в JavaScript, это операция по присвоению значения переменной и одновременному выводу значения. Грубо говоря, let объявляет локальную переменную, т.е. вы не можете использовать эту переменную вне области видимости.

Я не уверен, что вы подразумеваете под «выражением let», но := отличается от дифференцированного назначения области.

person Peter    schedule 08.02.2020
comment
Под let expression я подразумеваю выражение lisp let (как сказано в вопросе). Но я не уверен, что вы поняли, о чем я спросил. Я, очевидно, не говорю о Javascript, я хочу определить переменную во внутренней области, которая может быть или не быть определена во внешней области. - person Atonal; 08.02.2020

Используя существующий класс Let, вы можете изменить строковое представление класса, используя __str__ (согласно этот ответ). Затем просто назовите свой экземпляр «x», и print(x) будет работать.

person HydraulicSheep    schedule 08.02.2020

Я не знаю, почему я не нашел этого перед публикацией этого вопроса (https://nvbn.github.io/2014/09/25/let-statement-in-python/)

Он ведет себя так, как я хочу:

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)

Однако его нужно немного доработать. Например, неопределенные переменные возвращают None внутри let, а не NameError.

Я до сих пор не знаю, как делать рекурсивные давайте, не оборачивая их в функцию. т.е. что-то типа:

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

Вклад приветствуется!

Обновить

Этот ответ не будет работать должным образом, потому что вы не можете обновить locals()... ну, вернемся к исходной точке.

person Atonal    schedule 08.02.2020
comment
Это не работает, так как вы не можете обновить словарь местных жителей в функции: repl.it/@alexmojaki /DoubleVacantParallelcompiler Кажется, что это работает только в вашем тесте, потому что на уровне модуля глобальные и локальные переменные одинаковы, и словарь глобальных переменных можно обновлять. - person Alex Hall; 08.02.2020
comment
Спасибо! Я не знал, что не могу обновить словарь местных жителей в Python 3.x. Но, похоже, я даже exec (compile('x = 1', '<string>', 'single'), frame.f_locals, frame.f_globals) не умею. Кажется, что на самом деле нет никакого способа сделать это. P.S. В коде есть еще одна ошибка, захваченный кадр для вашего примера был неправильным для меня... что требует изменения индексов вывода getuterframes на основе lineno. - person Atonal; 08.02.2020