8. Sentencias compuestas

Las sentencias compuestas contienen (grupos de) otras sentencias; estas afectan o controlan la ejecución de esas otras sentencias de alguna manera. En general, las sentencias compuestas abarcan varias líneas, aunque en representaciones simples una sentencia compuesta completa puede estar contenida en una línea.

Las sentencias if, while y for implementan construcciones de control de flujo tradicionales. try especifica gestores de excepción o código de limpieza para un grupo de sentencias, mientras que las sentencias with permite la ejecución del código de inicialización y finalización alrededor de un bloque de código. Las definiciones de función y clase también son sentencias sintácticamente compuestas.

Una sentencia compuesta consta de una o más “cláusulas”. Una cláusula consta de un encabezado y una “suite”. Los encabezados de cláusula de una declaración compuesta particular están todos en el mismo nivel de indentación. Cada encabezado de cláusula comienza con una palabra clave de identificación única y termina con dos puntos. Una suite es un grupo de sentencias controladas por una cláusula. Una suite puede ser una o más sentencias simples separadas por punto y coma en la misma línea como el encabezado, siguiendo los dos puntos del encabezado, o puede ser una o puede ser una o más declaraciones indentadas en líneas posteriores. Solo la última forma de una suite puede contener sentencias compuestas anidadas; lo siguiente es ilegal, principalmente porque no estaría claro a qué cláusula if seguido de la cláusula else hace referencia:

if test1: if test2: print(x)

También tenga en cuenta que el punto y coma se une más apretado que los dos puntos en este contexto, de modo que en el siguiente ejemplo, todas o ninguna de las llamadas print() se ejecutan:

if x < y < z: print(x); print(y); print(z)

Resumiendo:

compound_stmt ::=  if_stmt
                   | while_stmt
                   | for_stmt
                   | try_stmt
                   | with_stmt
                   | funcdef
                   | classdef
                   | async_with_stmt
                   | async_for_stmt
                   | async_funcdef
suite         ::=  stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement     ::=  stmt_list NEWLINE | compound_stmt
stmt_list     ::=  simple_stmt (";" simple_stmt)* [";"]

Tenga en cuenta que las sentencias siempre terminan en un NEWLINE posiblemente seguida de DEDENT. También tenga en cuenta que las cláusulas de continuación opcionales siempre comienzan con una palabra clave que no puede iniciar una sentencia, por lo tanto, no hay ambigüedades (el problema de “colgado if” se resuelve en Python al requerir que las sentencias anidadas if deben estar indentadas).

El formato de las reglas gramaticales en las siguientes secciones coloca cada cláusula en una línea separada para mayor claridad.

8.1. La sentencia if

La sentencia if se usa para la ejecución condicional:

if_stmt ::=  "if" assignment_expression ":" suite
             ("elif" assignment_expression ":" suite)*
             ["else" ":" suite]

Selecciona exactamente una de las suites evaluando las expresiones una por una hasta que se encuentre una verdadera (vea la sección Operaciones booleanas para la definición de verdadero y falso); entonces esa suite se ejecuta (y ninguna otra parte de la sentencia if se ejecuta o evalúa). Si todas las expresiones son falsas, se ejecuta la suite de cláusulas else, si está presente.

8.2. La sentencia while

La sentencia while se usa para la ejecución repetida siempre que una expresión sea verdadera:

while_stmt ::=  "while" assignment_expression ":" suite
                ["else" ":" suite]

Esto prueba repetidamente la expresión y, si es verdadera, ejecuta la primera suite; si la expresión es falsa (que puede ser la primera vez que se prueba), se ejecuta el conjunto de cláusulas else, si está presente, y el bucle termina.

La sentencia break ejecutada en la primer suite termina el bucle sin ejecutar la suite de cláusulas else. La sentencia continue ejecutada en la primera suite omite el resto de la suite y vuelve a probar la expresión.

8.3. La sentencia for

La sentencia for se usa para iterar sobre los elementos de una secuencia (como una cadena de caracteres, tupla o lista) u otro objeto iterable:

for_stmt ::=  "for" target_list "in" expression_list ":" suite
              ["else" ":" suite]

La lista de expresiones se evalúa una vez; debería producir un objeto iterable. Se crea un iterador para el resultado de la expression_list. La suite se ejecuta una vez para cada elemento proporcionado por el iterador, en el orden retornado por el iterador. Cada elemento a su vez se asigna a la lista utilizando las reglas estándar para las asignaciones (ver Declaraciones de asignación), y luego se ejecuta la suite. Cuando los elementos están agotados (que es inmediatamente cuando la secuencia está vacía o un iterador genera una excepción del tipo StopIteration), la suite en la cláusula else, si está presente, se ejecuta y el bucle termina.

La sentencia break ejecutada en la primera suite termina el bucle sin ejecutar el conjunto de cláusulas else. La sentencia continue ejecutada en la primera suite omite el resto de las cláusulas y continúa con el siguiente elemento, o con la cláusula else si no hay un elemento siguiente.

El bucle for realiza asignaciones a las variables en la lista. Esto sobrescribe todas las asignaciones anteriores a esas variables, incluidas las realizadas en la suite del bucle for:

for i in range(10):
    print(i)
    i = 5             # this will not affect the for-loop
                      # because i will be overwritten with the next
                      # index in the range

Los nombres en la lista no se eliminan cuando finaliza el bucle, pero si la secuencia está vacía, el bucle no les habrá asignado nada. Sugerencia: la función incorporada range() retorna un iterador de enteros adecuado para emular el efecto de Pascal for i := a to b do; por ejemplo, list(range(3)) retorna la lista [0, 1, 2].

Nota

Hay una sutileza cuando la secuencia está siendo modificada por el bucle (esto solo puede ocurrir para secuencias mutables, por ejemplo, listas). Se utiliza un contador interno para realizar un seguimiento de qué elemento se usa a continuación, y esto se incrementa en cada iteración. Cuando este contador ha alcanzado la longitud de la secuencia, el bucle termina. Esto significa que si la suite elimina el elemento actual (o anterior) de la secuencia, se omitirá el siguiente elemento (ya que obtiene el índice del elemento actual que ya ha sido tratado). Del mismo modo, si la suite inserta un elemento en la secuencia anterior al elemento actual, el elemento actual será tratado nuevamente la próxima vez a través del bucle. Esto puede conducir a errores graves que se pueden evitar haciendo una copia temporal usando una porción de la secuencia completa, por ejemplo,

for x in a[:]:
    if x < 0: a.remove(x)

8.4. La sentencia try

La sentencia try es especifica para gestionar excepciones o código de limpieza para un grupo de sentencias:

try_stmt  ::=  try1_stmt | try2_stmt
try1_stmt ::=  "try" ":" suite
               ("except" [expression ["as" identifier]] ":" suite)+
               ["else" ":" suite]
               ["finally" ":" suite]
try2_stmt ::=  "try" ":" suite
               "finally" ":" suite

The except clause(s) specify one or more exception handlers. When no exception occurs in the try clause, no exception handler is executed. When an exception occurs in the try suite, a search for an exception handler is started. This search inspects the except clauses in turn until one is found that matches the exception. An expression-less except clause, if present, must be last; it matches any exception. For an except clause with an expression, that expression is evaluated, and the clause matches the exception if the resulting object is «compatible» with the exception. An object is compatible with an exception if it is the class or a base class of the exception object, or a tuple containing an item that is the class or a base class of the exception object.

Si ninguna cláusula except coincide con la excepción, la búsqueda de un gestor de excepciones continúa en el código circundante y en la pila de invocación. 1

Si la evaluación de una expresión en el encabezado de una cláusula except genera una excepción, la búsqueda original de un gestor se cancela y se inicia la búsqueda de la nueva excepción en el código circundante y en la pila de llamadas (se trata como si toda la sentencia try provocó la excepción).

Cuando se encuentra una cláusula except coincidente, la excepción se asigna al destino especificado después de la palabra clave as en esa cláusula except, si está presente, y se ejecuta la suite de cláusulas except. Todas las cláusulas except deben tener un bloque ejecutable. Cuando se alcanza el final de este bloque, la ejecución continúa normalmente después de toda la sentencia try. (Esto significa que si existen dos gestores de errores anidados para la misma excepción, y la excepción ocurre en la cláusula try del gestor interno, el gestor externo no gestionará la excepción).

Cuando se ha asignado una excepción usando as target, se borra al final de la cláusula except. Esto es como si

except E as N:
    foo

fue traducido a

except E as N:
    try:
        foo
    finally:
        del N

Esto significa que la excepción debe asignarse a un nombre diferente para poder referirse a ella después de la cláusula except. Las excepciones se borran porque con el seguimiento vinculado a ellas, forman un bucle de referencia con el marco de la pila, manteniendo activos todos los locales en esa pila hasta que ocurra la próxima recolección de basura.

Antes de que se ejecute un conjunto de cláusulas except, los detalles sobre la excepción se almacenan en el módulo sys y se puede acceder a través de sys.exc_info(). sys.exc_info() retorna 3 tuplas que consisten en la clase de excepción, la instancia de excepción y un objeto de rastreo (ver sección Jerarquía de tipos estándar) que identifica el punto en el programa donde ocurrió la excepción. Lo valores sys.exc_info() se restauran a sus valores anteriores (antes de la llamada) al regresar de una función que manejó una excepción.

La cláusula opcional else se ejecuta si el flujo de control sale de la suite try, no se produjo ninguna excepción, y no se ejecutó la sentencia return, continue o break. Las excepciones en la cláusula else no se gestionaron con las cláusulas precedentes except.

Si está presente finally, esto especifica un gestor de “limpieza”. La cláusula try se ejecuta, incluidas las cláusulas except y else. Si se produce una excepción en cualquiera de las cláusulas y no se maneja, la excepción se guarda temporalmente. Se ejecuta la cláusula finally. Si hay una excepción guardada, se vuelve a generar al final de la cláusula finally. Si la cláusula finally genera otra excepción, la excepción guardada se establece como el contexto de la nueva excepción. Si la cláusula finally ejecuta una sentencia return, break o continue, la excepción guardada se descarta:

>>> def f():
...     try:
...         1/0
...     finally:
...         return 42
...
>>> f()
42

La información de excepción no está disponible para el programa durante la ejecución de la cláusula finally.

Cuando se ejecuta una sentencia return, break o continue en la suite try de un try…la sentencia finally, la cláusula finally también se ejecuta “al salir”.

El valor de retorno de una función está determinado por la última sentencia return ejecutada. Dado que la cláusula finally siempre se ejecuta, una sentencia return ejecutada en la cláusula finally siempre será la última ejecutada:

>>> def foo():
...     try:
...         return 'try'
...     finally:
...         return 'finally'
...
>>> foo()
'finally'

Se puede encontrar información adicional sobre las excepciones en la sección Excepciones, e información sobre el uso de la sentencia raise, para generar excepciones se puede encontrar en la sección La declaración raise.

Distinto en la versión 3.8: Antes de Python 3.8, una sentencia continue era ilegal en la cláusula finally debido a un problema con la implementación.

8.5. La sentencia with

La sentencia with se usa para ajustar la ejecución de un bloque con métodos definidos por un administrador de contexto (ver sección Gestores de Contexto en la Declaración with). Esto permite que los patrones de uso comunes tryexceptfinally se encapsulen para una reutilización conveniente.

with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::=  expression ["as" target]

La ejecución de la sentencia with con un «item» se realiza de la siguiente manera:

  1. La expresión de contexto (la expresión dada en with_item) se evalúa para obtener un administrador de contexto.

  2. El administrador de contexto __enter__() se carga para su uso posterior.

  3. El administrador de contexto __exit__() se carga para su uso posterior.

  4. Se invoca el método del administrador de contexto __enter__().

  5. Si se incluyó el destino en la sentencia with, se le asigna el valor de retorno de __enter__().

    Nota

    La sentencia with garantiza que si el método __enter__() regresa sin error, entonces siempre se llamará a __exit__(). Por lo tanto, si se produce un error durante la asignación a la lista de destino, se tratará de la misma manera que si se produciría un error dentro de la suite. Vea el paso 6 a continuación.

  6. La suite se ejecuta.

  7. Se invoca el método del administrador de contexto __exit__(). Si una excepción causó la salida de la suite, su tipo, valor y rastreo se pasan como argumentos a __exit__(). De lo contrario, se proporcionan tres argumentos None.

    Si se salió de la suite debido a una excepción, y el valor de retorno del método __exit__() fue falso, la excepción se vuelve a plantear. Si el valor de retorno era verdadero, la excepción se suprime y la ejecución continúa con la sentencia que sigue a la sentencia with.

    Si se salió de la suite por cualquier motivo que no sea una excepción, el valor de retorno de __exit__() se ignora y la ejecución continúa en la ubicación normal para el tipo de salida que se tomó.

El siguiente código:

with EXPRESSION as TARGET:
    SUITE

es semánticamente equivalente a:

manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not exit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        exit(manager, None, None, None)

Con más de un elemento, los administradores de contexto se procesan como si varias sentencias with estuvieran anidadas:

with A() as a, B() as b:
    SUITE

es semánticamente equivalente a:

with A() as a:
    with B() as b:
        SUITE

Distinto en la versión 3.1: Soporte para múltiples expresiones de contexto.

Ver también

PEP 343 - La sentencia «with»

La especificación, antecedentes y ejemplos de la sentencia de Python with.

8.6. Definiciones de funciones

Una definición de función define una función objeto determinada por el usuario (consulte la sección Jerarquía de tipos estándar):

funcdef                   ::=  [decorators] "def" funcname "(" [parameter_list] ")"
                               ["->" expression] ":" suite
decorators                ::=  decorator+
decorator                 ::=  "@" assignment_expression NEWLINE
parameter_list            ::=  defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
                                 | parameter_list_no_posonly
parameter_list_no_posonly ::=  defparameter ("," defparameter)* ["," [parameter_list_starargs]]
                               | parameter_list_starargs
parameter_list_starargs   ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]
parameter                 ::=  identifier [":" expression]
defparameter              ::=  parameter ["=" expression]
funcname                  ::=  identifier

Una definición de función es una sentencia ejecutable. Su ejecución vincula el nombre de la función en el espacio de nombres local actual a un objeto de función (un contenedor alrededor del código ejecutable para la función). Este objeto de función contiene una referencia al espacio de nombres global actual como el espacio de nombres global que se utilizará cuando se llama a la función.

La definición de la función no ejecuta el cuerpo de la función; esto se ejecuta solo cuando se llama a la función. 2

Una definición de función puede estar envuelta por una o más expresiones decorator. Las expresiones de decorador se evalúan cuando se define la función, en el ámbito que contiene la definición de la función. El resultado debe ser invocable, la cual se invoca con el objeto de función como único argumento. El valor retornado está vinculado al nombre de la función en lugar del objeto de la función. Se aplican múltiples decoradores de forma anidada. Por ejemplo, el siguiente código

@f1(arg)
@f2
def func(): pass

es más o menos equivalente a

def func(): pass
func = f1(arg)(f2(func))

excepto que la función original no está vinculada temporalmente al nombre func.

Distinto en la versión 3.9: Functions may be decorated with any valid assignment_expression. Previously, the grammar was much more restrictive; see PEP 614 for details.

Cuando uno o más parameters tienen la forma parameter = expression, se dice que la función tiene «valores de parámetros predeterminados». Para un parámetro con un valor predeterminado, el correspondiente argument puede omitirse desde una llamada, en cuyo caso se sustituye el valor predeterminado del parámetro. Si un parámetro tiene un valor predeterminado, todos los parámetros siguientes hasta el «*» también deben tener un valor predeterminado — esta es una restricción sintáctica que la gramática no expresa.

Los valores de los parámetros predeterminados se evalúan de izquierda a derecha cuando se ejecuta la definición de la función. Esto significa que la expresión se evalúa una vez, cuando se define la función, y que se utiliza el mismo valor «precalculado» para cada llamada . Esto es especialmente importante para entender cuando un parámetro predeterminado es un objeto mutable, como una lista o un diccionario: si la función modifica el objeto (por ejemplo, al agregar un elemento a una lista), el valor predeterminado está en efecto modificado. Esto generalmente no es lo que se pretendía. Una forma de evitar esto es usar None como valor predeterminado y probarlo explícitamente en el cuerpo de la función, por ejemplo:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin

Function call semantics are described in more detail in section Invocaciones. A function call always assigns values to all parameters mentioned in the parameter list, either from positional arguments, from keyword arguments, or from default values. If the form «*identifier» is present, it is initialized to a tuple receiving any excess positional parameters, defaulting to the empty tuple. If the form «**identifier» is present, it is initialized to a new ordered mapping receiving any excess keyword arguments, defaulting to a new empty mapping of the same type. Parameters after «*» or «*identifier» are keyword-only parameters and may only be passed by keyword arguments. Parameters before «/» are positional-only parameters and may only be passed by positional arguments.

Distinto en la versión 3.8: The / function parameter syntax may be used to indicate positional-only parameters. See PEP 570 for details.

Los parámetros pueden tener annotation de la forma «: expression» que sigue al nombre del parámetro. Cualquier parámetro puede tener una anotación, incluso las de la forma *identifier o ** identifier. Las funciones pueden tener una anotación «return» de la forma «-> expression» después de la lista de parámetros. Estas anotaciones pueden ser cualquier expresión válida de Python. La presencia de anotaciones no cambia la semántica de una función. Los valores de anotación están disponibles como valores de un diccionario con los nombres de los parámetros en el atributo __annotations__ del objeto de la función. Si se usa annotations importada desde __future__, las anotaciones se conservan como cadenas de caracteres en tiempo de ejecución que permiten la evaluación pospuesta. De lo contrario, se evalúan cuando se ejecuta la definición de la función. En este caso, las anotaciones pueden evaluarse en un orden diferente al que aparecen en el código fuente.

También es posible crear funciones anónimas (funciones no vinculadas a un nombre), para uso inmediato en expresiones. Utiliza expresiones lambda, descritas en la sección Lambdas. Tenga en cuenta que la expresión lambda es simplemente una abreviatura para una definición de función simplificada; una función definida en una sentencia «def» puede pasarse o asignarse a otro nombre al igual que una función definida por una expresión lambda. La forma «def» es en realidad más poderosa ya que permite la ejecución de múltiples sentencias y anotaciones.

Nota del programador: Las funciones son objetos de la primera-clase. Una sentencia «def» ejecutada dentro de una definición de función define una función local que se puede retornar o pasar. Las variables libres utilizadas en la función anidada pueden acceder a las variables locales de la función que contiene el def. Vea la sección Nombres y vínculos para más detalles.

Ver también

PEP 3107 - Anotaciones de funciones

La especificación original para anotaciones de funciones.

PEP 484 - Sugerencias de tipo

Definición de un significado estándar para anotaciones: sugerencias de tipo.

PEP 526 - Sintaxis para anotaciones variables

Capacidad para escribir declaraciones de variables indirectas, incluidas variables de clase y variables de instancia

PEP 563 - Evaluación pospuesta de anotaciones

Admite referencias directas dentro de las anotaciones conservando las anotaciones en forma de cadena de caracteres en tiempo de ejecución en lugar de una evaluación apresurada.

8.7. Definiciones de clase

Una definición de clase define un objeto de clase (ver sección Jerarquía de tipos estándar):

classdef    ::=  [decorators] "class" classname [inheritance] ":" suite
inheritance ::=  "(" [argument_list] ")"
classname   ::=  identifier

Una definición de clase es una sentencia ejecutable. La lista de herencia generalmente proporciona una lista de clases base (consulte Metaclases para usos más avanzados), por lo que cada elemento de la lista debe evaluar a un objeto de clase que permita la subclasificación. Las clases sin una lista de herencia heredan, por defecto, de la clase base object; por lo tanto,

class Foo:
    pass

es equivalente a

class Foo(object):
    pass

La suite de la clase se ejecuta en un nuevo marco de ejecución (ver Nombres y vínculos), usando un espacio de nombres local recién creado y el espacio de nombres global original. (Por lo general, el bloque contiene principalmente definiciones de funciones). Cuando la suite de la clase finaliza la ejecución, su marco de ejecución se descarta pero se guarda su espacio de nombres local. 3 Luego se crea un objeto de clase utilizando la lista de herencia para las clases base y el espacio de nombres local guardado para el diccionario de atributos. El nombre de la clase está vinculado a este objeto de clase en el espacio de nombres local original.

El orden en que se definen los atributos en el cuerpo de la clase se conserva en el __dict__ de la nueva clase. Tenga en cuenta que esto es confiable solo justo después de crear la clase y solo para las clases que se definieron utilizando la sintaxis de definición.

La creación de clases se puede personalizar en gran medida usando metaclasses.

Las clases también se pueden decorar: al igual que cuando se decoran funciones,

@f1(arg)
@f2
class Foo: pass

es más o menos equivalente a

class Foo: pass
Foo = f1(arg)(f2(Foo))

Las reglas de evaluación para las expresiones de decorador son las mismas que para los decoradores de funciones. El resultado se vincula al nombre de la clase.

Distinto en la versión 3.9: Classes may be decorated with any valid assignment_expression. Previously, the grammar was much more restrictive; see PEP 614 for details.

** Nota del programador: ** Las variables definidas en la definición de la clase son atributos de clase; son compartidos por instancias. Los atributos de instancia se pueden establecer en un método con self.name = value. Se puede acceder a los atributos de clase e instancia a través de la notación «self.name», y un atributo de instancia oculta un atributo de clase con el mismo nombre cuando se accede de esta manera. Los atributos de clase se pueden usar como valores predeterminados para los atributos de instancia, pero el uso de valores mutables puede generar resultados inesperados. Descriptors se puede usar para crear variables de instancia con diferentes detalles de implementación.

Ver también

PEP 3115 - Metaclases en Python 3000

La propuesta que cambió la declaración de metaclases a la sintaxis actual y la semántica de cómo se construyen las clases con metaclases.

PEP 3129 - Decoradores de clase

La propuesta que agregó decoradores de clase. Los decoradores de funciones y métodos se introdujeron en PEP 318.

8.8. Corrutinas

Nuevo en la versión 3.5.

8.8.1. Definición de la función corrutina

async_funcdef ::=  [decorators] "async" "def" funcname "(" [parameter_list] ")"
                   ["->" expression] ":" suite

La ejecución de las corrutinas de Python puede suspenderse y reanudarse en muchos puntos (ver coroutine). Dentro del cuerpo de una función de corrutina, los identificadores await y async se convierten en palabras claves reservadas; las expresiones await, async for y async with solo se puede usar en los cuerpos de funciones de corrutina.

Las funciones definidas con la sintaxis async def siempre son funciones de corrutina, incluso si no contienen palabras claves await o async.

Es un error del tipo SyntaxError usar una expresión yield from dentro del cuerpo de una función de corrutina.

Un ejemplo de una función corrutina:

async def func(param1, param2):
    do_stuff()
    await some_coroutine()

8.8.2. La sentencia async for

async_for_stmt ::=  "async" for_stmt

An asynchronous iterable provides an __aiter__ method that directly returns an asynchronous iterator, which can call asynchronous code in its __anext__ method.

The async for statement allows convenient iteration over asynchronous iterables.

El siguiente código:

async for TARGET in ITER:
    SUITE
else:
    SUITE2

Es semánticamente equivalente a:

iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True

while running:
    try:
        TARGET = await type(iter).__anext__(iter)
    except StopAsyncIteration:
        running = False
    else:
        SUITE
else:
    SUITE2

Ver también __aiter__() y __anext__() para más detalles.

Es un error del tipo SyntaxError usar una sentencia async for fuera del cuerpo de una función de corrutina.

8.8.3. La sentencia async with

async_with_stmt ::=  "async" with_stmt

Un asynchronous context manager es un context manager que puede suspender la ejecución en sus métodos enter y exit.

El siguiente código:

async with EXPRESSION as TARGET:
    SUITE

es semánticamente equivalente a:

manager = (EXPRESSION)
aenter = type(manager).__aenter__
aexit = type(manager).__aexit__
value = await aenter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not await aexit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        await aexit(manager, None, None, None)

Ver también __aenter__() y __aexit__() para más detalles.

Es un error del tipo SyntaxError usar una sentencia async with fuera del cuerpo de una función de corrutina.

Ver también

PEP 492 - Corrutinas con sintaxis async y await

La propuesta que convirtió a las corrutinas en un concepto independiente adecuado en Python, y agregó una sintaxis de soporte.

Notas al pie

1

La excepción se propaga a la pila de invocación a menos que haya una cláusula finally que provoque otra excepción. Esa nueva excepción hace que se pierda la anterior.

2

Una cadena de caracteres literal que aparece como la primera sentencia en el cuerpo de la función se transforma en el atributo __doc__ de la función y, por lo tanto, en funciones docstring.

3

Una cadena de caracteres literal que aparece como la primera sentencia en el cuerpo de la clase se transforma en el elemento del espacio de nombre __doc__ y, por lo tanto, de la clase docstring.