Saltar al contenido

¿Python está fuertemente tipado?

Ten en cuenta que en las ciencias un error casi siempere suele tener varias resoluciones, pero nosotros compartiremos lo más óptimo y eficiente.

Solución:

Python se escribe fuerte y dinámicamente.

  • Fuerte escribir significa que el tipo de un valor no cambia de forma inesperada. A string contener solo dígitos no se convierte mágicamente en un número, como puede suceder en Perl. Cada cambio de tipo requiere una conversión explícita.
  • Dinámica escribir significa que los objetos en tiempo de ejecución (valores) tienen un tipo, a diferencia de static escribiendo donde las variables tienen un tipo.

En cuanto a tu ejemplo

bob = 1
bob = "bob"

Esto funciona porque la variable no tiene un tipo; puede nombrar cualquier objeto. Después bob=1, encontrarás eso type(bob) devoluciones int, pero después bob="bob", vuelve str. (Tenga en cuenta que type es una función regular, por lo que evalúa su argumento y luego devuelve el tipo de valor).

Contraste esto con los dialectos más antiguos de C, que tenían un tipo estático y débil, de modo que los punteros y los números enteros eran prácticamente intercambiables. (La ISO C moderna requiere conversiones en muchos casos, pero mi compilador sigue siendo indulgente con esto de forma predeterminada).

Debo agregar que la tipificación fuerte frente a la débil es más un continuo que una elección booleana. C ++ tiene una escritura más fuerte que C (se requieren más conversiones), pero el sistema de tipos se puede alterar mediante el uso de conversiones de punteros.

La fuerza del sistema de tipos en un lenguaje dinámico como Python está realmente determinada por cómo responden sus primitivas y funciones de biblioteca a los diferentes tipos. P.ej, + está sobrecargado para que funcione en dos números o dos cuerdas, pero no una string y un número. Esta es una elección de diseño hecha cuando + se implementó, pero no es realmente una necesidad derivada de la semántica del lenguaje. De hecho, cuando sobrecargas + en un tipo personalizado, puede hacer que convierta implícitamente cualquier cosa en un número:

def to_number(x):
    """Try to convert function argument to float-type object."""
    try: 
        return float(x) 
    except (TypeError, ValueError): 
        return 0 

class Foo:
    def __init__(self, number): 
        self.number = number

    def __add__(self, other):
        return self.number + to_number(other)

Instancia de clase Foo se puede agregar a otros objetos:

>>> a = Foo(42)
>>> a + "1"
43.0
>>> a + Foo
42
>>> a + 1
43.0
>>> a + None
42

Observe que a pesar de que Python fuertemente tipado está completamente bien al agregar objetos de tipo int y float y devuelve un objeto de tipo float (p.ej, int(42) + float(1) devoluciones 43.0). Por otro lado, debido a la falta de coincidencia entre los tipos, Haskell se quejaría si se intentara lo siguiente (42 :: Integer) + (1 :: Float). Esto hace que Haskell sea un lenguaje estrictamente tipado, donde los tipos son completamente disjuntos y solo es posible una forma controlada de sobrecarga a través de clases de tipos.

Hay algunas cuestiones importantes que creo que se han pasado por alto en todas las respuestas existentes.


Mecanografiado débil significa permitir el acceso a la representación subyacente. En C, puedo crear un puntero a los caracteres, luego decirle al compilador que quiero usarlo como un puntero a los números enteros:

char sz[] = "abcdefg";
int *i = (int *)sz;

En una plataforma little-endian con enteros de 32 bits, esto hace i en una array de los números 0x64636261 y 0x00676665. De hecho, incluso puede lanzar punteros a números enteros (del tamaño apropiado):

intptr_t i = (intptr_t)&sz;

Y, por supuesto, esto significa que puedo sobrescribir la memoria en cualquier parte del sistema. *

char *spam = (char *)0x12345678
spam[0] = 0;

* Por supuesto, el sistema operativo moderno usa memoria virtual y protección de página, por lo que solo puedo sobrescribir la memoria de mi propio proceso, pero no hay nada en C en sí que ofrezca tal protección, como cualquiera que haya codificado, digamos, Classic Mac OS o Win16 puede decirle.

Lisp tradicional permitía tipos similares de piratería; en algunas plataformas, las celdas flotantes y contras de dos palabras eran del mismo tipo, y se podía pasar una a una función esperando la otra y “funcionaría”.

La mayoría de los lenguajes de hoy no son tan débiles como lo eran C y Lisp, pero muchos de ellos todavía tienen algunas fugas. Por ejemplo, cualquier lenguaje OO que tenga un “abatido” sin marcar, * eso es una filtración de tipo: básicamente le estás diciendo al compilador “Sé que no te di suficiente información para saber que esto es seguro, pero estoy bastante seguro es, “cuando el objetivo de un sistema de tipos es que el compilador siempre tiene suficiente información para saber qué es seguro.

* Un abatimiento comprobado no debilita el sistema de tipos del lenguaje solo porque mueve el control al tiempo de ejecución. Si lo hiciera, entonces el polimorfismo de subtipo (también conocido como llamadas a funciones virtuales o totalmente dinámicas) sería la misma violación del sistema de tipos, y no creo que nadie quiera decir eso.

Muy pocos lenguajes de “scripting” son débiles en este sentido. Incluso en Perl o Tcl, no puede tomar un string y simplemente interprete sus bytes como un número entero. * Pero vale la pena señalar que en CPython (y de manera similar para muchos otros intérpretes para muchos idiomas), si es realmente persistente, puede usar ctypes para cargar libpython, lanzar un objeto id a un POINTER(Py_Object)y forzar la filtración del sistema de tipos. Si esto hace que el sistema de tipos sea débil o no depende de sus casos de uso; si está intentando implementar un entorno limitado de ejecución restringido en el idioma para garantizar la seguridad, debe lidiar con este tipo de escapes …

* Puedes usar una función como struct.unpack para leer los bytes y construir un nuevo int a partir de “cómo C representaría estos bytes”, pero eso obviamente no tiene fugas; incluso Haskell lo permite.


Mientras tanto, la conversión implícita es realmente algo diferente de un sistema de tipo débil o con fugas.

Cada idioma, incluso Haskell, tiene funciones para, por ejemplo, convertir un entero en un string o un flotador. Pero algunos lenguajes realizarán algunas de esas conversiones automáticamente, por ejemplo, en C, si llama a una función que quiere un float, y lo pasas en int, se convierte para ti. Esto definitivamente puede conducir a errores con, por ejemplo, desbordamientos inesperados, pero no son los mismos tipos de errores que se obtienen con un sistema de tipo débil. Y C no está siendo más débil aquí; puede agregar un int y un flotante en Haskell, o incluso concatenar un flotante a un string, solo tienes que hacerlo de manera más explícita.

Y con lenguajes dinámicos, esto es bastante confuso. No existe tal cosa como “una función que quiera un flotador” en Python o Perl. Pero hay funciones sobrecargadas que hacen diferentes cosas con diferentes tipos, y hay un fuerte sentido intuitivo de que, por ejemplo, agregar un string a otra cosa es “una función que quiere un string”. En ese sentido, Perl, Tcl y JavaScript parecen realizar muchas conversiones implícitas ("a" + 1 te dio "a1"), mientras que Python hace mucho menos ("a" + 1 plantea una excepción, pero 1.0 + 1 te da 2.0*). Es difícil expresar ese sentido en términos formales. ¿Por qué no debería haber una + eso toma un string y un int, cuando obviamente hay otras funciones, como indexación, que hacen?

* En realidad, en Python moderno, eso se puede explicar en términos de subtipificación OO, ya que isinstance(2, numbers.Real) es true. No creo que haya ningún sentido en el que 2 es una instancia del string escriba en Perl o JavaScript … aunque en Tcl, en realidad lo es, ya que todo es una instancia de string.


Finalmente, hay otra definición, completamente ortogonal, de escritura “fuerte” frente a “débil”, donde “fuerte” significa poderoso / flexible / expresivo.

Por ejemplo, Haskell le permite definir un tipo que es un número, un string, una lista de este tipo, o un mapa de cadenas a este tipo, que es una forma perfecta de representar cualquier cosa que se pueda decodificar desde JSON. No hay forma de definir tal tipo en Java. Pero al menos Java tiene tipos paramétricos (genéricos), por lo que puede escribir una función que tome una Lista de T y saber que los elementos son de tipo T; otros lenguajes, como los primeros Java, lo obligaban a usar una Lista de objetos y se abatía. Pero al menos Java te permite crear nuevos tipos con sus propios métodos; C solo te permite crear estructuras. Y BCPL ni siquiera tenía eso. Y así sucesivamente hasta el ensamblaje, donde los únicos tipos son diferentes longitudes de bits.

Entonces, en ese sentido, el sistema de tipos de Haskell es más fuerte que el Java moderno, que es más fuerte que el Java anterior, que es más fuerte que el C, que es más fuerte que el BCPL.

Entonces, ¿dónde encaja Python en ese espectro? Eso es un poco complicado. En muchos casos, la escritura pato le permite simular todo lo que puede hacer en Haskell, e incluso algunas cosas que no puede hacer; seguro, los errores se detectan en tiempo de ejecución en lugar de en tiempo de compilación, pero aún así se detectan. Sin embargo, hay casos en los que la escritura de pato no es suficiente. Por ejemplo, en Haskell, puede decir que una lista vacía de ints es una lista de ints, por lo que puede decidir que reducir + sobre esa lista debería devolver 0 *; en Python, una lista vacía es una lista vacía; no hay información de tipo que le ayude a decidir qué reducir + sobre lo que debería hacer.

* De hecho, Haskell no te deja hacer esto; si llama a la función de reducción que no toma un valor inicial en una lista vacía, obtiene un error. Pero su sistema de tipos es lo suficientemente poderoso como para podría hacer que esto funcione, y Python no lo es.

Está confundiendo “tipado fuerte” con “tipado dinámicamente”.

No puedo cambiar el tipo de 1 agregando el string '12', pero puedo elegir qué tipos almaceno en una variable y cambiar eso durante el tiempo de ejecución del programa.

Lo opuesto a la escritura dinámica es static mecanografía; los declaración de tipos de variables no cambia durante la vida útil de un programa. Lo opuesto a la escritura fuerte es la escritura débil; el tipo de valores puede cambiar durante la vida útil de un programa.

Reseñas y valoraciones

Si crees que te ha resultado de provecho este post, agradeceríamos que lo compartas con otros entusiastas de la programación y nos ayudes a difundir este contenido.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *