Можем ли мы иметь уступку в состоянии?

92

Возможна ли уступка в состоянии?

Например,

if (a=some_func()):
    # Use a
Vishal
источник
Один и тот же ответ на два вопроса не означает, что они дублируются. Нетривиально ответить самому себе, но нетривиально понять аргументы или если есть способ обойти эту проблему.
Мехмет

Ответы:

111

Почему бы не попробовать?

>>> def some_func():
...   return 2
... 
>>> a = 2
>>> if (a = some_func()):
  File "<stdin>", line 1
    if (a = some_func()):
          ^
SyntaxError: invalid syntax
>>> 

Так что нет.

Обновление: это возможно (с другим синтаксисом) в Python 3.8

Джейсон Холл
источник
35
это намеренно запрещено, поскольку Гвидо, доброжелательный диктатор питонов, считает их ненужными и более запутывающими, чем полезными. По той же причине нет операторов пост-инкремента или пре-инкремента (++).
Мэтт Бём
4
он позволяет добавлять дополненную распайку в 2.0 , потому что x = x + 1требует дополнительного времени поиска , когда x += 1был несколько быстрее, но я уверен , что он даже не нравится делать , что много. :-)
wescpy
надеюсь, это скоро станет возможным. Python просто медленно развивается
Ник О'Лай
4
«почему бы не попробовать» - потому что кто знает, каков может быть синтаксис? Может быть, OP попробовал это, и это не сработало, но это не значит, что синтаксис не отличается, или что нет способа сделать это, не предназначенного
Леви Х,
57

ОБНОВЛЕНИЕ - исходный ответ находится внизу

Python 3.8 принесет PEP572

Аннотация
Это предложение по созданию способа присвоения переменных в выражении с использованием обозначения NAME: = expr. Добавлено новое исключение TargetScopeError и одно изменение в порядке оценки.

https://lwn.net/Articles/757713/

«Беспорядок в PEP 572» был темой сессии Python Language Summit 2018, которую провел благотворительный пожизненный диктатор (BDFL) Гвидо ван Россум. PEP 572 стремится добавить к языку выражения присваивания (или «встроенные присваивания»), но он видел продолжительное обсуждение множества огромных потоков в списке рассылки python-dev - даже после нескольких раундов на python-idea. Эти темы часто были спорными и были явно объемными до такой степени, что многие, вероятно, просто игнорировали их. На саммите Ван Россум сделал обзор предложения по функциям, который он, кажется, склонен принять, но он также хотел обсудить, как избежать такого рода всплесков потоков в будущем.

https://www.python.org/dev/peps/pep-0572/#examples-from-the-python-standard-library

Примеры из стандартной библиотеки Python

site.py env_base используется только в этих строках, а его назначение if перемещает его как «заголовок» блока.

Ток:

env_base = os.environ.get("PYTHONUSERBASE", None)
if env_base:
    return env_base

Улучшенный:

if env_base := os.environ.get("PYTHONUSERBASE", None):
    return env_base
_pydecimal.py

Избегайте вложенных if и удалите один уровень отступа.

Ток:

if self._is_special:
    ans = self._check_nans(context=context)
    if ans:
        return ans

Улучшенный:

if self._is_special and (ans := self._check_nans(context=context)):
    return ans

copy.py Код выглядит более регулярным и избегает множественных вложенных if. (См. Приложение A, чтобы узнать, откуда взялся этот пример.)

Ток:

reductor = dispatch_table.get(cls)
if reductor:
    rv = reductor(x)
else:
    reductor = getattr(x, "__reduce_ex__", None)
    if reductor:
        rv = reductor(4)
    else:
        reductor = getattr(x, "__reduce__", None)
        if reductor:
            rv = reductor()
        else:
            raise Error(
                "un(deep)copyable object of type %s" % cls)

Улучшенный:

if reductor := dispatch_table.get(cls):
    rv = reductor(x)
elif reductor := getattr(x, "__reduce_ex__", None):
    rv = reductor(4)
elif reductor := getattr(x, "__reduce__", None):
    rv = reductor()
else:
    raise Error("un(deep)copyable object of type %s" % cls)
datetime.py

tz используется только для s + = tz, перемещение его присваивания внутри if помогает показать его область действия.

Ток:

s = _format_time(self._hour, self._minute,
                 self._second, self._microsecond,
                 timespec)
tz = self._tzstr()
if tz:
    s += tz
return s

Улучшенный:

s = _format_time(self._hour, self._minute,
                 self._second, self._microsecond,
                 timespec)
if tz := self._tzstr():
    s += tz
return s

sysconfig.py Вызов fp.readline () в условии while и вызов .match () в строках if делает код более компактным без

что затрудняет понимание.

Ток:

while True:
    line = fp.readline()
    if not line:
        break
    m = define_rx.match(line)
    if m:
        n, v = m.group(1, 2)
        try:
            v = int(v)
        except ValueError:
            pass
        vars[n] = v
    else:
        m = undef_rx.match(line)
        if m:
            vars[m.group(1)] = 0

Улучшенный:

while line := fp.readline():
    if m := define_rx.match(line):
        n, v = m.group(1, 2)
        try:
            v = int(v)
        except ValueError:
            pass
        vars[n] = v
    elif m := undef_rx.match(line):
        vars[m.group(1)] = 0

Упрощение понимания списков Понимание списка может эффективно отображать и фильтровать, фиксируя условие:

results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

Точно так же подвыражение можно повторно использовать в основном выражении, дав ему имя при первом использовании:

stuff = [[y := f(x), x/y] for x in range(5)]

Обратите внимание, что в обоих случаях переменная y связана в содержащей его области (т. Е. На том же уровне, что и результаты или прочее).

Захват значений условий Выражения присваивания можно использовать для хорошего эффекта в заголовке оператора if или while:

# Loop-and-a-half
while (command := input("> ")) != "quit":
    print("You entered:", command)

# Capturing regular expression match objects
# See, for instance, Lib/pydoc.py, which uses a multiline spelling
# of this effect
if match := re.search(pat, text):
    print("Found:", match.group(0))
# The same syntax chains nicely into 'elif' statements, unlike the
# equivalent using assignment statements.
elif match := re.search(otherpat, text):
    print("Alternate found:", match.group(0))
elif match := re.search(third, text):
    print("Fallback found:", match.group(0))

# Reading socket data until an empty string is returned
while data := sock.recv(8192):
    print("Received data:", data)

В частности, с циклом while, это может устранить необходимость иметь бесконечный цикл, присваивание и условие. Он также создает плавную параллель между циклом, который просто использует вызов функции в качестве своего условия, и циклом, который использует это как свое условие, но также использует фактическое значение.

Форк Пример из низкоуровневого мира UNIX:

if pid := os.fork():
    # Parent code
else:
    # Child code

Оригинальный ответ

http://docs.python.org/tutorial/datastructures.html

Обратите внимание, что в Python, в отличие от C, присвоение не может происходить внутри выражений. Программисты на C могут жаловаться на это, но это позволяет избежать распространенного класса проблем, встречающихся в программах на языке C: ввод = в выражении, когда было предназначено ==.

также см:

http://effbot.org/pyfaq/why-can-ti-use-an-assignment-in-an-expression.htm

Джон Ла Рой
источник
Мне нравится этот ответ, потому что он фактически указывает на то, почему такая «функция» могла быть намеренно исключена из Python. Обучая программированию новичков, я видел, как многие совершали эту ошибку if (foo = 'bar'), намереваясь проверить ценность foo.
Джонатан Кросс
2
@JonathanCross, Эта "функция" фактически будет добавлена ​​в 3.8. Маловероятно, что его можно будет использовать так экономно, как следовало бы - но, по крайней мере, это не простая =
Джон Ла Рой
@JohnLaRooy: Глядя на примеры, я думаю, что фраза "вряд ли будет использоваться так экономно, как следовало бы" была точной; Я обнаружил, что из ~ 10 примеров только два действительно улучшают код. (А именно, в качестве единственного выражения либо в условии while, чтобы избежать дублирования строки или наличия условия цикла в теле, либо в цепочке elif, чтобы избежать вложенности)
Алекси Торхамо
39

Нет, BDFL не понравилась эта функция.

С того места, где я сижу, Гвидо ван Россум, «Доброжелательный диктатор на всю жизнь», изо всех сил старался сохранить Python настолько простым, насколько это возможно. Мы можем спорить с некоторыми из принятых им решений - я бы предпочел, чтобы он сказал: «Нет Но тот факт, что не было комитета, разрабатывающего Python, а вместо этого доверенный «консультативный совет», основанный в основном на заслугах и фильтрующий чувства одного дизайнера, создал чертовски хороший язык, ИМХО.

Кевин Литтл
источник
15
Просто? Эта функция могла бы упростить часть моего кода, потому что она могла бы сделать его более компактным и, следовательно, более читаемым. Теперь мне нужны две строчки там, где раньше была одна. Я никогда не понимал, почему Python на протяжении многих лет (и часто по очень уважительной причине) отказывался от функций, которые есть у других языков программирования. Особенно эта функция, о которой мы здесь говорим, очень и очень полезна.
Regis май
6
Меньше кода не всегда проще или легче читать. Возьмем, к примеру, рекурсивную функцию. Его эквивалент цикла часто более читабелен.
FMF
1
Мне не нравится его C-версия, но мне очень не хватает чего-то вроде rust, if letкогда у меня есть цепочка if elif, но мне нужно хранить и использовать значение условия в каждом случае.
Thayne
1
Я должен сказать, что код, который я пишу сейчас (причина, по которой я искал эту проблему), НАМНОГО уродливее без этой функции. Вместо того, чтобы использовать if, за которым следует множество else if, мне нужно сохранить отступ следующего if под последним else.
MikeKulls
17

Не напрямую, в соответствии с моим старым рецептом, но, как говорится в рецепте, семантический эквивалент легко построить, например, если вам нужно выполнить транслитерацию непосредственно из ссылочного алгоритма с кодировкой C (конечно, перед рефакторингом на более идиоматический Python; -). Т.е.:

class DataHolder(object):
    def __init__(self, value=None): self.value = value
    def set(self, value): self.value = value; return value
    def get(self): return self.value

data = DataHolder()

while data.set(somefunc()):
  a = data.get()
  # use a

Кстати, очень идиоматическая питоническая форма для вашего конкретного случая, если вы точно знаете, какое ложное значение somefuncможет вернуть, когда оно действительно возвращает ложное значение (например 0), является

for a in iter(somefunc, 0):
  # use a

так что в этом конкретном случае рефакторинг будет довольно простым ;-).

Если возвращение может быть любой вид falsish значения (0, None, '', ...), одна возможность заключается в том :

import itertools

for a in itertools.takewhile(lambda x: x, iter(somefunc, object())):
    # use a

но вы можете предпочесть простой настраиваемый генератор:

def getwhile(func, *a, **k):
    while True:
      x = func(*a, **k)
      if not x: break
      yield x

for a in getwhile(somefunc):
    # use a
Алекс Мартелли
источник
Я бы проголосовал за это дважды, если бы мог. Это отличное решение для тех времен, когда что-то подобное действительно необходимо. Я адаптировал ваше решение к классу Matcher регулярных выражений, экземпляр которого создается один раз, а затем .check () используется в операторе if, а .result () используется внутри его тела для получения совпадения, если оно было. Благодарность! :)
Teekin
16

Да, но только начиная с Python 3.8 и новее.

PEP 572 предлагает выражения присваивания и уже был принят.

Цитирование части синтаксиса и семантики PEP:

# Handle a matched regex
if (match := pattern.search(data)) is not None:
    # Do something with match

# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
   process(chunk)

# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

В вашем конкретном случае вы сможете написать

if a := some_func():
    # Use a
Timgeb
источник
5

Нет. Присваивание в Python - это утверждение, а не выражение.

Игнасио Васкес-Абрамс
источник
И у Гвидо по-другому не было бы.
Марк Рэнсом
1
@MarkRansom Приветствую Гвидо. Хорошо .. вздох.
StephenBoesch
@javadba этот парень был прав гораздо чаще, чем ошибался. Я ценю, что наличие одного человека, отвечающего за видение, приводит к гораздо более согласованной стратегии, чем разработка комитетом; Я могу сравнивать и противопоставлять C ++, который является моим основным хлебом с маслом.
Марк Рэнсом
Я чувствую, что и Ruby, и Scala (на разных языках) лучше понимают это, чем python: но в любом случае здесь не место ..
Стивен Бош
4

Благодаря новой функции Python 3.8 в этой версии можно будет делать это, хотя и не с использованием =оператора присваивания, похожего на Ada :=. Пример из документов:

# Handle a matched regex
if (match := pattern.search(data)) is not None:
    # Do something with match
Жан-Франсуа Фабр
источник
2

Вы можете определить функцию, которая будет выполнять назначение за вас:

def assign(name, value):
    import inspect
    frame = inspect.currentframe()
    try:
        locals_ = frame.f_back.f_locals
    finally:
        del frame 
    locals_[name] = value
    return value

if assign('test', 0):
    print("first", test)
elif assign('xyz', 123):
    print("second", xyz)
Виллем Хенгевельд
источник
1

Одна из причин, по которой присваивания недопустимы в условиях, заключается в том, что легче ошибиться и присвоить True или False:

some_variable = 5

# This does not work
# if True = some_variable:
#   do_something()

# This only works in Python 2.x
True = some_variable

print True  # returns 5

В Python 3 True и False - ключевые слова, так что больше никакого риска.

user2979916
источник
1
In [161]: l_empty == [] Out [161]: True In [162]: [] == [] Out [162]: Верно Я не думаю, что это причина
вулкан
Почти уверен, что большинство людей == Trueвсе равно выбирают правильную сторону.
numbermaniac
1

Оператор присваивания, также неофициально известный как оператор моржа, был создан 28 февраля 2018 г. в PEP572 .

Для полноты картины я опубликую соответствующие части, чтобы вы могли сравнить различия между 3.7 и 3.8:

3.7
---
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
test: or_test ['if' or_test 'else' test] | lambdef
test_nocond: or_test | lambdef_nocond
lambdef: 'lambda' [varargslist] ':' test
lambdef_nocond: 'lambda' [varargslist] ':' test_nocond
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*

3.8
---
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
namedexpr_test: test [':=' test]                         <---- WALRUS OPERATOR!!!
test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*
BPL
источник