Podemos ter atribuição em uma condição?

92

É possível ter atribuição em uma condição?

Por ex.

if (a=some_func()):
    # Use a
Vishal
fonte
O fato de duas perguntas terem a mesma resposta não significa que sejam duplicatas. Trivial responder a si mesmo, mas não é trivial ver o raciocínio, ou se há uma maneira de contornar.
mehmet

Respostas:

111

Por que não experimentá-lo?

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

Então não.

Atualização: isso é possível (com sintaxe diferente) no Python 3.8

Jason Hall
fonte
36
isso é intencionalmente proibido, pois Guido, ditador benevolente do python, os considera desnecessários e mais confusos do que úteis. É a mesma razão pela qual não há operadores pós-incremento ou pré-incremento (++).
Matt Boehm,
4
ele permitiu a adição de atribuição aumentada no 2.0 porque x = x + 1requer tempo de pesquisa adicional enquanto x += 1era um pouco mais rápido, mas tenho certeza de que ele nem gostava de fazer isso . :-)
wescpy
esperança, isso será possível em breve. Python só evolui lentamente
Nik O'Lai
5
"porque não experimentar" - porque quem sabe qual pode ser a sintaxe? Talvez o OP tenha tentado isso e não funcionou, mas isso não significa que a sintaxe não seja diferente ou que não haja uma maneira de fazer isso que não fosse intencional
Levi H
59

ATUALIZAÇÃO - a resposta original está perto do final

Python 3.8 trará PEP572

Resumo
Esta é uma proposta para criar uma maneira de atribuir a variáveis ​​dentro de uma expressão usando a notação NOME: = expr. Uma nova exceção, TargetScopeError, é adicionada e há uma alteração na ordem de avaliação.

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

A "bagunça do PEP 572" foi o tópico de uma sessão do Python Language Summit 2018 liderada pelo benevolente ditador vitalício (BDFL) Guido van Rossum. O PEP 572 busca adicionar expressões de atribuição (ou "atribuições inline") à linguagem, mas tem visto uma discussão prolongada sobre vários tópicos enormes na lista de e-mail python-dev - mesmo depois de várias rodadas de ideias python. Esses tópicos costumavam ser controversos e claramente volumosos a ponto de muitos provavelmente simplesmente os ignorarem. No encontro, Van Rossum deu uma visão geral da proposta de recurso, que ele parece inclinado a aceitar, mas ele também queria discutir como evitar esse tipo de explosão de discussão no futuro.

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

Exemplos da biblioteca padrão Python

site.py env_base é usado apenas nessas linhas, colocando sua atribuição no if move-o como o "cabeçalho" do bloco.

Atual:

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

Melhorado:

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

Evite se aninhado e remova um nível de indentação.

Atual:

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

Melhorado:

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

O código copy.py parece mais regular e evita vários if aninhados. (Veja o Apêndice A para a origem deste exemplo.)

Atual:

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)

Melhorado:

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 é usado apenas para s + = tz, mover sua atribuição dentro de if ajuda a mostrar seu escopo.

Atual:

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

Melhorado:

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

sysconfig.py Chamar fp.readline () na condição while e chamar .match () nas linhas if tornam o código mais compacto, sem

tornando mais difícil de entender.

Atual:

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

Melhorado:

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

Simplificando as compreensões de lista Uma compreensão de lista pode mapear e filtrar com eficiência, capturando a condição:

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

Da mesma forma, uma subexpressão pode ser reutilizada dentro da expressão principal, dando a ela um nome no primeiro uso:

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

Observe que em ambos os casos a variável y é limitada no escopo de conteúdo (ou seja, no mesmo nível dos resultados ou coisas).

Capturando valores de condição As expressões de atribuição podem ser usadas com bons resultados no cabeçalho de uma instrução if ou 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)

Particularmente com o loop while, isso pode eliminar a necessidade de ter um loop infinito, uma atribuição e uma condição. Ele também cria um paralelo suave entre um loop que simplesmente usa uma chamada de função como sua condição e um que usa isso como sua condição, mas também usa o valor real.

Fork Um exemplo do mundo UNIX de baixo nível:

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

Resposta original

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

Observe que em Python, ao contrário de C, a atribuição não pode ocorrer dentro de expressões. Os programadores C podem reclamar sobre isso, mas evita uma classe comum de problemas encontrados em programas C: digitar = em uma expressão quando == era pretendido.

Veja também:

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

John La Rooy
fonte
1
Gosto dessa resposta porque, na verdade, ela aponta por que tal "recurso" pode ter sido deliberadamente deixado de fora do Python. Ao ensinar programação para iniciantes, tenho visto muitos cometerem esse erro if (foo = 'bar')enquanto pretendem testar o valor de foo.
Jonathan Cross
2
@JonathanCross, Este "recurso" será adicionado na versão 3.8. É improvável que seja usado com a moderação que deveria - mas pelo menos não é uma planície=
John La Rooy
@JohnLaRooy: Olhando os exemplos, acho que "improvável que seja usado com a moderação que deveria" foi certeiro; Dos cerca de 10 exemplos, descobri que apenas dois realmente melhoram o código. (Ou seja, como a única expressão em uma condição while, para evitar duplicar a linha ou ter a condição de loop no corpo, ou em uma cadeia elif para evitar aninhamento)
Aleksi Torhamo
Não funciona com o ternário a if a := 1 == 1 else FalseInvalid syntax: c Então, aparentemente, ainda tem que recorrer ao antigo estranho (lambda: (ret := foo(), ret if ret else None))()[-1] (ou seja, como uma forma de evitar a chamada repetida para foo()no ternário) .
Hi-Angel
39

Não, o BDFL não gostou desse recurso.

De onde estou, Guido van Rossum, "Benevolent Dictator For Life", lutou muito para manter o Python tão simples quanto possível. Podemos questionar algumas das decisões que ele tomou - eu teria preferido que ele dissesse 'Não Mas o fato de não haver um comitê para projetar Python, mas sim um "conselho consultivo" confiável, baseado em grande parte no mérito, filtrando as sensibilidades de um designer, produziu uma linguagem incrível, IMHO.

Kevin Little
fonte
15
Simples? Esse recurso pode simplificar bastante o meu código, porque poderia tê-lo tornado mais compacto e, portanto, mais legível. Agora preciso de duas linhas, onde antes precisava. Nunca entendi por que Python rejeitou recursos de outras linguagens de programação por muitos anos (e muitas vezes por um bom motivo). Especialmente esse recurso de que estamos falando aqui é muito, muito útil.
Regis maio
6
Menos código nem sempre é mais simples ou mais legível. Pegue uma função recursiva, por exemplo. Seu equivalente em loop é geralmente mais legível.
FMF de
1
Eu não gosto da versão C dela, mas eu realmente sinto falta de ter algo como o da ferrugem if letquando tenho uma cadeia if elif, mas preciso armazenar e usar o valor da condição em cada caso.
Thayne
1
Devo dizer que o código que estou escrevendo agora (o motivo pelo qual pesquisei esse problema) é MUITO mais feio sem esse recurso. Em vez de usar if seguido por muitos else ifs, preciso continuar recuando o próximo if sob o último else.
MikeKulls
17

Não diretamente, de acordo com esta receita minha - mas como a receita diz, é fácil construir o equivalente semântico, por exemplo, se você precisar transliterar diretamente de um algoritmo de referência codificado em C (antes de refatorar para Python mais idiomático, é claro; -). Ie:

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, uma forma Pythônica muito idiomática para o seu caso específico, se você souber exatamente qual valor falso somefuncpode retornar quando retornar um valor falso (por exemplo 0), é

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

então, neste caso específico, a refatoração seria muito fácil ;-).

Se o retorno poderia ser qualquer tipo de valor falsish (0, None, '', ...), uma possibilidade é:

import itertools

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

mas você pode preferir um gerador personalizado simples:

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
fonte
Eu votaria nisso duas vezes, se pudesse. Esta é uma ótima solução para aqueles momentos em que algo assim é realmente necessário. Eu adaptei sua solução para uma classe Matcher regex, que é instanciada uma vez e então .check () é usado na instrução if e .result () é usado dentro de seu corpo para recuperar a correspondência, se houver. Obrigado! :)
Teekin
16

Sim, mas apenas a partir do Python 3.8.

O PEP 572 propõe Expressões de Atribuição e já foi aceito.

Citando a parte de sintaxe e semântica do 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]

No seu caso específico, você será capaz de escrever

if a := some_func():
    # Use a
Timgeb
fonte
5

Não. A atribuição em Python é uma declaração, não uma expressão.

Ignacio Vazquez-Abrams
fonte
E Guido não aceitaria de outra maneira.
Mark Ransom,
1
@MarkRansom Todos saudam Guido. Certo .. suspiro.
StephenBoesch
@javadba o cara acertou muito mais vezes do que errou. Eu entendo que ter uma única pessoa responsável pela visão resulta em uma estratégia muito mais coerente do que o projeto por comitê; Posso comparar e contrastar com C ++, que é meu principal pão com manteiga.
Mark Ransom
Eu sinto que ruby ​​e scala (v linguagens diferentes) acertam significativamente mais do que python: mas em qualquer caso, aqui não é o lugar ...
StephenBoesch
4

Graças ao novo recurso do Python 3.8 , será possível fazer isso a partir desta versão, embora não usando, =mas o operador de atribuição do tipo Ada :=. Exemplo dos documentos:

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

Você pode definir uma função para fazer a atribuição para você:

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
fonte
1

Uma das razões pelas quais as atribuições são ilegais em condições é que é mais fácil cometer um erro e atribuir Verdadeiro ou Falso:

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

Em Python 3, True e False são palavras-chave, então não há mais risco.

user2979916
fonte
1
In [161]: l_empty == [] Out [161]: True In [162]: [] == [] Out [162]: True Não acho que seja essa a razão
vulcão
Com certeza a maioria das pessoas coloca == Truedo lado certo de qualquer maneira.
numbermaniac
1

O operador designado - também conhecido informalmente como o operador morsa - foi criado em 28 de fevereiro de 2018 no PEP572 .

Para fins de integridade, postarei as partes relevantes para que você possa comparar as diferenças entre 3.7 e 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
fonte