Du traducteurBrandon Rhodes est une personne très modeste qui se présente sur Twitter comme "un programmeur Python qui rembourse un prêt à la communauté sous forme de rapports ou d'essais". Le nombre de ces «rapports et essais» est impressionnant, tout comme le nombre de projets gratuits auxquels Brandon a contribué ou contribue. Et Brandon a publié deux livres et en écrit un troisième.
Je trouve très souvent dans les commentaires sur Habré une incompréhension ou un rejet fondamental des langages dynamiques, du typage dynamique, de la programmation généralisée et d'autres paradigmes. Je publie cette traduction (transcription) autorisée (abrégée) d'un des rapports de Brandon dans l'espoir qu'elle aidera les programmeurs existant dans les paradigmes des langages statiques à mieux comprendre les langages dynamiques, en particulier Python.
Comme d'habitude chez moi, je vous demande de m'informer en PM de mes fautes et fautes de frappe.
Que signifie l'expression «cas marginal» dans le titre de mon rapport? Le cas limite se présente lorsque vous parcourez une séquence d'options jusqu'à atteindre la valeur extrême. Par exemple, un polygone à n côtés. Si n = 3, alors c'est un triangle, n = 4 est un quadrilatère, n = 5 est un pentagone, etc. À mesure que n approche de l'infini, les côtés deviennent de plus en plus petits et le contour du polygone devient comme un cercle. Ainsi, le cercle est le cas limite pour les polygones réguliers. C'est ce qui arrive lorsqu'une certaine idée est amenée à sa limite.
Je veux parler de Python comme un cas extrême pour C ++. Si vous prenez toutes les bonnes idées de C ++ et les nettoyez jusqu'à leur conclusion logique, je suis sûr que vous vous retrouverez avec Python aussi naturellement qu'une série de polygones se mettra en cercle.
Actifs non essentiels
Je me suis intéressé à Python dans les années 90: c'était une période de ma vie où je me suis débarrassé des «actifs non essentiels», comme je l'appelle. Beaucoup de choses ont commencé à m'ennuyer. Les interruptions, par exemple. Rappelez-vous, une fois sur de nombreuses cartes informatiques, il y avait de tels contacts avec des cavaliers? Et vous placez ces cavaliers sur les manuels pour que la carte vidéo reçoive une interruption de priorité plus élevée, pour que votre jeu s'exécute plus rapidement? Donc, j'étais fatigué d'allouer et de libérer de la mémoire en utilisant malloc()
et free()
près au même moment où j'ai arrêté d'ajuster les performances de mon ordinateur avec des cavaliers. C'était en 1997 environ.
Je veux dire, lorsque nous étudions un processus, nous nous efforçons généralement d'en obtenir un contrôle complet, d'avoir à portée de main tous les leviers et boutons possibles. Ensuite, certaines personnes sont toujours fascinées par cette possibilité de contrôle. Mais mon caractère est que, dès que je m'habitue à la gestion et que je comprends ce qui est quoi, je commence immédiatement à chercher l'opportunité de renoncer à certains de mes pouvoirs, de transférer des leviers et des boutons sur une machine afin qu'elle m'assigne des interruptions.
Par conséquent, à la fin des années 90, je cherchais un langage de programmation qui me permettrait de me concentrer sur le domaine et la modélisation des tâches, plutôt que de me demander dans quelle zone de la mémoire de l'ordinateur mes données étaient stockées. Comment pouvons-nous simplifier le C ++ sans répéter les péchés des langages de script célèbres?
Par exemple, je ne pouvais pas utiliser Perl, et vous savez pourquoi? Ce signe dollar! Il a immédiatement précisé que le créateur de Perl ne comprenait pas le fonctionnement des langages de programmation. Vous utilisez le dollar dans Bash pour séparer les noms de variables du reste de la chaîne, car un programme Bash se compose littéralement de commandes perçues et de leurs paramètres. Mais après avoir appris à connaître ces langages de programmation, dans lesquels les chaînes sont placées entre des paires de petits caractères appelés guillemets, et non tout au long du texte du programme, vous commencez à percevoir $
comme une ordure visuelle. Le signe dollar est inutile, c'est moche, il faut y aller! Si vous souhaitez concevoir un langage pour une programmation sérieuse, vous ne devez pas utiliser de caractères spéciaux pour indiquer des variables.
Syntaxe
Et la syntaxe? Prenez C comme base! Cela fonctionne plutôt bien. Que l'affectation soit désignée par un signe égal. Cette désignation n'est pas acceptée dans toutes les langues, mais d'une manière ou d'une autre, beaucoup y sont habitués. Mais ne faisons pas de l'affectation une expression. Les utilisateurs de notre langue ne seront pas seulement des programmeurs professionnels, mais aussi des écoliers, des scientifiques ou des scientifiques des données (si vous ne savez pas laquelle de ces catégories d'utilisateurs écrit le pire code, alors je ferai allusion que ce ne sont pas des écoliers). Nous ne donnerons pas aux utilisateurs la possibilité de changer l'état des variables dans des endroits inattendus, et nous ferons de l'affectation un opérateur.
Que faut-il alors utiliser pour dénoter l'égalité si le signe égal a déjà été utilisé pour l'affectation? Bien sûr, double affectation, comme cela se fait en C! Beaucoup y sont déjà habitués. Nous emprunterons également à C la notation pour toutes les opérations arithmétiques et au niveau du bit, car ces notations fonctionnent et beaucoup en sont très satisfaites.
Bien sûr, nous pouvons améliorer quelque chose. Que pensez-vous lorsque vous voyez le signe de pourcentage dans le texte du programme? À propos de l'interpolation de chaînes, bien sûr! Bien que %
soit principalement un opérateur de capture de module, il n'était tout simplement pas défini pour les chaînes. Et si oui, alors pourquoi ne pas le réutiliser?
Littéraux numériques et chaînes qui contrôlent les séquences avec des barres obliques inverses - tout cela ressemblera à C.
Contrôle du flux d'exécution? La même chose if
, else
, while
, break
et continue
. Bien sûr, nous ajouterons un peu de plaisir en cooptant le bon vieux for
parcourir les structures de données et les plages de valeurs. Cela sera proposé plus tard en C ++ 11, mais en Python, l'opérateur for
initialement encapsulé toutes les opérations pour calculer les tailles, parcourir les liens, incrémenter le compteur, etc., en d'autres termes, faire tout ce qui était nécessaire pour fournir à l'utilisateur un élément de la structure de données. Quel type de structures? Cela n'a pas d'importance, il suffit de le transmettre, il le comprendra.
Nous emprunterons également des exceptions à C ++, mais nous les rendrons si bon marché en termes de consommation de ressources qu'elles peuvent être utilisées non seulement pour gérer les erreurs, mais aussi pour contrôler le flux d'exécution. Nous rendrons l'indexation plus intéressante en ajoutant le découpage - la capacité d'indexer non seulement les éléments individuels des structures de données séquentielles, mais aussi leurs plages.
Oh oui! Nous allons corriger le défaut de conception d'origine en C - ajoutez une virgule pendante!
Cette histoire a commencé avec Pascal, un langage terrible dans lequel un point-virgule est utilisé comme délimiteur d' expression. Cela signifie que l'utilisateur doit mettre un point-virgule à la fin de chaque expression dans le bloc sauf la dernière . Par conséquent, chaque fois que vous modifiez l'ordre des expressions dans un programme en Pascal, vous risquez de recevoir une erreur de syntaxe si vous ne vous assurez pas de supprimer le point-virgule de la dernière ligne et de l'ajouter à la fin de la ligne qui était la dernière.
If (n = 0) then begin writeln('N is now zero'); func := 1 end
Kernigan et Ritchie ont fait ce qu'il fallait lorsqu'ils ont défini le point-virgule en C comme le terminateur de l' expression, plutôt que le délimiteur, créant cette merveilleuse symétrie lorsque chaque ligne du programme, y compris la dernière, se termine de la même manière et peut être librement échangée. Malheureusement, à l'avenir, un sentiment d'harmonie a changé pour eux, et ils ont fait de la virgule un séparateur dans les initialiseurs statiques. Cela semble bien lorsque l'expression tient sur une seule ligne:
int a[] = {4, 5, 6};
mais lorsque votre initialiseur s'allonge et que vous l'arrangez verticalement, vous obtenez la même asymétrie inconfortable qu'en Pascal:
int a[] = { 4, 5, 6 };
À un stade précoce de son développement, Python a rendu la virgule suspendue dans les structures de données complètement facultative, quelle que soit la disposition des éléments de cette structure: horizontalement ou verticalement. Soit dit en passant, cela est très pratique pour la génération automatique de code: vous n'avez pas besoin de traiter le dernier élément comme un cas spécial.
Plus tard, les normes C99 et C ++ 11 ont également corrigé le malentendu initial, vous permettant d'ajouter une virgule après le dernier littéral dans l'initialiseur.
Espaces de noms
Nous devons également implémenter dans notre langage de programmation une chose telle que les espaces de noms ou les espaces de noms. C'est une partie critique du langage qui devrait nous éviter des erreurs comme les conflits de noms. Nous le ferons plus facilement que C ++: au lieu de donner à l'utilisateur la possibilité de nommer arbitrairement un espace de noms, nous allons créer un espace de noms par module (fichier) et les désigner avec des noms de fichiers. Par exemple, si vous créez le module foo.py
, l'espace de noms foo
lui sera attribué.
Pour travailler avec un tel modèle simplifié d'espaces de noms, un utilisateur n'a besoin que d'un seul opérateur.
Créez le répertoire my_package
, placez-y le fichier my_module.py
et déclarez la classe dans le fichier:
class C(object): READ = 1 WRITE = 2
alors l'accès aux attributs de classe sera le suivant:
import my_package.my_module my_package.my_module.C.READ
Ne vous inquiétez pas, nous n'obligerons pas l'utilisateur à imprimer le nom complet à chaque fois. Nous lui donnerons l'opportunité d'utiliser plusieurs versions de l'instruction import
pour varier le degré de «proximité» de l'espace de noms:
import my_package.my_module my_package.my_module.C.READ from my_package import my_module my_module.C.READ from my_package.my_module import C C.READ
Ainsi, les mêmes noms donnés dans différents packages ne seront jamais en conflit:
import json j = json.load(file) import pickle p = pickle.load(file)
Le fait que chaque module possède son propre espace de noms signifie également que nous n'avons pas besoin d'un modificateur static
. Cependant, nous rappelons une fonction que static
effectuée - encapsulant des variables internes. Pour montrer à vos collègues qu'un nom donné (variable, classe ou module) n'est pas public, nous le commençons par un trait de soulignement, par exemple, _ignore_this
. Cela peut également être un signal pour l'IDE de ne pas utiliser ce nom dans l'auto-complétion.
Surcharge de fonction
Nous n'implémenterons pas de surcharge de fonction dans notre langage. Le mécanisme de surcharge est trop compliqué. Au lieu de cela, nous utiliserons des arguments facultatifs avec des valeurs par défaut qui peuvent être omises de l'appel, ainsi que des arguments nommés pour «sauter» sur les arguments facultatifs avec des valeurs par défaut valides et définir uniquement les valeurs qui diffèrent des valeurs par défaut. Surtout, le manque de surcharge nous évitera de devoir déterminer quelle fonction de l'ensemble des fonctions surchargées vient d'être appelée, comment le gestionnaire d'appels a fonctionné: la fonction est toujours une dans ce module, elle est facile à trouver par son nom.
API système
Nous donnerons à l'utilisateur un accès complet à de nombreuses API système, y compris les sockets. Je ne comprends pas pourquoi les auteurs de langages de script proposent toujours leurs propres façons ingénieuses d'ouvrir un socket. Cependant, ils ne réalisent jamais l'API Unix Socket complète. Ils implémentent 5-6 fonctions qu'ils comprennent et jettent tout le reste. Python, contrairement à eux, possède des modules standard pour interagir avec le système d'exploitation qui implémentent chaque appel système standard. Cela signifie que vous pouvez ouvrir le livre de Stevens dès maintenant et commencer à écrire du code. Et tous vos sockets, processus et fourches fonctionneront exactement comme indiqué. Oui, il est possible que Guido ou les premiers contributeurs Python aient fait exactement cela, car ils étaient trop paresseux pour écrire leur implémentation des bibliothèques système, trop paresseux pour expliquer à nouveau aux utilisateurs comment fonctionnent les sockets. Mais en conséquence, ils ont obtenu un effet merveilleux: vous pouvez transférer toutes vos connaissances UNIX acquises en C et C ++ vers l'environnement Python.
Nous avons donc décidé des fonctionnalités que nous «emprunterons» à C ++ pour créer notre langage de script simple. Maintenant, nous devons décider ce que nous voulons corriger.
Comportement indéfini
Comportement inconnu, comportement indéfini, comportement défini par l'implémentation ... Ce sont toutes de mauvaises idées pour le langage qui seront utilisées par les écoliers, les scientifiques et les data scientists. Et le gain de performances pour lequel de telles choses sont autorisées est souvent négligeable par rapport aux inconvénients. Au lieu de cela, nous annoncerons que tout programme syntaxiquement correct produit le même résultat sur n'importe quelle plate-forme. Nous décrirons le langage standard avec des phrases telles que «Python évalue toutes les expressions de gauche à droite» au lieu d'essayer de réorganiser les calculs en fonction du processeur, du système d'exploitation ou de la phase de lune. Si l'utilisateur est sûr que l'ordre des calculs est important, il a le droit de réécrire correctement le code: au final, l'utilisateur est le principal.
Priorités d'exploitation
Vous devez avoir rencontré des erreurs similaires: expression
oflags & 0x80 == nflags & 0x80
renvoie toujours 0, car les comparaisons en C ont priorité sur les opérations au niveau du bit. En d'autres termes, cette expression est évaluée à
oflags & (0x80 == nflags) & 0x80
Oh, ce C!
Nous éliminerons la cause potentielle de ces erreurs dans notre langage de script simple, en plaçant la priorité des opérations de comparaison derrière l'arithmétique et la manipulation des bits, afin que l'expression de notre exemple soit calculée de manière plus intuitive:
(oflags & 0x80) == (nflags & 0x80)
Autres améliorations
La lisibilité du code est importante pour nous. Si les opérations arithmétiques du langage C sont familières à l'utilisateur, même par l'arithmétique de l'école, la confusion entre les opérations logiques et au niveau du bit est une source claire d'erreurs. Nous remplacerons la double esperluette par le mot and
, et la double ligne verticale par le mot or
, afin que notre langue ressemble plus à la parole humaine qu'au piquet de caractères «informatiques».
Nous laisserons la possibilité d'un calcul abrégé à nos opérateurs logiques ( https://en.wikipedia.org/wiki/Short-circuit_evaluation ), mais aussi leur donner la possibilité de retourner la valeur finale de n'importe quel type, pas seulement booléen. Ensuite, des expressions comme
s = error.message or 'Error'
Dans cet exemple, la variable sera définie sur error.message
si elle n'est pas vide, sinon la chaîne 'Error'.
Nous étendons l'idée de C que 0 équivaut à faux à des objets autres que des entiers. Par exemple, sur des lignes et des conteneurs vides.
Nous détruirons le débordement d'entier. Notre langage sera cohérent dans la mise en œuvre et facile à utiliser, de sorte que nos utilisateurs n'auront pas besoin de se souvenir d'une valeur spéciale étrangement proche de deux milliards, après quoi l'ensemble, augmenté d'une unité, change soudainement de signe. Nous implémentons de tels entiers qui se comporteront comme des entiers jusqu'à épuisement de toute la mémoire disponible.
Typage strict vs faible
Autre problème important dans la conception du langage de script: la rigueur de la frappe. Beaucoup de spectateurs connaissent JavaScript? Que se passe-t-il si le nombre 3 est soustrait de la chaîne «4»?
js> '4' - 3 1
Super! Et si vous ajoutez le numéro 3 à la chaîne «4»?
js> '4' + 3 "43"
C'est ce qu'on appelle un typage lax (ou faible). C'est quelque chose comme un complexe d'infériorité lorsqu'un langage de programmation pense qu'un programmeur le condamnera s'il ne peut pas retourner le résultat d'une expression, même manifestement dénuée de sens, en répétant des types de transtypage. Le problème est que la conversion de type, qu'un langage faiblement typé produit automatiquement, conduit très rarement à un résultat significatif. Essayons des conversions un peu plus complexes:
js> [] + [] "" js> [] + {} "[object Object]"
Nous nous attendons à ce que l'opération d'addition soit commutative, mais que se passe-t-il si nous changeons les termes dans ce dernier cas?
js> {} + [] 0
JavaScript n'est pas seul dans ses problèmes. Perl dans une situation similaire essaie également de renvoyer au moins quelque chose:
perl> "3" + 1 4
Et awk fera quelque chose comme ça:
$ echo | awk '{print "3" + 1}' 4
Les créateurs de langages de script ont traditionnellement cru que la frappe libre était pratique . Ils se sont trompés: la frappe lâche est terrible ! Il viole le principe de localité. S'il y a une erreur dans le code, le langage de programmation doit en informer l'utilisateur, provoquant une exception aussi proche que possible de l'endroit problématique dans le code. Mais dans tous ces langages, qui transforment sans fin des types, jusqu'à ce que quelque chose soit réglé, le contrôle arrive généralement à la fin, et nous obtenons le résultat, en jugeant par lequel, dans notre programme, quelque chose ne va pas quelque part. Et nous devons déboguer tout notre programme, ligne par ligne, pour trouver cette erreur.
Un typage lâche dégrade également la lisibilité du code, car même si nous utilisons correctement la conversion de type implicite dans un programme, cela se produit de manière inattendue pour un autre programmeur.
En Python, comme en C ++, ces expressions renvoient une erreur.
>>> '4' - 3 TypeError >>> '4' + 3 TypeError
Parce que la conversion de type, si vraiment nécessaire, est facile à écrire explicitement:
>>> int('4') + 3 7 >>> '4' + str(3) '43'
Ce code est facile à lire et à entretenir, il précise ce qui se passe exactement dans le programme, ce qui conduit à ce résultat. En effet, les programmeurs Python pensent qu'explicite vaut mieux qu'implicite, et l'erreur ne doit pas passer inaperçue.
Python est un langage fortement typé, et la seule conversion de type implicite qui s'y produit se produit lors d'opérations arithmétiques sur des entiers, dont le résultat doit être exprimé sous la forme d'un nombre fractionnaire. Peut-être que cela ne devrait pas non plus être autorisé dans le programme, mais dans ce cas, trop d'utilisateurs devraient expliquer immédiatement la différence entre les nombres entiers et les nombres à virgule flottante, ce qui compliquerait leurs premières étapes en Python.
Suite: « Python comme cas ultime de C ++. Partie 2/2 . "