¿Python está fuertemente tipado?

234

Me he encontrado con enlaces que dicen que Python es un lenguaje fuertemente tipado.

Sin embargo, pensé que en idiomas fuertemente tipados no podría hacer esto:

bob = 1
bob = "bob"

Pensé que un lenguaje fuertemente tipado no aceptaba el cambio de tipo en tiempo de ejecución. Tal vez tengo una definición incorrecta (o demasiado simplista) de tipos fuertes / débiles.

Entonces, ¿es Python un lenguaje de tipo fuerte o débil?

Pacane
fuente

Respuestas:

359

Python está fuertemente tipado dinámicamente.

  • Una escritura fuerte significa que el tipo de un valor no cambia de manera inesperada. Una cadena que contiene 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.
  • La tipificación dinámica significa que los objetos de tiempo de ejecución (valores) tienen un tipo, a diferencia de la tipificación estática 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 que type(bob)vuelve int, pero después bob="bob", vuelve str. (Tenga en cuenta que typees una función regular, por lo que evalúa su argumento y luego devuelve el tipo del valor).

Contraste esto con los dialectos más antiguos de C, que eran débilmente, tipados estáticamente, de modo que los punteros y los enteros eran prácticamente intercambiables. (La ISO C moderna requiere conversiones en muchos casos, pero mi compilador todavía es indulgente con esto de forma predeterminada).

Debo agregar que la tipificación fuerte frente a 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 subvertir mediante el uso de conversiones de puntero.

La fuerza del sistema de tipos en un lenguaje dinámico como Python está realmente determinada por cómo sus primitivas y funciones de biblioteca responden a diferentes tipos. Por ejemplo, +está sobrecargado para que funcione en dos números o dos cadenas, pero no en una cadena y un número. Esta es una elección de diseño realizada cuando +se implementó, pero no es realmente una necesidad derivada de la semántica del lenguaje. De hecho, cuando se sobrecarga +en un tipo personalizado, puede convertirlo implícitamente 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)

La instancia de clase Foose puede agregar a otros objetos:

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

Observe que aunque fuertemente tipado Python es completamente bien con la adición de objetos del tipo inty floaty devuelve un objeto de tipo float(por ejemplo, int(42) + float(1)rendimientos 43.0). Por otro lado, debido a la falta de coincidencia entre los tipos, Haskell se quejaría si uno intenta lo siguiente (42 :: Integer) + (1 :: Float). Esto convierte a Haskell en un lenguaje estrictamente tipado, donde los tipos son completamente disjuntos y solo es posible una forma controlada de sobrecarga a través de las clases de tipos.

Fred Foo
fuente
18
Un ejemplo que no veo muy a menudo, pero creo que es importante mostrar que Python no está completamente tipeado, es todo lo que se evalúa como booleano: docs.python.org/release/2.5.2/lib/truth.html
gsingh2011
25
No estoy tan seguro de si este es un contraejemplo: las cosas pueden evaluar a un valor booleano, pero de repente no se "convierten" en un valor booleano. Es casi como si alguien llamara implícitamente algo como as_boolean (<value>), que no es lo mismo que el tipo de objeto que cambia, ¿verdad?
jbrendel
15
Ser sincero en un contexto booleano no es un contraejemplo, porque nada se está convirtiendo realmente en Trueo False. ¿Pero qué pasa con la promoción de números? 1.0 + 2funciona igual de bien en Python que en Perl o C, aunque "1.0" + 2no lo hace. Estoy de acuerdo con @jbrendel en que esta no es realmente una conversión implícita, es solo una sobrecarga, pero en el mismo sentido, Perl tampoco está haciendo ninguna conversión implícita. Si las funciones no tienen tipos de parámetros declarados, no hay lugar para que ocurran conversiones implícitas.
abarnert
13
Una mejor manera de pensar sobre la escritura fuerte es que el tipo es importante al realizar operaciones en una variable. Si el tipo no es como se esperaba, un lenguaje que se queja está fuertemente tipado (python / java) y uno que no lo está es tipeado débilmente (javascript) Los lenguajes tipados dinámicamente (python) son aquellos que permiten cambiar el tipo de una variable en tiempo de ejecución, mientras que los lenguajes de tipo estático (java) no permiten esto una vez que se declara una variable.
kashif
2
@ gsingh2011 La verdad es útil y no es un tipeo débil por sí solo, pero if isValid(value) - 1puede perderse un accidente . El booleano se convierte en entero, que luego se evalúa como un valor verdadero. False - 1se vuelve verdadero y True - 1falso, lo que lleva a un error de dos capas vergonzosamente difícil de depurar. En este sentido, python es principalmente fuertemente tipado; Las coacciones de tipo no suelen causar errores lógicos.
Aaron3468
57

Hay algunos problemas importantes que creo que se han perdido todas las respuestas existentes.


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

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

En una plataforma little endian con enteros de 32 bits, esto se convierte ien una matriz de números 0x64636261y 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, los sistemas operativos modernos usan 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 que ofrezca tal protección, como cualquiera que haya codificado, por ejemplo, Classic Mac OS o Win16 puede decirte.

Lisp tradicional permitió tipos similares de piratería informática; en algunas plataformas, las celdas flotantes de doble palabra y las contras eran del mismo tipo, y podría pasar una a una función esperando la otra y "funcionaría".

La mayoría de los lenguajes de hoy en día no son tan débiles como lo fueron C y Lisp, pero muchos de ellos todavía son algo permeables. Por ejemplo, cualquier lenguaje OO que tenga un "downcast" no verificado, * es una filtración de tipo: esencialmente le está diciendo al compilador "Sé que no le 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 tenga suficiente información para saber qué es seguro.

* Un downcast verificado no hace que el sistema de tipos de lenguaje sea más débil simplemente porque mueve la verificación al tiempo de ejecución. Si lo hiciera, entonces el polimorfismo de subtipo (también conocido como llamadas a funciones virtuales o completamente 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 una cadena y simplemente interpretar sus bytes como un 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, usted puede usar ctypespara cargar libpython, lanzar un objeto ida un POINTER(Py_Object)y forzar la fuga del sistema de tipos. Si esto debilita o no el sistema de tipos depende de sus casos de uso: si está tratando de implementar un entorno limitado de ejecución restringido en el idioma para garantizar la seguridad, tiene que lidiar con este tipo de escapes ...

* Puede usar una función como struct.unpackleer los bytes y construir un nuevo int a partir de "cómo representaría C 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 número entero en una cadena o un flotante. Pero algunos lenguajes realizarán automáticamente algunas de esas conversiones; por ejemplo, en C, si llama a una función que quiere un floaty lo pasa int, se convierte por usted. Esto definitivamente puede conducir a errores con, por ejemplo, desbordamientos inesperados, pero no son los mismos tipos de errores que se obtienen de un sistema de tipo débil. Y C no está siendo realmente más débil aquí; puedes agregar un int y un float en Haskell, o incluso concatenar un float en una cadena, solo tienes que hacerlo más explícitamente.

Y con lenguajes dinámicos, esto es bastante turbio. No hay tal cosa como "una función que quiera un flotador" en Python o Perl. Pero hay funciones sobrecargadas que hacen cosas diferentes con diferentes tipos, y hay una fuerte sensación intuitiva de que, por ejemplo, agregar una cadena a otra cosa es "una función que quiere una cadena". En ese sentido, Perl, Tcl y JavaScript parecen hacer muchas conversiones implícitas ( "a" + 1le da "a1"), mientras que Python hace mucho menos ( "a" + 1genera una excepción, pero 1.0 + 1le da 2.0*). Es difícil poner ese sentido en términos formales: ¿por qué no debería haber un +que tome una cadena y un int, cuando obviamente hay otras funciones, como la indexación, que sí?

* En realidad, en Python moderno, eso puede explicarse en términos de subtipo OO, ya que isinstance(2, numbers.Real)es cierto. No creo que tenga sentido que 2sea ​​una instancia del tipo de cadena en Perl o JavaScript ... aunque en Tcl, en realidad lo es, ya que todo es una instancia de cadena.


Finalmente, hay otra definición, completamente ortogonal, de mecanografía "fuerte" frente a "débil", donde "fuerte" significa poderoso / flexible / expresivo.

Por ejemplo, Haskell le permite definir un tipo que es un número, una cadena, una lista de este tipo o un mapa de cadenas a este tipo, que es una manera perfecta de representar cualquier cosa que pueda decodificarse desde JSON. No hay forma de definir ese 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 obligaron a usar una Lista de objetos y bajarlos. Pero al menos Java le 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 de Java moderno, que es más fuerte que el de Java anterior, que es más fuerte que el de C, que es más fuerte que el de BCPL.

Entonces, ¿dónde encaja Python en ese espectro? Eso es un poco complicado. En muchos casos, la escritura de pato le permite simular todo lo que puede hacer en Haskell e incluso algunas cosas que no puede hacer; claro, los errores se detectan en tiempo de ejecución en lugar de tiempo de compilación, pero aún se detectan. Sin embargo, hay casos en los que la escritura de patos no es suficiente. Por ejemplo, en Haskell, puede decir que una lista vacía de entradas es una lista de entradas, 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 lo ayude a decidir qué reducción +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 de inicio en una lista vacía, obtendrá un error. Sin embargo, su sistema de tipos es lo suficientemente potente que podría hacer este trabajo, y Python no lo es.

abarnert
fuente
3
¡Esta respuesta es brillante! Una pena que haya languidecido durante tanto tiempo al final de la lista.
LeoR
1
Solo un pequeño comentario a su ejemplo de C: char sz[]no es un puntero a char, es una matriz de char, y en la asignación se descompone en puntero.
majkel.mk
39

Está confundiendo 'fuertemente tipado' con 'tipeado dinámicamente' .

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

Lo contrario de la escritura dinámica es la escritura estática; La declaración de tipos de variables no cambia durante la vida útil de un programa. Lo contrario de escribir fuerte es escribir débil; El tipo de valores puede cambiar durante la vida útil de un programa.

Martijn Pieters
fuente
La descripción en el enlace fuertemente tipada: "Generalmente, un lenguaje fuertemente tipado tiene reglas de tipeo más estrictas en el momento de la compilación, lo que implica que es más probable que ocurran errores y excepciones durante la compilación". implica que Python es un lenguaje débilmente escrito ... ¿está mal wiki?
Lloviendo
1
@ s̮̦̩e̝͓c̮͔̞ṛ̖̖e̬̣̦t̸͉̥̳̼: eso no está implícito en absoluto. Python tiene reglas de escritura estrictas en tiempo de compilación, cada objeto creado tiene un solo tipo. Y 'generalmente' no implica nada, solo significa que Python es una excepción a eso.
Martijn Pieters
24

De acuerdo con este artículo de Python wiki, Python tiene un tipo dinámico y fuerte (también proporciona una buena explicación).

Quizás esté pensando en lenguajes tipados estáticamente donde los tipos no pueden cambiar durante la ejecución del programa y la verificación de tipos ocurre durante el tiempo de compilación para detectar posibles errores.

Esta pregunta SO podría ser de interés: los lenguajes de tipo dinámico versus los lenguajes de tipo estático y este artículo de Wikipedia sobre sistemas de tipos proporciona más información

Levon
fuente
18

TLDR;

La escritura de Python es dinámica, por lo que puede cambiar una variable de cadena a un int

x = 'somestring'
x = 50

La escritura en Python es fuerte, por lo que no puede combinar tipos:

'foo' + 3 --> TypeError: cannot concatenate 'str' and 'int' objects

En Javascript débilmente tipado esto sucede ...

 'foo'+3 = 'foo3'

En cuanto a la inferencia de tipos

Java te obliga a declarar explícitamente tus tipos de objeto

int x = 50

Kotlin usa la inferencia para darse cuenta de que es unint

x = 50

Pero debido a que ambos idiomas usan tipos estáticos , xno se pueden cambiar de un int. Ninguno de los dos idiomas permitiría un cambio dinámico como

x = 50
x = 'now a string'
Adam Hughes
fuente
No conozco los detalles de Javascript, pero 'x' + 3puede estar operator+sobrecargando y haciendo la conversión de tipo detrás de escena.
Lloviendo
3
De todos modos, su respuesta es en realidad más concisa y fácil de entender que las anteriores.
Lloviendo
8

Ya ha sido respondido varias veces, pero Python es un lenguaje fuertemente tipado:

>>> x = 3
>>> y = '4'
>>> print(x+y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Lo siguiente en JavaScript:

var x = 3    
var y = '4'
alert(x + y) //Produces "34"

Esa es la diferencia entre escribir débil y escribir fuerte. Los tipos débiles intentan convertir automáticamente de un tipo a otro, dependiendo del contexto (por ejemplo, Perl). Los tipos fuertes nunca se convierten implícitamente.

Su confusión radica en un malentendido de cómo Python une los valores a los nombres (comúnmente conocidos como variables).

En Python, los nombres no tienen tipos, por lo que puede hacer cosas como:

bob = 1
bob = "bob"
bob = "An Ex-Parrot!"

Y los nombres pueden estar vinculados a cualquier cosa:

>>> def spam():
...     print("Spam, spam, spam, spam")
...
>>> spam_on_eggs = spam
>>> spam_on_eggs()
Spam, spam, spam, spam

Para más lectura:

https://en.wikipedia.org/wiki/Dynamic_dispatch

y el poco relacionado pero más avanzado:

http://effbot.org/zone/call-by-object.htm

Wayne Werner
fuente
1
Varios años después - otro recurso útil y relevante: youtu.be/_AEJHKGk9ns
Wayne Werner
La escritura fuerte frente a débil no tiene nada que ver con el tipo de resultado de expresiones como 3 + '4'. JavaScript es tan fuerte como Python para este ejemplo.
qznc
@qznc ¿cómo es Javasript tan fuerte? No creo haber insinuado que tenía algo que ver con el tipo resultante, de hecho, declaro explícitamente que los tipos débiles intentan convertir automáticamente de un tipo a otro .
Wayne Werner
2
@oneloop eso no es necesariamente cierto, es solo que el comportamiento para combinar flotadores e int está bien definido y da como resultado un flotador. También puedes hacerlo "3"*4en python. El resultado, por supuesto, es "3333". No dirías que está convirtiendo ninguna de las dos cosas. Por supuesto, eso podría ser solo discutir la semántica.
Wayne Werner
1
@oneloop No es necesariamente cierto porque Python produce floatfuera de la combinación de floaty intque está convirtiendo el tipo implícitamente. Existe una relación natural entre float e int, y de hecho, la jerarquía de tipos lo explica. Supongo que se podría argumentar que Javascript considera '3'+4y 'e'+4que ambas son operaciones bien definidas de la misma manera que Python considera 3.0 + 4que están bien definidas, pero en ese momento, en realidad, no existen los tipos fuertes o débiles, simplemente (no) definidos operaciones
Wayne Werner
6

Una variable de Python almacena una referencia sin tipo al objeto de destino que representa el valor.

Cualquier operación de asignación significa asignar la referencia sin tipo al objeto asignado, es decir, el objeto se comparte a través de las referencias originales y nuevas (contadas).

El tipo de valor está vinculado al objeto de destino, no al valor de referencia. La verificación de tipo (fuerte) se realiza cuando se realiza una operación con el valor (tiempo de ejecución).

En otras palabras, las variables (técnicamente) no tienen tipo: no tiene sentido pensar en términos de un tipo de variable si se quiere ser exacto. Pero las referencias se desreferencian automáticamente y realmente pensamos en términos del tipo del objeto de destino.

pepr
fuente
6

El término "mecanografía fuerte" no tiene una definición definida.

Por lo tanto, el uso del término depende de con quién está hablando.

No considero ningún lenguaje, en el que el tipo de una variable no se declare explícitamente o se escriba de forma estática para que se escriba con fuerza.

La escritura fuerte no solo impide la conversión (por ejemplo, la conversión "automática" de un entero a una cadena). Impide la asignación (es decir, cambiar el tipo de una variable).

Si el siguiente código compila (interpreta), el idioma no es de tipo fuerte:

Foo = 1 Foo = "1"

En un lenguaje fuertemente tipado, un programador puede "contar" con un tipo.

Por ejemplo, si un programador ve la declaración,

UINT64 kZarkCount;

y él o ella sabe que 20 líneas más tarde, kZarkCount sigue siendo un UINT64 (siempre que ocurra en el mismo bloque), sin tener que examinar el código intermedio.

usuario5330045
fuente
1

Acabo de descubrir una excelente forma concisa de memorizarlo:

Expresión escrita dinámica / estática; valor fuertemente / débilmente tipado.

Lloviendo
fuente
0

Creo que este simple ejemplo debería explicar las diferencias entre tipeo fuerte y dinámico:

>>> tup = ('1', 1, .1)
>>> for item in tup:
...     type(item)
...
<type 'str'>
<type 'int'>
<type 'float'>
>>>

Java:

public static void main(String[] args) {
        int i = 1;
        i = "1"; //will be error
        i = '0.1'; // will be error
    }
Dmitry Zagorulkin
fuente
Su código de Python demuestra la escritura dinámica mientras que Java muestra la escritura estática. Un mejor ejemplo sería $ var = '2' + 1 // el resultado es 3
erichlf
@ivleph estoy de acuerdo. también es posible escribir algo como esto: "a" * 3 == "aaa"
Dmitry Zagorulkin
-4
class testme(object):
    ''' A test object '''
    def __init__(self):
        self.y = 0

def f(aTestMe1, aTestMe2):
    return aTestMe1.y + aTestMe2.y




c = testme            #get a variable to the class
c.x = 10              #add an attribute x inital value 10
c.y = 4               #change the default attribute value of y to 4

t = testme()          # declare t to be an instance object of testme
r = testme()          # declare r to be an instance object of testme

t.y = 6               # set t.y to a number
r.y = 7               # set r.y to a number

print(f(r,t))         # call function designed to operate on testme objects

r.y = "I am r.y"      # redefine r.y to be a string

print(f(r,t))         #POW!!!!  not good....

Lo anterior crearía una pesadilla de código inmanejable en un sistema grande durante un largo período de tiempo. Llámalo como quieras, pero la capacidad de cambiar "dinámicamente" un tipo de variables es solo una mala idea ...

Ryan Alexander
fuente