Czy możemy mieć przydział w stanie?

92

Czy można mieć przydział w stanie?

Na przykład.

if (a=some_func()):
    # Use a
Vishal
źródło
To, że dwa pytania mają tę samą odpowiedź, nie oznacza, że ​​są duplikatami. Trywialne, aby odpowiedzieć sobie, ale nie jest to trywialne, aby zobaczyć uzasadnienie lub jeśli jest na to sposób.
mehmet

Odpowiedzi:

111

Dlaczego by tego nie wypróbować?

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

Więc nie.

Aktualizacja: jest to możliwe (z inną składnią) w Pythonie 3.8

Jason Hall
źródło
35
jest to celowo zabronione, ponieważ Guido, życzliwy dyktator pytonów, uważa je za niepotrzebne i bardziej zagmatwane niż przydatne. Z tego samego powodu nie ma operatorów post-inkrementacyjnych ani preinkrementacyjnych (++).
Matt Boehm
4
zezwolił na dodanie rozszerzonego przypisania w wersji 2.0, ponieważ x = x + 1wymaga dodatkowego czasu wyszukiwania, podczas gdy x += 1był nieco szybszy, ale jestem pewien, że nawet nie lubił robić tak dużo. :-)
wescpy
mam nadzieję, że wkrótce stanie się to możliwe. Python ewoluuje powoli
Nik O'Lai
4
„dlaczego by tego nie wypróbować” - bo kto wie, jaka może być składnia? Może OP spróbował tego i nie zadziałało, ale to nie znaczy, że składnia nie jest inna lub że nie ma sposobu, aby to zrobić, który nie jest przeznaczony
Levi H
57

AKTUALIZACJA - Oryginalna odpowiedź znajduje się na dole

Python 3.8 przyniesie PEP572

Abstrakt
To jest propozycja stworzenia sposobu przypisywania zmiennych w wyrażeniu przy użyciu notacji NAZWA: = wyrażenie. Dodano nowy wyjątek TargetScopeError i nastąpiła jedna zmiana w kolejności oceny.

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

„Bałagan PEP 572” był tematem sesji szczytu języka Python w 2018 roku prowadzonej przez życzliwego dyktatora życia (BDFL) Guido van Rossuma. PEP 572 stara się dodać wyrażenia przypisania (lub „przypisania w wierszu”) do języka, ale widział długotrwałą dyskusję na temat wielu dużych wątków na liście mailingowej python-dev - nawet po wielu rundach poświęconych pomysłom na Pythona. Wątki te były często kontrowersyjne i wyraźnie obszerne do tego stopnia, że ​​wielu prawdopodobnie po prostu je wyciszyło. Na szczycie Van Rossum przedstawił przegląd propozycji funkcji, którą wydaje się być skłonny zaakceptować, ale chciał też omówić, jak uniknąć tego rodzaju eksplozji wątków w przyszłości.

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

Przykłady ze standardowej biblioteki Pythona

site.py env_base jest używane tylko w tych wierszach, umieszczenie jego przypisania na if przesuwa go jako "nagłówek" bloku.

Obecny:

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

Ulepszony:

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

Unikaj zagnieżdżania if i usuń jeden poziom wcięcia.

Obecny:

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

Ulepszony:

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

copy.py Kod wygląda na bardziej regularny i unikaj wielokrotnego zagnieżdżania if. (Zobacz dodatek A, aby zapoznać się z pochodzeniem tego przykładu).

Obecny:

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)

Ulepszony:

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 jest używane tylko dla s + = tz, przeniesienie jego przypisania wewnątrz if pomaga pokazać jego zasięg.

Obecny:

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

Ulepszony:

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

sysconfig.py Wywołanie fp.readline () w warunku while i wywołanie .match () w liniach if sprawia, że ​​kod jest bardziej zwarty bez

utrudniając zrozumienie.

Obecny:

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

Ulepszony:

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

Upraszczanie składanych list Funkcja rozumienia list może skutecznie mapować i filtrować, przechwytując warunek:

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

Podobnie podwyrażenie można ponownie wykorzystać w wyrażeniu głównym, nadając mu nazwę przy pierwszym użyciu:

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

Zauważ, że w obu przypadkach zmienna y jest związana w zakresie zawierającym (tj. Na tym samym poziomie co wyniki lub rzeczy).

Przechwytywanie wartości warunków Wyrażenia przypisania mogą być używane z dobrym skutkiem w nagłówku instrukcji if lub 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)

Szczególnie w przypadku pętli while może to wyeliminować potrzebę posiadania nieskończonej pętli, przypisania i warunku. Tworzy również płynną paralelę między pętlą, która po prostu używa wywołania funkcji jako warunku, a taką, która używa tego jako warunku, ale także używa rzeczywistej wartości.

Fork Przykład z niskiego poziomu świata UNIX:

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

Oryginalna odpowiedź

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

Zauważ, że w Pythonie, w przeciwieństwie do C, przypisanie nie może występować wewnątrz wyrażeń. Programiści C mogą narzekać na to, ale pozwala to uniknąć typowej klasy problemów napotykanych w programach C: wpisanie = w wyrażeniu, gdy zamierzano ==.

Zobacz także:

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

John La Rooy
źródło
Podoba mi się ta odpowiedź, ponieważ tak naprawdę wskazuje, dlaczego taka „funkcja” mogła zostać celowo pominięta w Pythonie. Podczas nauczania programowania dla początkujących widziałem, jak wielu popełnia ten błąd if (foo = 'bar'), zamierzając przetestować wartość foo.
Jonathan Cross
2
@JonathanCross, Ta „funkcja” zostanie dodana w wersji 3.8. Jest mało prawdopodobne, aby był używany tak oszczędnie, jak powinien - ale przynajmniej nie jest to zwykły=
John La Rooy,
@JohnLaRooy: Patrząc na przykłady, myślę, że „mało prawdopodobne, by był używany tak oszczędnie, jak powinien” był trafiony; Z ~ 10 przykładów stwierdzam, że tylko dwa poprawiają kod. (Mianowicie, jako jedyne wyrażenie albo w stanie while, aby uniknąć powielania linii lub stanu pętli w ciele lub w łańcuchu elif, aby uniknąć zagnieżdżenia)
Aleksi Torhamo
39

Nie, BDFL nie lubił tej funkcji.

Z miejsca, w którym siedzę, Guido van Rossum, „Benevolent Dictator For Life”, ciężko walczył, aby Python był tak prosty, jak to tylko możliwe. Możemy spierać się o niektóre decyzje, które podjął - wolałbym, żeby powiedział „Nie Ale fakt, że nie było komitetu projektującego Pythona, ale zamiast tego zaufana „rada doradcza”, oparta w dużej mierze na zasługach, filtrująca wrażliwość jednego projektanta, zaowocował cholernie przyjemnym językiem, IMHO.

Kevin Little
źródło
15
Prosty? Ta funkcja mogłaby uprościć całkiem część mojego kodu, ponieważ mogłaby uczynić go bardziej zwartym i przez to bardziej czytelnym. Teraz potrzebuję dwóch wierszy, w których kiedyś potrzebowałem. Nigdy nie zrozumiałem, dlaczego Python odrzucał funkcje, które inne języki programowania mają przez wiele lat (i często z bardzo dobrego powodu). Szczególnie ta funkcja, o której tutaj mówimy, jest bardzo, bardzo przydatna.
Regis,
6
Mniej kodu nie zawsze jest prostsze lub czytelne w morde. Weźmy na przykład funkcję rekurencyjną. To odpowiednik pętli jest często bardziej czytelny.
FMF
1
Nie podoba mi się wersja C tego, ale naprawdę brakuje mi czegoś takiego jak rdza, if letgdy mam łańcuch if elif, ale muszę przechowywać i używać wartości warunku w każdym przypadku.
Thayne
1
Muszę powiedzieć, że kod, który teraz piszę (powód, dla którego przeszukałem ten numer) jest DUŻO brzydszy bez tej funkcji. Zamiast używać if, po którym następuje wiele else ifs, muszę nadal wciskać następny if pod ostatnim else.
MikeKulls
17

Nie bezpośrednio, zgodnie z moim starym przepisem - ale jak mówi przepis, łatwo jest zbudować semantyczny odpowiednik, np. Jeśli potrzebujesz transliteracji bezpośrednio z algorytmu referencyjnego zakodowanego w C (przed refaktoryzacją do bardziej idiomatycznego Pythona, oczywiście; -). To znaczy:

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

BTW, bardzo idiomatyczna forma Pythona dla twojego konkretnego przypadku, jeśli wiesz dokładnie, jaka fałszywa wartość somefuncmoże zwrócić, gdy zwraca fałszywą wartość (np. 0), Jest

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

więc w tym konkretnym przypadku refaktoryzacja byłaby całkiem prosta ;-).

Jeśli zysk może być dowolny rodzaj wartości falsish (0, None, '', ...), jedną z możliwości jest:

import itertools

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

ale możesz preferować prosty generator niestandardowy:

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

for a in getwhile(somefunc):
    # use a
Alex Martelli
źródło
Gdybym mógł, zagłosowałbym dwukrotnie. To świetne rozwiązanie na czasy, kiedy coś takiego jest naprawdę potrzebne. Zaadaptowałem Twoje rozwiązanie do klasy regex Matcher, która jest tworzona raz, a następnie .check () jest używana w instrukcji if i .result () używanej wewnątrz jej treści w celu pobrania dopasowania, jeśli taki istnieje. Dzięki! :)
Teekin
16

Tak, ale tylko od wersji Python 3.8 i nowszych.

PEP 572 proponuje wyrażenia przypisania i został już zaakceptowany.

Cytując składnię i semantykę części 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]

W swoim konkretnym przypadku będziesz mógł pisać

if a := some_func():
    # Use a
timgeb
źródło
5

Nie. Przypisanie w Pythonie jest instrukcją, a nie wyrażeniem.

Ignacio Vazquez-Abrams
źródło
A Guido nie chciał tego inaczej.
Mark Ransom
1
@MarkRansom All hail Guido. Racja … westchnij.
StephenBoesch
@javadba facet miał rację znacznie częściej niż mylił się. Doceniam, że posiadanie jednej osoby odpowiedzialnej za wizję skutkuje dużo bardziej spójną strategią niż projekt komitetu; Potrafię porównać i kontrastować z C ++, który jest moim głównym tematem.
Mark Ransom
Czuję, że zarówno ruby, jak i scala (w różnych językach) robią to dobrze znacznie bardziej niż python: ale w każdym razie tutaj nie ma miejsca ..
StephenBoesch
4

Dzięki nowej funkcji Pythona 3.8 będzie można zrobić coś takiego z poziomu tej wersji, choć nie używając =tylko operatora przypisania w stylu Ada :=. Przykład z dokumentów:

# Handle a matched regex
if (match := pattern.search(data)) is not None:
    # Do something with match
Jean-François Fabre
źródło
2

Możesz zdefiniować funkcję, która będzie wykonywać przypisywanie za Ciebie:

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)
Willem Hengeveld
źródło
1

Jednym z powodów, dla których przypisania są nielegalne w warunkach, jest to, że łatwiej jest popełnić błąd i przypisać Prawdę lub Fałsz:

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

W Pythonie 3 True i False to słowa kluczowe, więc nie ma już żadnego ryzyka.

user2979916
źródło
1
In [161]: l_empty == [] Out [161]: True In [162]: [] == [] Out [162]: True Nie sądzę, żeby to był powód
wulkan
Jestem pewien, że większość ludzi i tak stawia == Truena właściwą stronę.
numbermaniac
1

Operator przypisania - znany również nieformalnie jako operator morsa - został utworzony 28 lutego 2018 r. W PEP572 .

Ze względu na kompletność opublikuję odpowiednie części, abyś mógł porównać różnice między 3.7 i 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
źródło