¿Cómo manejan las funciones de Python los tipos de parámetros que pasan?


305

A menos que me equivoque, crear una función en Python funciona así:

def my_func(param1, param2):
    # stuff

Sin embargo, en realidad no proporciona los tipos de esos parámetros. Además, si recuerdo, Python es un lenguaje fuertemente tipado, como tal, parece que Python no debería permitirle pasar un parámetro de un tipo diferente al que esperaba el creador de la función. Sin embargo, ¿cómo sabe Python que el usuario de la función está pasando los tipos adecuados? ¿El programa simplemente morirá si es del tipo incorrecto, suponiendo que la función realmente use el parámetro? ¿Tienes que especificar el tipo?


15
Creo que la respuesta aceptada en esta pregunta debería actualizarse para estar más en línea con las capacidades actuales que ofrece Python. Creo que esta respuesta hace el trabajo.
code_dredd

Respuestas:


173

Python está fuertemente tipado porque cada objeto tiene un tipo, cada objeto conoce su tipo, es imposible usar accidental o deliberadamente un objeto de un tipo "como si" fuera un objeto de un tipo diferente , y todas las operaciones elementales en el objeto son delegado a su tipo.

Esto no tiene nada que ver con los nombres . Un nombre en Python no "tiene un tipo": si y cuando se define un nombre, el nombre se refiere a un objeto , y el objeto tiene un tipo (pero de hecho eso no fuerza un tipo en el nombre : un nombre es un nombre).

Un nombre en Python puede referirse perfectamente a diferentes objetos en diferentes momentos (como en la mayoría de los lenguajes de programación, aunque no en todos), y no hay ninguna restricción en el nombre que, si alguna vez se ha referido a un objeto de tipo X, está por siempre limitado a referirse solo a otros objetos de tipo X. Las restricciones en los nombres no son parte del concepto de "escritura fuerte", aunque algunos entusiastas de la escritura estática (donde los nombres no quedan limitados, y en una estática, también conocido como de compilación el tiempo, la moda también) hacen un mal uso del término de esta manera.


71
Por lo tanto, parece que la escritura fuerte no es tan fuerte, en este caso particular, es más débil que la escritura estática. En mi humilde opinión, la restricción de escritura en tiempo de compilación en nombre / variable / referencia es realmente muy importante, por lo que afirmo valientemente que Python no es tan bueno como la escritura estática. en este aspecto Por favor corrígeme si estoy equivocado.
liang

19
@liang Esa es una opinión, por lo que no puede estar bien o mal. Ciertamente también es mi opinión, y he probado muchos idiomas. El hecho de que no pueda usar mi IDE para averiguar el tipo (y, por lo tanto, los miembros) de los parámetros es un gran inconveniente de Python. Si este inconveniente es más importante que las ventajas de escribir pato depende de la persona a la que le pregunte.
Maarten Bodewes

66
Pero esto no responde a ninguna de las preguntas: "Sin embargo, ¿cómo sabe Python que el usuario de la función está pasando los tipos adecuados? ¿El programa simplemente morirá si es del tipo incorrecto, suponiendo que la función realmente use el parámetro? ¿Tiene que especificar el tipo? " o ..
qPCR4vir

44
@ qPCR4vir, cualquier objeto se puede pasar como argumento. El error (una excepción, el programa no "morirá" si está codificado para atraparlo, ver try/ except) ocurrirá cuando y si se intenta una operación que el objeto no admite. En Python 3.5 ahora puede opcionalmente "especificar tipos" de argumentos, pero no se produce ningún error, per se, si se viola la especificación; la notación de escritura solo está destinada a ayudar a separar las herramientas que realizan análisis, etc., no altera el comportamiento de Python.
Alex Martelli

2
@AlexMartelli. ¡Gracias! Para mí, esta es la respuesta correcta: "El error (una excepción, el programa no" morirá "si está codificado para atraparlo, vea intentar / excepto) ..."
qPCR4vir

753

Las otras respuestas han hecho un buen trabajo al explicar la escritura de los patos y la respuesta simple de tzot :

Python no tiene variables, como otros lenguajes donde las variables tienen un tipo y un valor; tiene nombres que apuntan a objetos, que conocen su tipo.

Sin embargo , una cosa interesante ha cambiado desde 2010 (cuando se hizo la pregunta por primera vez), a saber, la implementación de PEP 3107 (implementado en Python 3). Ahora puede especificar el tipo de parámetro y el tipo de retorno de una función como esta:

def pick(l: list, index: int) -> int:
    return l[index]

Podemos ver aquí que picktoma 2 parámetros, una lista ly un número entero index. También debería devolver un número entero.

Entonces, aquí está implícito que les una lista de enteros que podemos ver sin mucho esfuerzo, pero para funciones más complejas puede ser un poco confuso lo que debe contener la lista. También queremos que el valor predeterminado indexsea ​​0. Para resolver esto, puede optar por escribir pickasí:

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

Tenga en cuenta que ahora ponemos una cadena como el tipo de l, que está sintácticamente permitido, pero no es bueno para el análisis mediante programación (que volveremos más adelante).

Es importante tener en cuenta que Python no generará un aumento TypeErrorsi pasa un flotador index, la razón de esto es uno de los puntos principales en la filosofía de diseño de Python: "Aquí todos aceptamos adultos" , lo que significa que se espera que usted tenga en cuenta lo que puede pasar a una función y lo que no puede. Si realmente desea escribir código que arroje TypeErrors, puede usar la isinstancefunción para verificar que el argumento pasado sea del tipo apropiado o una subclase de este tipo:

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

En la siguiente sección y en los comentarios se habla más sobre por qué rara vez se debe hacer esto y qué se debe hacer en su lugar.

PEP 3107 no solo mejora la legibilidad del código, sino que también tiene varios casos de uso adecuados que puede leer aquí .


La anotación de tipo recibió mucha más atención en Python 3.5 con la introducción de PEP 484, que presenta un módulo estándar para sugerencias de tipo.

Estas sugerencias de tipo provienen del verificador de tipo mypy ( GitHub ), que ahora cumple con PEP 484 .

Con el módulo de mecanografía viene con una colección bastante completa de sugerencias de tipos, que incluyen:

  • List, Tuple, Set, Map- para list, tuple, sety maprespectivamente.
  • Iterable - útil para generadores.
  • Any - Cuando podría ser cualquier cosa.
  • Union- cuando podría ser cualquier cosa dentro de un conjunto específico de tipos, a diferencia de Any.
  • Optional- Cuando podría ser Ninguno. Taquigrafía para Union[T, None].
  • TypeVar - Utilizado con genéricos.
  • Callable - se usa principalmente para funciones, pero podría usarse para otras llamadas.

Estas son las sugerencias de tipo más comunes. Se puede encontrar una lista completa en la documentación del módulo de escritura .

Aquí está el viejo ejemplo usando los métodos de anotación introducidos en el módulo de escritura:

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

Una característica poderosa es la Callableque le permite escribir métodos de anotación que toman una función como argumento. Por ejemplo:

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

El ejemplo anterior podría volverse más preciso con el uso de en TypeVarlugar de Any, pero esto se ha dejado como un ejercicio para el lector, ya que creo que ya llené mi respuesta con demasiada información sobre las nuevas características maravillosas habilitadas por las sugerencias de tipo.


Anteriormente, cuando se documentaba un código de Python con, por ejemplo, Sphinx, algunas de las funciones anteriores se podían obtener escribiendo cadenas de documentos con el siguiente formato:

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

Como puede ver, esto requiere varias líneas adicionales (el número exacto depende de cuán explícito quiera ser y de cómo formatee su cadena de documentación). Pero ahora debería estar claro para usted cómo PEP 3107 proporciona una alternativa que es superior en muchos (¿todos?). Esto es especialmente cierto en combinación con PEP 484 que, como hemos visto, proporciona un módulo estándar que define una sintaxis para este tipo de sugerencias / anotaciones que se pueden usar de tal manera que sea inequívoca y precisa pero flexible, lo que hace que Potente combinación.

En mi opinión personal, esta es una de las mejores características de Python. No puedo esperar a que la gente empiece a aprovechar su poder. Perdón por la respuesta larga, pero esto es lo que sucede cuando me emociono.


Aquí se puede encontrar un ejemplo de código de Python que usa muchas sugerencias de tipo .


2
@rickfoosusa: sospecho que no está ejecutando Python 3 en el que se agregó la función.
erb

26
¡Espera un minuto! Si el parámetro de definición y el tipo de retorno no aumentan a TypeError, ¿cuál es el punto de usar pick(l: list, index: int) -> intcomo definición de una línea? O me equivoqué, no lo sé.
Erdin Eray

24
@Eray Erdin: Ese es un malentendido común y en absoluto una mala pregunta. Se puede utilizar con fines de documentación, ayuda a los IDE a realizar una mejor autocompletación y a encontrar errores antes del tiempo de ejecución mediante el análisis estático (al igual que mypy que mencioné en la respuesta). Hay esperanzas de que el tiempo de ejecución aproveche la información y realmente acelere los programas, pero es probable que tarde mucho en implementarse. También es posible que pueda crear un decorador que arroje los TypeErrors por usted (la información se almacena en el __annotations__atributo del objeto de función).
erb

2
@ErdinEray Debo agregar que lanzar TypeErrors es una mala idea (la depuración nunca es divertida, no importa qué tan bien se planteen las excepciones). Pero no tema, la ventaja de las nuevas características descritas en mi respuesta permite una mejor manera: no confíe en ninguna comprobación en tiempo de ejecución, haga todo antes de tiempo de ejecución con mypy o use un editor que haga el análisis estático para usted, como PyCharm .
erb

2
@ Tony: cuando devuelve dos o más objetos, devuelve una tupla, por lo que debe usar la anotación de tipo Tuple, es decirdef f(a) -> Tuple[int, int]:
erb

14

No especificas un tipo. El método solo fallará (en tiempo de ejecución) si intenta acceder a los atributos que no están definidos en los parámetros que se pasan.

Entonces esta simple función:

def no_op(param1, param2):
    pass

... no fallará sin importar qué dos argumentos se pasen.

Sin embargo, esta función:

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... se producirá un error en tiempo de ejecución si param1y param2no tanto tener atributos exigibles nombradas quack.


+1: Los atributos y métodos no están determinados estáticamente. El concepto de cómo se establecería este "tipo apropiado" o "tipo incorrecto" es si el tipo funciona o no correctamente en la función.
S.Lott

11

Muchos idiomas tienen variables, que son de un tipo específico y tienen un valor. Python no tiene variables; tiene objetos, y usa nombres para referirse a estos objetos.

En otros idiomas, cuando dices:

a = 1

entonces una variable (típicamente entera) cambia su contenido al valor 1.

En Python

a = 1

significa "usar el nombre a para referirse al objeto 1 ". Puede hacer lo siguiente en una sesión interactiva de Python:

>>> type(1)
<type 'int'>

La función typese llama con el objeto 1; Como cada objeto conoce su tipo, es fácil typeencontrar dicho tipo y devolverlo.

Del mismo modo, cada vez que defina una función

def funcname(param1, param2):

la función recibe dos objetos, y los nombra param1y param2, independientemente de sus tipos. Si desea asegurarse de que los objetos recibidos sean de un tipo específico, codifique su función como si fueran del tipo o tipos necesarios y capture las excepciones que se generan si no lo son. Las excepciones lanzadas son típicamente TypeError(utilizó una operación no válida) y AttributeError(intentó acceder a un miembro inexistente (los métodos también son miembros)).


8

Python no está fuertemente tipado en el sentido de comprobación de tipo estático o en tiempo de compilación.

La mayor parte del código de Python se encuentra en el llamado "Duck Typing" , por ejemplo, busca un método readen un objeto; no le importa si el objeto es un archivo en el disco o un socket, solo desea leer N bytes de ella.


21
Python está fuertemente tipado. También se escribe dinámicamente.
Daniel Newby

1
Pero esto no responde a ninguna de las preguntas: "Sin embargo, ¿cómo sabe Python que el usuario de la función está pasando los tipos adecuados? ¿El programa simplemente morirá si es del tipo incorrecto, suponiendo que la función realmente use el parámetro? ¿Tiene que especificar el tipo? " o ..
qPCR4vir

6

Como explica Alex Martelli ,

La solución normal, Pythonic, preferida es casi invariablemente "tipear el pato": intente usar el argumento como si fuera de un cierto tipo deseado, hágalo en una declaración try / except capturando todas las excepciones que podrían surgir si el argumento no fuera de hecho de ese tipo (o cualquier otro tipo que lo imite muy bien ;-), y en la cláusula except, intente otra cosa (usando el argumento "como si" fuera de otro tipo).

Lea el resto de su publicación para obtener información útil.


5

A Python no le importa lo que pases a sus funciones. Cuando llame my_func(a,b), las variables param1 y param2 mantendrán los valores de a y b. Python no sabe que está llamando a la función con los tipos adecuados, y espera que el programador se encargue de eso. Si se llamará a su función con diferentes tipos de parámetros, puede ajustar el código de acceso a ellos con bloques try / except y evaluar los parámetros de la manera que desee.


11
Python no tiene variables, como otros lenguajes donde las variables tienen un tipo y un valor; tiene nombres que apuntan a objetos , que conocen su tipo.
tzot

2

Nunca especifiques el tipo; Python tiene el concepto de escribir pato ; Básicamente, el código que procesa los parámetros hará ciertas suposiciones sobre ellos, quizás llamando a ciertos métodos que se espera que implemente un parámetro. Si el parámetro es del tipo incorrecto, se generará una excepción.

En general, depende de su código asegurarse de que está pasando objetos del tipo adecuado; no hay un compilador para hacer cumplir esto con anticipación.


2

Hay una notable excepción al tipeo de patos que vale la pena mencionar en esta página.

Cuando la strfunción llama al __str__método de clase, verifica sutilmente su tipo:

>>> class A(object):
...     def __str__(self):
...         return 'a','b'
...
>>> a = A()
>>> print a.__str__()
('a', 'b')
>>> print str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type tuple)

Como si Guido nos insinuara qué excepción debería generar un programa si encuentra un tipo inesperado.


1

En Python todo tiene un tipo. Una función de Python hará cualquier cosa que se le pida que haga si el tipo de argumentos lo admite.

Ejemplo: fooagregará todo lo que se puede __add__editar;) sin preocuparse mucho por su tipo. Eso significa que, para evitar fallas, debe proporcionar solo aquellas cosas que admiten la adición.

def foo(a,b):
    return a + b

class Bar(object):
    pass

class Zoo(object):
    def __add__(self, other):
        return 'zoom'

if __name__=='__main__':
    print foo(1, 2)
    print foo('james', 'bond')
    print foo(Zoo(), Zoo())
    print foo(Bar(), Bar()) # Should fail

1

No vi esto mencionado en otras respuestas, así que lo agregaré al bote.

Como otros han dicho, Python no impone el tipo en los parámetros de función o método. Se asume que usted sabe lo que está haciendo y que si realmente necesita saber el tipo de algo que se pasó, lo comprobará y decidirá qué hacer por usted mismo.

Una de las principales herramientas para hacer esto es la función isinstance ().

Por ejemplo, si escribo un método que espera obtener datos de texto binario sin formato, en lugar de las cadenas codificadas utf-8 normales, podría verificar el tipo de parámetros en el camino y adaptarme a lo que encuentro, o elevar un excepción a rechazar.

def process(data):
    if not isinstance(data, bytes) and not isinstance(data, bytearray):
        raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data))
    # Do more stuff

Python también proporciona todo tipo de herramientas para excavar en objetos. Si eres valiente, incluso puedes usar importlib para crear tus propios objetos de clases arbitrarias, sobre la marcha. Hice esto para recrear objetos a partir de datos JSON. Tal cosa sería una pesadilla en un lenguaje estático como C ++.


1

Para utilizar de manera efectiva el módulo de mecanografía (nuevo en Python 3.5) incluya all ( *).

from typing import *

Y estará listo para usar:

List, Tuple, Set, Map - for list, tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

Sin embargo, todavía se puede utilizar como nombres de tipos int, list, dict, ...


1

He implementado un contenedor si alguien quisiera especificar tipos de variables.

import functools
    
def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for i in range(len(args)):
            v = args[i]
            v_name = list(func.__annotations__.keys())[i]
            v_type = list(func.__annotations__.values())[i]
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        v = result
        v_name = 'return'
        v_type = func.__annotations__['return']
        error_msg = 'Variable `' + str(v_name) + '` should be type ('
        error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
        if not isinstance(v, v_type):
                raise TypeError(error_msg)
        return result

    return check

Úselo como:

@type_check
def test(name : str) -> float:
    return 3.0

@type_check
def test2(name : str) -> str:
    return 3.0

>> test('asd')
>> 3.0

>> test(42)
>> TypeError: Variable `name` should be type (<class 'str'>) but instead is type (<class 'int'>)

>> test2('asd')
>> TypeError: Variable `return` should be type (<class 'str'>) but instead is type (<class 'float'>)

EDITAR

El código anterior no funciona si alguno de los argumentos '(o el tipo de devolución) no se declara. La siguiente edición puede ayudar, por otro lado, solo funciona para kwargs y no verifica los args.

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for name, value in kwargs.items():
            v = value
            v_name = name
            if name not in func.__annotations__:
                continue
                
            v_type = func.__annotations__[name]

            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ') '
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        if 'return' in func.__annotations__:
            v = result
            v_name = 'return'
            v_type = func.__annotations__['return']
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                    raise TypeError(error_msg)
        return result

    return check
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.