Python est-il fortement typé?

234

J'ai rencontré des liens qui disent que Python est un langage fortement typé.

Cependant, je pensais que dans des langues fortement typées, vous ne pouviez pas faire cela:

bob = 1
bob = "bob"

Je pensais qu'un langage fortement typé n'acceptait pas le changement de type au moment de l'exécution. J'ai peut-être une définition erronée (ou trop simpliste) des types forts / faibles.

Alors, Python est-il un langage fortement ou faiblement typé?

Pacane
la source

Réponses:

359

Python est fortement typé dynamiquement.

  • Un typage fort signifie que le type d'une valeur ne change pas de manière inattendue. Une chaîne contenant uniquement des chiffres ne devient pas par magie un nombre, comme cela peut arriver en Perl. Chaque changement de type nécessite une conversion explicite.
  • Le typage dynamique signifie que les objets d'exécution (valeurs) ont un type, par opposition au typage statique où les variables ont un type.

Quant à votre exemple

bob = 1
bob = "bob"

Cela fonctionne parce que la variable n'a pas de type; il peut nommer n'importe quel objet. Après bob=1, vous trouverez que type(bob)revient int, mais après bob="bob", il revient str. (Notez qu'il types'agit d'une fonction régulière, elle évalue donc son argument, puis renvoie le type de la valeur.)

Comparez cela à des dialectes plus anciens de C, qui étaient typés faiblement et statiquement, de sorte que les pointeurs et les entiers étaient à peu près interchangeables. (ISO C moderne nécessite des conversions dans de nombreux cas, mais mon compilateur est toujours indulgent à ce sujet par défaut.)

Je dois ajouter que le typage fort vs faible est plus un continuum qu'un choix booléen. C ++ a un typage plus fort que C (plus de conversions sont nécessaires), mais le système de type peut être inversé en utilisant des transtypages de pointeurs.

La force du système de types dans un langage dynamique tel que Python est vraiment déterminée par la façon dont ses primitives et ses fonctions de bibliothèque répondent à différents types. Par exemple, +est surchargé de sorte qu'il fonctionne sur deux nombres ou deux chaînes, mais pas sur une chaîne et un nombre. C'est un choix de conception fait lors de l' +implémentation, mais pas vraiment une nécessité découlant de la sémantique du langage. En fait, lorsque vous surchargez +un type personnalisé, vous pouvez le faire implicitement convertir n'importe quoi en nombre:

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)

Une instance de classe Foopeut être ajoutée à d'autres objets:

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

Notez que même si fortement typé Python est tout à fait bien avec l' ajout d' objets de type intet floatet retourne un objet de type float(par exemple, les int(42) + float(1)rendements 43.0). D'un autre côté, en raison de l'inadéquation entre les types, Haskell se plaindrait si l'on essayait ce qui suit (42 :: Integer) + (1 :: Float). Cela fait de Haskell un langage strictement typé, où les types sont entièrement disjoints et seule une forme contrôlée de surcharge est possible via les classes de types.

Fred Foo
la source
18
Un exemple que je ne vois pas très souvent, mais je pense qu'il est important de montrer que Python n'est pas complètement fortement typé, c'est tout ce qui est évalué comme booléen: docs.python.org/release/2.5.2/lib/truth.html
gsingh2011
25
Je ne suis pas sûr qu'il s'agisse d'un contre-exemple: les choses peuvent devenir un booléen, mais elles ne «deviennent» pas soudainement un booléen. C'est presque comme si quelqu'un appelait implicitement quelque chose comme as_boolean (<value>), ce qui n'est pas le même que le type de l'objet lui-même changeant, non?
jbrendel
15
Être honnête dans un contexte booléen n'est pas un contre-exemple, car rien n'est réellement converti en Trueou False. Mais qu'en est-il de la promotion des numéros? 1.0 + 2fonctionne aussi bien en Python qu'en Perl ou C, même si ce "1.0" + 2n'est pas le cas. Je suis d'accord avec @jbrendel que ce n'est pas vraiment une conversion implicite, c'est juste une surcharge - mais dans le même sens, Perl ne fait pas non plus de conversion implicite. Si les fonctions n'ont pas de types de paramètres déclarés, il n'y a aucun endroit où des conversions implicites se produisent.
abarnert
13
Une meilleure façon de penser au typage fort est que le type est important lors de l'exécution d'opérations sur une variable. Si le type n'est pas comme prévu, un langage qui se plaint est fortement typé (python / java) et celui qui ne l'est pas est faiblement typé (javascript) Les langages dynamiquement typés (python) sont ceux qui permettent au type d'une variable de changer à runtime alors que les langages typés statiquement (java) ne le permettent pas une fois qu'une variable est déclarée.
kashif
2
@ gsingh2011 La vérité est utile et n'est pas un faible typage en soi, mais un accident if isValid(value) - 1peut fuir. Le booléen est contraint en entier, qui est ensuite évalué comme une valeur véridique. False - 1devient véridique et True - 1devient fausse, ce qui entraîne une erreur embarrassante et difficile à déboguer de deux couches. En ce sens, python est principalement fortement typé; les contraintes de type ne provoquent généralement pas d'erreurs logiques.
Aaron3468
57

Il y a des problèmes importants qui, je pense, n'ont pas été résolus dans toutes les réponses existantes.


Un typage faible signifie permettre l'accès à la représentation sous-jacente. En C, je peux créer un pointeur sur des caractères, puis dire au compilateur que je veux l'utiliser comme pointeur sur des entiers:

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

Sur une plate-forme little-endian avec des entiers 32 bits, cela se itransforme en un tableau des nombres 0x64636261et 0x00676665. En fait, vous pouvez même convertir des pointeurs eux-mêmes en entiers (de la taille appropriée):

intptr_t i = (intptr_t)&sz;

Et bien sûr, cela signifie que je peux écraser la mémoire n'importe où dans le système. *

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

* Bien sûr, les systèmes d'exploitation modernes utilisent la mémoire virtuelle et la protection des pages, je ne peux donc qu'écraser la mémoire de mon propre processus, mais il n'y a rien sur C lui-même qui offre une telle protection, comme toute personne qui a déjà codé, disons, Classic Mac OS ou Win16 peut vous le dire.

Le Lisp traditionnel permettait des types de piratage similaires; sur certaines plates-formes, les flottants à double mot et les contre-cellules étaient du même type, et vous pouviez simplement passer l'un à une fonction qui attendait l'autre et cela "fonctionnerait".

Aujourd'hui, la plupart des langages ne sont pas aussi faibles que C et Lisp, mais beaucoup d'entre eux sont encore quelque peu fuyants. Par exemple, tout langage OO qui a un "downcast" non contrôlé *, c'est une fuite de type: vous dites essentiellement au compilateur "Je sais que je ne vous ai pas donné suffisamment d'informations pour savoir que cela est sûr, mais je suis à peu près sûr c'est ", lorsque le point essentiel d'un système de type est que le compilateur a toujours suffisamment d'informations pour savoir ce qui est sûr.

* Un abaissé vérifié n'affaiblit pas le système de type de la langue simplement parce qu'il déplace la vérification vers l'exécution. Si c'était le cas, le polymorphisme de sous-type (alias appels de fonction virtuels ou entièrement dynamiques) serait la même violation du système de type, et je ne pense pas que quiconque veuille le dire.

Très peu de langages de "scripting" sont faibles dans ce sens. Même en Perl ou Tcl, vous ne pouvez pas prendre une chaîne et simplement interpréter ses octets comme un entier. * Mais il convient de noter qu'en CPython (et de même pour de nombreux autres interprètes pour de nombreuses langues), si vous êtes vraiment persistant, vous peut utiliser ctypespour charger libpython, lancer un objet iden un POINTER(Py_Object)et forcer le système de saisie à fuir. Que cela rende le système de type faible ou non dépend de vos cas d'utilisation - si vous essayez d'implémenter un sandbox d'exécution restreinte en langage pour garantir la sécurité, vous devez faire face à ce genre d'évasions…

* Vous pouvez utiliser une fonction comme struct.unpackpour lire les octets et construire un nouvel entier à partir de "comment C représenterait ces octets", mais ce n'est évidemment pas une fuite; même Haskell le permet.


Pendant ce temps, la conversion implicite est vraiment différente d'un système de type faible ou qui fuit.

Chaque langue, même Haskell, a des fonctions pour, par exemple, convertir un entier en chaîne ou en flottant. Mais certaines langues effectueront automatiquement certaines de ces conversions pour vous - par exemple, en C, si vous appelez une fonction qui veut un float, et que vous la transmettez int, elle est convertie pour vous. Cela peut certainement entraîner des bogues avec, par exemple, des débordements inattendus, mais ce ne sont pas les mêmes types de bogues que vous obtenez d'un système de type faible. Et C n'est pas vraiment plus faible ici; vous pouvez ajouter un int et un float dans Haskell, ou même concaténer un float à une chaîne, il vous suffit de le faire plus explicitement.

Et avec les langages dynamiques, c'est assez trouble. Il n'y a rien de tel qu'une "fonction qui veut un flottant" en Python ou Perl. Mais il y a des fonctions surchargées qui font différentes choses avec différents types, et il y a un fort sentiment intuitif que, par exemple, ajouter une chaîne à autre chose est "une fonction qui veut une chaîne". En ce sens, Perl, Tcl et JavaScript semblent faire beaucoup de conversions implicites ( "a" + 1vous donne "a1"), tandis que Python en fait beaucoup moins ( "a" + 1déclenche une exception, mais 1.0 + 1vous donne 2.0*). Il est juste difficile de mettre ce sens en termes formels - pourquoi ne devrait-il pas y avoir un +qui prend une chaîne et un int, alors qu'il y a évidemment d'autres fonctions, comme l'indexation, qui le font?

* En fait, en Python moderne, cela peut être expliqué en termes de sous-typage OO, car isinstance(2, numbers.Real)c'est vrai. Je ne pense pas qu'il y ait un sens dans lequel se 2trouve une instance du type chaîne en Perl ou JavaScript… bien qu'en Tcl, ce soit le cas, puisque tout est une instance de chaîne.


Enfin, il existe une autre définition, complètement orthogonale, du typage «fort» contre «faible», où «fort» signifie puissant / flexible / expressif.

Par exemple, Haskell vous permet de définir un type qui est un nombre, une chaîne, une liste de ce type ou une mappe de chaînes à ce type, ce qui est parfaitement un moyen de représenter tout ce qui peut être décodé à partir de JSON. Il n'y a aucun moyen de définir un tel type en Java. Mais au moins Java a des types paramétriques (génériques), vous pouvez donc écrire une fonction qui prend une liste de T et savoir que les éléments sont de type T; d'autres langages, comme Java, vous obligeaient à utiliser une liste d'objets et à abattre. Mais au moins Java vous permet de créer de nouveaux types avec leurs propres méthodes; C vous permet uniquement de créer des structures. Et BCPL n'en avait même pas. Et ainsi de suite jusqu'à l'assemblage, où les seuls types sont des longueurs de bits différentes.

Donc, dans ce sens, le système de type de Haskell est plus fort que le Java moderne, qui est plus fort que le Java précédent, qui est plus fort que le C, qui est plus fort que le BCPL.

Alors, où Python s'intègre-t-il dans ce spectre? C'est un peu délicat. Dans de nombreux cas, le typage canard vous permet de simuler tout ce que vous pouvez faire dans Haskell, et même certaines choses que vous ne pouvez pas; Bien sûr, les erreurs sont détectées lors de l'exécution au lieu de la compilation, mais elles sont toujours détectées. Cependant, il y a des cas où la frappe de canard n'est pas suffisante. Par exemple, dans Haskell, vous pouvez dire qu'une liste vide d'entiers est une liste d'entiers, vous pouvez donc décider que la réduction +sur cette liste devrait retourner 0 *; en Python, une liste vide est une liste vide; il n'y a aucune information de type pour vous aider à décider ce que la réduction +devrait faire.

* En fait, Haskell ne vous laisse pas faire cela; si vous appelez la fonction de réduction qui ne prend pas de valeur de départ dans une liste vide, vous obtenez une erreur. Mais son système de type est suffisamment puissant pour que vous puissiez faire fonctionner cela, contrairement à Python.

abarnert
la source
3
Cette réponse est brillante! Dommage qu'il soit resté si longtemps en bas de la liste.
LeoR
1
Juste un petit commentaire sur votre exemple C: char sz[]n'est pas un pointeur vers char, c'est un tableau de char, et dans l'affectation il se désintègre en pointeur.
majkel.mk
39

Vous confondez «fortement typé» avec «typé dynamiquement» .

Je ne peux pas changer le type de 1en ajoutant la chaîne '12', mais je peux choisir les types que je stocke dans une variable et changer cela pendant l'exécution du programme.

Le contraire du typage dynamique est le typage statique; la déclaration des types de variables ne change pas pendant la durée de vie d'un programme. Le contraire d'un typage fort est un typage faible; le type de valeurs peut changer pendant la durée de vie d'un programme.

Martijn Pieters
la source
La description du lien est fortement typée: "En général, un langage fortement typé a des règles de frappe plus strictes au moment de la compilation, ce qui implique que des erreurs et des exceptions sont plus susceptibles de se produire pendant la compilation." implique que Python est un langage faiblement typé ..., wiki est-il incorrect?
pleut
1
@ s̮̦̩e̝͓c̮͔̞ṛ̖̖e̬̣̦t̸͉̥̳̼: ce n'est pas du tout implicite. Python a des règles de frappe strictes au moment de la compilation, chaque objet créé n'a qu'un seul type. Et «généralement» n'implique rien, cela signifie simplement que Python est une exception à cela.
Martijn Pieters
24

Selon cet article wiki Python, Python est à la fois dynamiquement et fortement typé (fournit également une bonne explication).

Vous pensez peut-être à des langages typés statiquement où les types ne peuvent pas changer pendant l'exécution du programme et la vérification de type se produit pendant la compilation pour détecter les erreurs possibles.

Cette question SO pourrait être intéressante: les langages de types dynamiques contre les langages de types statiques et cet article Wikipedia sur les systèmes de types fournit plus d'informations

Levon
la source
18

TLDR;

Le typage de Python est Dynamique pour que vous puissiez changer une variable de chaîne en int

x = 'somestring'
x = 50

Le typage Python est Strong , vous ne pouvez donc pas fusionner les types:

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

En Javascript faiblement typé, cela se produit ...

 'foo'+3 = 'foo3'

Concernant l'inférence de type

Java vous oblige à déclarer explicitement vos types d'objets

int x = 50

Kotlin utilise l'inférence pour réaliser que c'est unint

x = 50

Mais comme les deux langues utilisent des types statiques , xelles ne peuvent pas être modifiées à partir d'un int. Aucune des deux langues ne permettrait un changement dynamique comme

x = 50
x = 'now a string'
Adam Hughes
la source
Je ne connais pas les détails de Javascript mais 'x' + 3peut-être operator+surcharger et faire la conversion de type derrière la scène?
pleut
3
Quoi qu'il en soit, votre réponse est en fait plus concise et plus facile à comprendre que les précédentes.
pleut
8

Il a déjà été répondu plusieurs fois, mais Python est un langage fortement typé:

>>> 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'

Les éléments suivants en JavaScript:

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

C'est la différence entre une frappe faible et une frappe forte. Les types faibles essaient automatiquement de convertir d'un type à un autre, selon le contexte (par exemple Perl). Les types forts ne se convertissent jamais implicitement.

Votre confusion réside dans une mauvaise compréhension de la façon dont Python lie les valeurs aux noms (communément appelés variables).

En Python, les noms n'ont pas de types, vous pouvez donc faire des choses comme:

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

Et les noms peuvent être liés à n'importe quoi:

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

Pour en savoir plus:

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

et le légèrement lié mais plus avancé:

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

Wayne Werner
la source
1
Plusieurs années plus tard - une autre ressource utile et pertinente: youtu.be/_AEJHKGk9ns
Wayne Werner
Le typage fort vs faible n'a rien à voir avec le type de résultat des expressions comme 3 + '4'. JavaScript est tout aussi puissant que Python pour cet exemple.
qznc
@qznc comment Javasript est-il tout aussi fort? Je ne pense pas avoir laissé entendre que cela avait quelque chose à voir avec le type résultant, en effet, je déclare explicitement que les types faibles essaient automatiquement de se convertir d'un type à un autre .
Wayne Werner
2
@oneloop ce n'est pas nécessairement vrai, c'est juste que le comportement de combinaison des flottants et des entiers est bien défini, et se traduit par un flottant. Vous pouvez aussi le faire "3"*4en python. Le résultat est bien sûr "3333". Vous ne diriez pas que cela convertit l'une ou l'autre chose. Bien sûr, cela pourrait être juste une dispute sémantique.
Wayne Werner
1
@oneloop Ce n'est pas nécessairement vrai parce que Python produit à floatpartir de la combinaison de floatet intqu'il convertit implicitement le type. Il existe une relation naturelle entre float et int, et en effet, le type héritier le précise. Je suppose que vous pourriez faire valoir que Javascript considère '3'+4et être 'e'+4à la fois des opérations bien définies de la même manière que Python considère 3.0 + 4être bien défini, mais à ce moment-là, il n'y a vraiment pas de types forts ou faibles, juste (non) définis opérations.
Wayne Werner
6

Une variable Python stocke une référence non typée à l'objet cible qui représente la valeur.

Toute opération d'affectation signifie l'attribution de la référence non typée à l'objet affecté - c'est-à-dire que l'objet est partagé via l'original et les nouvelles références (comptées).

Le type de valeur est lié à l'objet cible, pas à la valeur de référence. La vérification du type (fort) est effectuée lorsqu'une opération avec la valeur est effectuée (temps d'exécution).

En d'autres termes, les variables (techniquement) n'ont pas de type - il n'est pas logique de penser en termes de type de variable si l'on veut être exact. Mais les références sont automatiquement déréférencées et nous pensons en fait en termes de type d'objet cible.

pepr
la source
6

Le terme "typage fort" n'a pas de définition définitive.

Par conséquent, l'utilisation du terme dépend de la personne avec qui vous parlez.

Je ne considère aucun langage dans lequel le type d'une variable n'est ni explicitement déclaré, ni typé statiquement comme fortement typé.

Un typage fort n'empêche pas seulement la conversion (par exemple, la conversion "automatique" d'un entier en une chaîne). Cela empêche l'affectation (c.-à-d. Changer le type d'une variable).

Si le code suivant compile (interprète), le langage n'est pas typé fort:

Foo = 1 Foo = "1"

Dans un langage fortement typé, un programmeur peut "compter sur" un type.

Par exemple, si un programmeur voit la déclaration,

UINT64 kZarkCount;

et il ou elle sait que 20 lignes plus tard, kZarkCount est toujours un UINT64 (tant qu'il apparaît dans le même bloc) - sans avoir à examiner le code intermédiaire.

user5330045
la source
1

Je viens de découvrir une superbe manière concise de le mémoriser:

Experssion typée dynamique / statique; valeur fortement / faiblement typée.

Il pleut
la source
0

Je pense que cet exemple simple devrait vous expliquer les différences entre la frappe forte et la frappe dynamique:

>>> 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
la source
Votre code python montre une frappe dynamique tandis que java montre une frappe statique. Un meilleur exemple serait $ var = '2' + 1 // le résultat est 3
erichlf
@ivleph je suis d'accord. il est également possible d'écrire quelque chose comme ceci: "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....

Ce qui précède créerait un cauchemar de code non maintenable dans un grand système sur une longue période. Appelez ça comme vous voulez, mais la possibilité de changer "dynamiquement" un type de variable n'est qu'une mauvaise idée ...

Ryan Alexander
la source