¿Podemos tener asignación en una condición?

92

¿Es posible tener cesión en condición?

Por ej.

if (a=some_func()):
    # Use a
Vishal
fuente
Que dos preguntas tengan la misma respuesta no significa que sean duplicadas. Trivial para responderse a sí mismo, pero no trivial para ver el razonamiento, o si hay una forma de evitarlo.
mehmet

Respuestas:

111

¿Por qué no probarlo?

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

Entonces, no.

Actualización: esto es posible (con diferente sintaxis) en Python 3.8

Jason Hall
fuente
35
esto está intencionalmente prohibido ya que Guido, el benevolente dictador pitón, los encuentra innecesarios y más confusos que útiles. Es la misma razón por la que no hay operadores de post-incremento o pre-incremento (++).
Matt Boehm
4
Permitió la adición de asignación aumentada en 2.0 porque x = x + 1requiere tiempo de búsqueda adicional mientras que x += 1era algo más rápido, pero estoy seguro de que ni siquiera le gustó mucho hacer eso . :-)
wescpy
Espero que esto sea posible pronto. Python evoluciona lentamente
Nik O'Lai
4
"¿Por qué no probarlo?" Porque ¿quién sabe cuál podría ser la sintaxis? Tal vez OP lo intentó y no funcionó, pero eso no significa que la sintaxis no sea diferente, o que no haya una forma de hacerlo que no sea la intencionada
Levi H
57

ACTUALIZACIÓN: la respuesta original está casi al final

Python 3.8 traerá PEP572

Resumen
Esta es una propuesta para crear una forma de asignar variables dentro de una expresión utilizando la notación NOMBRE: = expr. Se agrega una nueva excepción, TargetScopeError, y hay un cambio en el orden de evaluación.

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

El "lío de PEP 572" fue el tema de una sesión de Python Language Summit 2018 dirigida por el benevolente dictador de por vida (BDFL) Guido van Rossum. PEP 572 busca agregar expresiones de asignación (o "asignaciones en línea") al lenguaje, pero ha visto una discusión prolongada sobre múltiples hilos enormes en la lista de correo de python-dev, incluso después de múltiples rondas sobre python-ideas. Esos hilos a menudo eran polémicos y claramente voluminosos hasta el punto de que muchos probablemente simplemente los ignoraban. En la cumbre, Van Rossum dio una descripción general de la propuesta de la función, que parece inclinado a aceptar, pero también quería discutir cómo evitar este tipo de explosión de hilos en el futuro.

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

Ejemplos de la biblioteca estándar de Python

site.py env_base solo se usa en estas líneas, poniendo su asignación en el if lo mueve como el "encabezado" del bloque.

Actual:

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

Mejorado:

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

Evite anidado si y elimine un nivel de sangría.

Actual:

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

Mejorado:

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

copy.py El código parece más regular y evita múltiples if anidados. (Consulte el Apéndice A para conocer el origen de este ejemplo).

Actual:

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)

Mejorado:

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 solo se usa para s + = tz, mover su asignación dentro de if ayuda a mostrar su alcance.

Actual:

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

Mejorado:

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

sysconfig.py Llamar a fp.readline () en la condición while y llamar a .match () en las líneas if hace que el código sea más compacto sin

haciéndolo más difícil de entender.

Actual:

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

Mejorado:

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

Simplificar las comprensiones de listas Una comprensión de listas puede mapear y filtrar de manera eficiente al capturar la condición:

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

De manera similar, una subexpresión se puede reutilizar dentro de la expresión principal, dándole un nombre en el primer uso:

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

Tenga en cuenta que en ambos casos la variable y está vinculada en el ámbito contenedor (es decir, al mismo nivel que los resultados o cosas).

Captura de valores de condición Las expresiones de asignación se pueden utilizar con buenos resultados en el encabezado de una declaración if o 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 con el ciclo while, esto puede eliminar la necesidad de tener un ciclo infinito, una asignación y una condición. También crea un paralelo suave entre un bucle que simplemente usa una llamada de función como su condición, y uno que usa eso como su condición pero también usa el valor real.

Fork Un ejemplo del mundo UNIX de bajo nivel:

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

Respuesta original

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

Tenga en cuenta que en Python, a diferencia de C, la asignación no puede ocurrir dentro de expresiones. Los programadores de C pueden quejarse de esto, pero evita una clase común de problemas encontrados en los programas de C: escribir = en una expresión cuando se pretendía ==.

ver también:

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

John La Rooy
fuente
Me gusta esta respuesta porque en realidad señala por qué tal "característica" podría haberse dejado deliberadamente fuera de Python. Al enseñar programación para principiantes, he visto a muchos cometer este error if (foo = 'bar')al intentar probar el valor de foo.
Jonathan Cross
2
@JonathanCross, esta "característica" se agregará en la 3.8. Es poco probable que se use con tanta moderación como debería, pero al menos no es simple=
John La Rooy
@JohnLaRooy: Mirando los ejemplos, creo que "es poco probable que se use con tanta moderación como debería" fue acertado; De los ~ 10 ejemplos, encuentro que solo dos realmente mejoran el código. (Es decir, como la única expresión en una condición while, para evitar duplicar la línea o tener la condición de bucle en el cuerpo, o en una cadena elif para evitar el anidamiento)
Aleksi Torhamo
39

No, al BDFL no le gustó esa característica.

Desde donde estoy sentado, Guido van Rossum, "Dictador benévolo de por vida", ha luchado mucho para que Python sea lo más simple posible. Podemos objetar algunas de las decisiones que ha tomado; hubiera preferido que dijera 'No Pero el hecho de que no haya un comité diseñando Python, sino un "consejo asesor" de confianza, basado en gran parte en el mérito, filtrando las sensibilidades de un diseñador, ha producido un lenguaje increíblemente agradable, en mi humilde opinión.

Kevin pequeño
fuente
15
¿Sencillo? Esta característica podría simplificar bastante parte de mi código porque podría haberlo hecho más compacto y, por lo tanto, más legible. Ahora necesito dos líneas donde solía necesitar una. Nunca entendí por qué Python rechazó las características que tienen otros lenguajes de programación durante muchos años (y a menudo por una muy buena razón). Especialmente esta característica de la que estamos hablando aquí es muy, muy útil.
Regis mayo
6
Menos código no siempre es más simple o morde legible. Tome una función recursiva, por ejemplo. Su equivalente de bucle suele ser más legible.
FMF
1
No me gusta la versión C, pero realmente extraño tener algo como rust if letcuando tengo una cadena if elif, pero necesito almacenar y usar el valor de la condición en cada caso.
Thayne
1
Tengo que decir que el código que estoy escribiendo ahora (la razón por la que busqué este número) es MUCHO más feo sin esta función. En lugar de usar if seguido de muchos otros ifs, necesito seguir sangrando el siguiente if debajo del último else.
MikeKulls
17

No directamente, según esta vieja receta mía , pero como dice la receta, es fácil construir el equivalente semántico, por ejemplo, si necesita transliterar directamente desde un algoritmo de referencia codificado en C (antes de refactorizar a Python más idiomático, por supuesto; -). Es decir:

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

Por cierto, una forma Pythonic muy idiomática para su caso específico, si sabe exactamente qué valor falso somefuncpuede devolver cuando devuelve un valor falso (por ejemplo 0), es

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

así que en este caso específico la refactorización sería bastante fácil ;-).

Si el retorno es de cualquier tipo de valor falsish (0, None, '', ...), una posibilidad es:

import itertools

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

pero es posible que prefieras un generador personalizado simple:

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
fuente
Votaría esto dos veces si pudiera. Esta es una gran solución para esos momentos en los que realmente se necesita algo como esto. Adapté su solución a una clase regex Matcher, que se instancia una vez y luego .check () se usa en la declaración if y .result () se usa dentro de su cuerpo para recuperar la coincidencia, si hubiera una. ¡Gracias! :)
Teekin
16

Sí, pero solo desde Python 3.8 en adelante.

PEP 572 propone Assignment Expressions y ya ha sido aceptado.

Citando la parte de sintaxis y semántica del 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]

En su caso específico, podrá escribir

if a := some_func():
    # Use a
timgeb
fuente
5

No. La asignación en Python es una declaración, no una expresión.

Ignacio Vázquez-Abrams
fuente
Y Guido no lo haría de otra manera.
Mark Ransom
1
@MarkRansom Todos saludan a Guido. Bien ... suspiro.
StephenBoesch
@javadba, el tipo ha tenido razón muchas más veces de lo que se ha equivocado. Aprecio que tener una sola persona a cargo de la visión da como resultado una estrategia mucho más coherente que el diseño por comité; Puedo comparar y contrastar con C ++, que es mi pan y mantequilla principal.
Mark Ransom
Siento que tanto ruby ​​como scala (v idiomas diferentes) lo hacen bien significativamente más que python: pero en cualquier caso, este no es el lugar ..
StephenBoesch
4

Gracias a la nueva característica de Python 3.8 será posible hacer tal cosa desde esta versión, aunque no usando el =operador de asignación tipo Ada :=. Ejemplo de los documentos:

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

Puede definir una función para que realice la asignación por usted:

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

Una de las razones por las que las asignaciones son ilegales en determinadas condiciones es que es más fácil cometer un error y asignar Verdadero o 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

En Python 3, True y False son palabras clave, por lo que ya no hay riesgo.

usuario2979916
fuente
1
In [161]: l_empty == [] Out [161]: True In [162]: [] == [] Out [162]: True No creo que esa sea la razón
volcán
Estoy bastante seguro de que la mayoría de la gente se pone == Truedel lado correcto de todos modos.
numbermaniac
1

El operador de asignación, también conocido informalmente como el operador de la morsa, se creó el 28 de febrero de 2018 en PEP572 .

En aras de la exhaustividad, publicaré las partes relevantes para que pueda comparar las diferencias entre 3.7 y 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
fuente