Чи можемо ми отримати призначення в умові?


92

Чи можна мати призначення в стані?

Для екс.

if (a=some_func()):
    # Use a

Те, що два запитання мають однакову відповідь, не означає, що вони є дублікатами. Тривіальне, щоб відповісти самому, але не тривіальне, щоб побачити міркування або якщо є спосіб обійти.
mehmet

Відповіді:


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
це навмисно заборонено, оскільки Гвідо, доброзичливий диктатор пітона, вважає їх непотрібними і більше заплутаними, ніж корисними. Це та сама причина, що немає операторів після збільшення або попереднього збільшення (++).
Matt Boehm,

4
він дозволяє додавати доповнену розпаювання в 2.0 , тому що x = x + 1вимагає додаткового часу пошуку , коли x += 1був трохи швидше, але я впевнений , що він навіть не подобається робити , що багато. :-)
wescpy

сподіваюся, це стане можливим найближчим часом. Python просто повільно розвивається
Nik O'Lai,

4
"чому б не спробувати" - Бо хто знає, яким може бути синтаксис? Можливо, OP спробував це, і це не спрацювало, але це не означає, що синтаксис не відрізняється, або що немає способу зробити це не за призначенням
Levi H

57

ОНОВЛЕННЯ - оригінальна відповідь знаходиться внизу

Python 3.8 додасть PEP572

Анотація
Це пропозиція щодо створення способу присвоєння змінним у виразі за допомогою позначення НАЗВА: = вираження. Додано новий виняток, TargetScopeError, і є одна зміна порядку оцінки.

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

"Безлад PEP 572" був темою сесії саміту Python у 2018 році під керівництвом доброзичливого диктатора довічного життя (BDFL) Гідо ван Россума. PEP 572 прагне додати до мови вирази присвоєння (або "вбудовані призначення"), але він бачив тривале обговорення багатьох величезних потоків у списку розсилки python-dev - навіть після кількох раундів на ідеях python. Ці нитки часто були суперечливими і були явно об’ємними до того моменту, коли багато хто, напевно, просто їх налаштовував. На саміті Ван Россум дав огляд пропозиції функції, яку він, схоже, схильний прийняти, але він також хотів обговорити, як уникнути такого вибуху нитки в майбутньому.

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. (Див. Додаток А для походження цього прикладу.)

Струм:

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, переміщуючи його призначення всередину, якщо допомагає показати його область дії.

Струм:

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 це може усунути необхідність мати нескінченний цикл, призначення та умову. Він також створює плавну паралель між циклом, який просто використовує виклик функції як свою умову, і таким, який використовує це як свій стан, але також використовує фактичне значення.

Fork Приклад із низькорівневого світу 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, щоб уникнути вкладеності)
Aleksi Torhamo

39

Ні, BDFL не сподобалась ця особливість.

З того місця, де я сиджу, Гвідо ван Россум, "Доброзичливий диктатор на все життя", наполегливо намагався зберегти Python якомога простішим. Ми можемо сперечатися з деякими прийнятими ним рішеннями - я б вважав за краще, щоб він сказав: "Ні" "Частіше. Але той факт, що не існувало комітету, який розробляв Python, а натомість довіреної" дорадчої ради ", що базується переважно на заслугах, фільтруючи чуттєвість одного дизайнера, породило чудову приємну мову, IMHO.


15
Просто? Ця функція могла спростити деяку частину мого коду, оскільки могла зробити його більш компактним і, отже, більш читабельним. Тепер мені потрібні два рядки там, де раніше мені потрібен був один. Я так і не зрозумів, чому Python протягом багатьох років (і часто з дуже вагомих причин) відкидав функції, які мають інші мови програмування. Особливо ця функція, про яку ми тут говоримо, дуже, дуже корисна.
Regis

6
Менше коду не завжди простіший або читабельніший. Візьмемо, наприклад, рекурсивну функцію. Його еквівалент циклу часто є більш читабельним.
FMF

1
Мені не подобається версія C, але мені дуже не вистачає чогось на кшталт іржі, if letколи у мене є ланцюжок if elif, але мені потрібно зберігати та використовувати значення умови в кожному випадку.
Тейн

1
Я повинен сказати, що код, який я пишу зараз (причина, по якій я шукав цю проблему), НАБАГАТО потворніший без цієї функції. Замість того, щоб використовувати, якщо слідує безліч інших ifs, мені потрібно продовжувати відступити наступний if під останнім else.
MikeKulls

17

Не безпосередньо, за цим моїм старим рецептом - але, як зазначає рецепт, легко створити семантичний еквівалент, наприклад, якщо вам потрібно транслітерувати безпосередньо з алгоритму, що кодується на С (перед переформатуванням на більш ідіоматичний 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

До речі, дуже ідіоматична пітонічна форма для вашого конкретного випадку, якщо ви точно знаєте, яке значення falsish 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

5

Ні. Призначення в Python - це твердження, а не вираз.


І Гвідо не мав би цього по-іншому.
Mark Ransom,

1
@MarkRansom Всім привіт Гвідо. Правильно .. зітхай.
StephenBoesch

@javadba хлопець мав рацію набагато частіше, ніж помилявся. Я розумію, що наявність однієї особи, яка відповідає за бачення, призводить до набагато більш послідовної стратегії, ніж розробка комітетом; Я можу порівняти і порівняти з C ++, який є моїм основним хлібом та маслом.
Марк Ренсом,

Я відчуваю, що і рубін, і скала (v на різних мовах) суттєво це розуміють, значно більше, ніж python: але в будь-якому випадку тут не місце ..
Стівен Боеш,

4

Завдяки новій функції Python 3.8 можна буде робити подібні дії з цієї версії, хоча і не використовуючи =оператор присвоєння, схожий на Ада :=. Приклад з документації:

# 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 - це ключові слова, тож ризику більше немає.


1
В [161]: l_empty == [] Вихід [161]: Правда Вхід [162]: [] == [] Вихід [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)*
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.