Présentation de Python pour les camarades dépassant le «langage A contre V» langue B "et autres préjugés

Pour tous les résidents de habrach qui ont le sentiment de déjà-vu: j'ai été invité à écrire ce post par l'article "Introduction à Python" et ses commentaires. Malheureusement, la qualité de cette "introduction" ahem ... ne parlons pas de tristes choses. Mais il était encore plus triste d'observer les querelles dans les commentaires des catégories «C ++ est plus rapide que Python», «Rust est encore plus rapide que C ++», «Python n'est pas nécessaire», etc. C'est étonnant qu'ils ne se souviennent pas de Ruby!


Comme l'a dit Bjarn Stroustrup,


"Il n'y a que deux types de langages de programmation: ceux que les gens jurent Ă  tout moment, et ceux que personne n'utilise."

Bienvenue à tous ceux qui souhaitent se familiariser avec Python sans tomber dans le piège!


La matinée dans les montagnes du Caucase de l'Est a été marquée par des cris. Deux jeunes hommes se sont assis sur un gros rocher et ont discuté vigoureusement de quelque chose, gesticulant activement. Une minute plus tard, ils ont commencé à se pousser les uns les autres, puis se sont agrippés et sont tombés d'un rocher dans (comme il s'est avéré) un buisson d'orties. Apparemment, ce buisson a poussé là pour une raison - il a immédiatement pacifié les bagarreurs et a apporté une trêve à leur différend inextinguible. Comme vous l'avez probablement deviné, j'étais l'un des débatteurs, l'autre était mon meilleur ami (bonjour, Quaker_t!), Mais le sujet de notre petit entretien était Visual Basic vs. Delphi !


Vous reconnaissez-vous? Parfois, nous transformons nos langages de programmation préférés en culte et sommes prêts à le défendre jusqu'au bout! Mais les années passent et le moment vient où «A contre B» du sujet des conflits se transforme en «Je suis plus à l'aise de travailler avec A, mais si nécessaire, j'apprendrai à travailler avec B, C, D, E et en général, avec n'importe quoi ». Ce n'est que lorsque nous rencontrons de nouveaux langages de programmation, que les vieilles habitudes et la culture peuvent ne pas nous laisser partir longtemps.


Je voudrais vous présenter Python et aider à transférer votre expérience dans une nouvelle direction. Comme toute technologie, elle a ses propres forces et faiblesses. Python, comme C ++, Rust, Ruby, JS et tout le monde, est un outil. Les instructions sont attachées à tout instrument et vous devez apprendre à utiliser correctement n'importe quel instrument.


"Auteur, vous n'avez pas de cervelle, alliez-vous nous présenter Python?" Faisons connaissance!


Python est un langage de programmation polyvalent de haut niveau dynamique. Python est un langage de programmation mature avec un écosystème et une tradition riches. Bien que la langue soit sortie en 1991, son apparence moderne a commencé à prendre forme au début des années 2000. Python est un langage chargé , dans sa bibliothèque standard, il existe des solutions pour de nombreuses occasions. Python est un langage de programmation populaire : Dropbox, Reddit, Instagram, Disqus, YouTube, Netflix, bon sang, même Eve Online et bien d'autres utilisent activement Python.


Quelle est la raison de cette popularité? Avec votre permission, je présenterai ma propre version.


Python est un langage de programmation simple . Saisie dynamique. Collecteur d'ordures. Fonctions d'ordre supérieur. Syntaxe simple pour travailler avec des dictionnaires, des ensembles, des tuples et des listes (y compris pour obtenir des tranches). Python est idéal pour les débutants: il permet de commencer avec la programmation procédurale, de passer lentement en POO et d'avoir un avant-goût de la programmation fonctionnelle. Mais cette simplicité est comme la pointe d'un iceberg. Il vaut la peine de plonger dans les profondeurs lorsque vous rencontrez la philosophie de Python - Zen Of Python . Plongez encore plus loin - et vous vous retrouvez dans un ensemble de règles claires pour la conception de code - Guide de style pour le code Python . Plongée sous-marine, le programmeur se penche progressivement sur le concept de "façon Python" ou "Pythonique". À cette étape étonnante de l'apprentissage des langues, vous commencez à comprendre pourquoi les bons programmes Python sont écrits de cette façon et non autrement. Pourquoi le langage a évolué dans ce sens, et non dans un autre. Python n'a pas réussi en vitesse. Mais il a réussi dans l'aspect le plus important de notre travail - la lisibilité. "Écrivez du code pour les gens, pas pour les voitures" - c'est la base des bases de Python.


Un bon code Python est magnifique. Et pour écrire du beau code - qu'est-ce qui n'est pas une occupation agréable?


Astuce 0: Avant de continuer à lire, veuillez jeter un œil à un coin Zen Python . La langue est basée sur ces postulats et notre communication sera beaucoup plus agréable si vous les connaissez.


Quel homme intelligent est venu avec l'indentation?


Le premier choc pour ceux qui n'ont jamais vu le code en Python est l'indentation du corps des instructions:


def main(): ins = input('Please say something') for w in ins.split(' '): if w == 'hello': print('world!') 

Je me souviens des soirées dans l'auberge de la Polytechnique de Pétersbourg lorsque mon voisin, VlK , les yeux brûlants, m'a dit qu'il avait déniché quelque chose de nouveau en Python. "Corps d'indentation? Sérieusement?" - a été ma réaction. En effet, pour une personne qui est passée de Visual Basic ( if ... end if ) à C # (accolades) via C, C ++ et Java, cette approche semblait, pour le dire légèrement, étrange. " Formatez -vous le code avec une indentation?", A demandé VlK . Bien sûr, je l'ai formaté. Plus précisément, Spiral Studio Visual l'a fait pour moi. Elle l'a bien fait. Je n'ai jamais pensé au formatage et à l'indentation - ils sont apparus dans le code par eux-mêmes et semblaient être quelque chose d'ordinaire et familier. Mais il n'y avait rien à cacher - le code était toujours formaté avec une indentation. "Alors pourquoi avez-vous besoin d'appareils orthopédiques si le corps des instructions est de toute façon décalé vers la droite?"


Cette nuit-là, je me suis assis avec Python. Avec le recul, je peux dire avec certitude ce qui a exactement aidé à absorber rapidement de nouveaux matériaux. C'était un éditeur de code. Influencé par le même VlK , peu de temps avant les événements décrits ci-dessus, je suis passé de Windows à Ubuntu et Emacs en tant qu'éditeur (dans la cour de 2007, à PyCharm, Atom, VS Code et autres - bien d'autres années). "Eh bien, maintenant Emacs va PR ..." - vous dites. Juste un peu :) Traditionnellement, la <tab> dans Emacs n'ajoute pas d'onglets, mais sert à aligner la ligne selon les règles de ce mode. Appuyez sur <tab> - et la ligne de code est déplacée vers la position appropriée suivante:



De cette façon, vous n'avez jamais à vous demander si vous avez correctement aligné le code.


Astuce 1: Lorsque vous vous familiarisez avec Python, utilisez un éditeur qui prend en charge l'indentation.


Savez-vous quel effet secondaire a toute cette honte? Le programmeur essaie d'éviter les longues constructions. Dès que la taille de la fonction dépasse les bordures verticales de l'écran, il devient plus difficile de distinguer à quelle conception appartient le bloc de code donné. Et plus il y a d'investissements, plus c'est difficile. En conséquence, vous essayez d'écrire de la manière la plus concise possible, en décomposant les longs corps de fonctions, boucles, transitions conditionnelles, etc.


Eh bien ta frappe dynamique


O, cette discussion existe presque aussi longtemps que le concept de «programmation» existe! La frappe dynamique n'est ni mauvaise ni bonne. La frappe dynamique est également notre outil. En Python, la frappe dynamique offre une grande liberté d'action. Et là où il y a une plus grande liberté d'action - plus susceptible de se tirer une balle dans le pied.


Il convient de préciser que la saisie en Python est stricte et l'ajout d'un nombre à une chaîne ne fonctionne pas:


 1 + '1' >>> TypeError: unsupported operand type(s) for +: 'int' and 'str' 

Python vérifie également la signature de la fonction lorsqu'elle est appelée et lèvera une exception si la signature d'appel n'est pas vraie:


 def sum(x, y): return x + y sum(10, 20, 30) >>> TypeError: sum() takes 2 positional arguments but 3 were given 

Mais lors du chargement d'un script, Python ne vous dira pas que la fonction attend un nombre et non une chaîne que vous lui passez. Et vous ne l'apprenez qu'au moment de l'exécution:


 def sum(x, y): return x + y sum(10, '10') >>> TypeError: can only concatenate str (not "int") to str 

Plus le défi est difficile pour le programmeur, en particulier lors de l'écriture de grands projets . Modern Python a répondu à ce défi avec un moteur d'annotation et une bibliothèque de types, et la communauté a développé des programmes qui effectuent une vérification de type statique . Par conséquent, le programmeur apprend de telles erreurs avant d'exécuter le programme:


 # main.py: def sum(x: int, y: int) -> int: return x + y sum(10, '10') $ mypy main.py tmp.py:5: error: Argument 2 to "sum" has incompatible type "str"; expected "int" 

Python n'attache aucune importance aux annotations, bien qu'il les stocke dans l'attribut __annotations__ . La seule condition est que les annotations doivent être des valeurs valides en termes de langue. Depuis leur apparition dans la version 3.0 (il y a plus de dix ans!), Ce sont les efforts de la communauté qui ont commencé à utiliser des annotations pour le marquage typé des variables et des arguments.


Un autre exemple, plus compliqué.
 #      , :   :) from typing import TypeVar, Iterable Num = TypeVar('Num', int, float) def sum(items: Iterable[Num]) -> Num: accum = 0 for item in items: accum += item return accum sum([1, 2, 3]) >>> 6 

Astuce 2: En pratique, la plupart des saisies dynamiques posent des problèmes lors de la lecture et du débogage du code. Surtout si ce code a été écrit sans annotations et que vous devez passer beaucoup de temps à déterminer les types de variables. Vous n'avez pas à indiquer et à documenter les types de tout et de tout, mais le temps consacré à une description détaillée des interfaces publiques et des sections de code les plus critiques sera récompensé au centuple!


Charlatan! Dactylographie de canard


Parfois, les passionnés de Python font semblant d'être mystérieux et parlent de "Duck typing".
La frappe de canard est l'utilisation du test de canard dans la programmation:


Si un objet se casse comme un canard, vole comme un canard et marche comme un canard, alors il s'agit très probablement d'un canard.

Prenons un exemple:


 class RpgCharacter: def __init__(self, weapon) self.weapon = weapon def battle(self): self.weapon.attack() 

Voici l'injection de dépendance classique. La classe RpgCharacter reçoit l'objet weapon dans le constructeur et plus tard, dans la méthode battle() , appelle weapon.attack() . Mais RpgCharacter ne dépend pas de la mise en œuvre spécifique de l' weapon . Il peut s'agir d'une épée, d'un BFG 9000 ou d'une baleine avec un pot de fleurs, prête à atterrir sur la tête de l'ennemi à tout moment. Il est important que l'objet ait une méthode attack() , Python n'est pas intéressé par tout le reste.



À proprement parler, la frappe de canard n'est pas unique. Il est présent dans tous les langages dynamiques (qui me sont familiers) qui implémentent la POO.


Ceci est un autre exemple de programmation minutieuse dans le monde de la frappe dynamique. Méthode mal nommée? Nommé de manière ambiguë une variable? Votre collègue, ou vous-même, après environ six mois, sera ravi de ratisser un tel code :)


Que se passerait-il si nous utilisons Java conditionnel?
 interface IWeapon { void attack(); } public class Sword implements IWeapon { public void attack() { //... } } public class RpgCharacter { IWeapon weapon; public RpgCharacter(IWeapon weapon) { this.weapon = weapon; } public void battle() { weapon.attack(); } } 

Et il y aurait un typage statique classique, avec vérification du type au stade de la compilation. Prix ​​- l'incapacité à utiliser un objet qui a une méthode attack() , mais qui IWeapon pas explicitement l'interface IWeapon .


Astuce 3 : Si vous le souhaitez, vous pouvez décrire l'interface en créant votre propre classe abstraite avec des méthodes et des propriétés . Mieux encore, passez du temps à tester et à rédiger de la documentation pour vous et vos utilisateurs de code.


Approche procédurale et __ méthodes_ spéciales __ ()


Python est un langage orienté objet et la classe d' object est à la racine de la hiérarchie d'héritage:


 isinstance('abc', object) >>> True isinstance(10, object) >>> True 

Mais lorsque obj.ToString() utilisé en Java et C #, obj.ToString() aura un appel à str(obj) en Python. Ou par exemple, au lieu de myList.length , Python aura len(my_list) . Le créateur de la langue, Guido van Rossum, a expliqué ceci comme suit:


Quand je lis le code qui dit len(x) , je sais que la longueur de quelque chose est demandée. Cela me dit immédiatement que le résultat sera un entier et que l'argument est une sorte de conteneur. Inversement, lors de la lecture de x.len() , j'ai besoin de savoir que x est une sorte de conteneur qui implémente une interface spécifique ou hérite d'une classe qui a la méthode len() . [Source] .

Néanmoins, à l'intérieur d'elle-même, les fonctions len() , str() et quelques autres appellent certaines méthodes de l'objet:


 class User: def __init__(self, name, last_name): self.name = name self.last_name = last_name def __str__(self): return f"Honourable {self.name} {self.last_name}" u = User('Alex', 'Black') label = str(u) print(label) >>> Honourable Alex Black 

Des méthodes spéciales sont également utilisées par les opérateurs de langage, à la fois mathématiques et booléens, ainsi que for ... in ... opérateurs de boucle for ... in ... , with opérateur de contexte, l'opérateur d'index [] , etc.
Par exemple, un protocole d'itérateur se compose de deux méthodes: __iter__() et __next__() :


 #  Iterable, IEnumerable, std::iterator  .. class InfinitePositiveIntegers: def __init__(self): self.counter = 0 def __iter__(self): """      .    iter(). """ return self def __next__(self): """  .    next(). """ self.counter += 1 return self.counter for i in InfinitePositiveIntegers(): print(i) >>> 1 >>> 2 >>> ... #  ,  Ctrl + C 

Eh bien, disons des méthodes spéciales. Mais pourquoi ont-ils l'air si tordus? Guido a expliqué cela par le fait qu'ils avaient les noms habituels sans le souligner, les programmeurs eux-mêmes ne les redéfiniraient pas au moins tôt ou tard. C'est-à-dire ____() est une sorte de protection contre le fou. Comme le temps l'a montré - la protection est efficace :)


Astuce 4: examinez de près les fonctions intégrées et les méthodes d'objets spéciaux . Ils font partie intégrante de la langue, sans laquelle il est impossible de la parler pleinement.


Où est l'encapsulation? Où est mon privé?! Où est mon conte de fées? !!


Python n'a pas de modificateurs d'accès pour les attributs de classe. L'intérieur des objets est ouvert à l'accès sans aucune restriction. Cependant, il existe une convention selon laquelle les attributs avec le préfixe _ sont considérés comme privés, par exemple:


 import os class MyFile: #    _os_handle = None def __init__(self, path: str): self._open(path) #    def _open(self, path): # os.open() - **    . #      open(). #   os.open()    . self._os_handle = os.open(path, os.O_RDWR | os.O_CREAT) #      def close(self): if self._os_handle is not None: os.close(self._os_handle) f = MyFile('/tmp/file.txt') print(f._os_handle) #    ""    ! f.close() 

Pourquoi?


Il n'y a rien de privé en Python. Ni la classe ni son instance ne vous cacheront ce qu'il y a à l'intérieur (grâce à quoi l'introspection la plus profonde est possible). Python vous fait confiance. Il dit en quelque sorte: "Mon pote, si tu veux fouiller dans les coins sombres - pas de problème. Je crois qu'il y a de bonnes raisons à cela et j'espère que tu ne casseras rien.

En fin de compte, nous sommes tous des adultes ici.

- Karl Fast [Source] .

Mais comment éviter les collisions de noms lors de l'héritage?

Python a un mécanisme spécial pour modifier le nom des attributs commençant par un double soulignement et ne se terminant pas par un double soulignement ( __my_attr )! Ceci est fait pour éviter les collisions de noms pendant l'héritage. Pour appeler les méthodes de classe en dehors du corps, Python ajoute l' ___ prefix ___ . Mais pour l'accès interne, rien ne change:


 class C: def __init__(self): self.__x = 10 def get_x(self): return self.__x c = C() c.__x >>> 'C' object has no attribute '__x' print(c.get_x()) >>> 10 print(c._C__x) >>> 10 

Regardons une application pratique. Par exemple, à la classe File , qui lit les fichiers du système de fichiers local, nous voulons ajouter des capacités de mise en cache. Notre collègue a réussi à écrire une classe mixin à ces fins. Mais afin d'isoler les méthodes et les attributs des conflits potentiels , un collègue a ajouté le préfixe __ à leurs noms:


 class BaseFile: def __init__(self, path): self.path = path class LocalMixin: def read_from_local(self): with open(self.path) as f: return f.read() class CachedMixin: class CacheMissError(Exception): pass def __init__(self): # Tepe,         #   __cache,   __from_cache(), # ,     ! self.__cache = {} def __from_cache(self): return self.__cache[self.path] def read_from_cache(self): try: return self.__from_cache() except KeyError as e: raise self.CacheMissError() from e def store_to_cache(self, data): self.__cache[self.path] = data class File(CachedMixin, LocalMixin, BaseFile): def __init__(self, path): CachedMixin.__init__(self) BaseFile.__init__(self, path) def read(self): try: return self.read_from_cache() except CachedMixin.CacheMissError: data = self.read_from_local() self.store_to_cache(data) return data 

Si vous êtes intéressé à regarder l'implémentation de ce mécanisme dans CPython, veuillez dans Python / compile.c


Enfin, en raison de la présence de propriétés dans le langage, cela n'a aucun sens d'écrire des getters et setters dans le style Java: getX(), setX() . Par exemple, dans les Coordinates classe écrites à l'origine,


 class Coordinates: def __init__(self, x, y): self.x = x self.y = y c = Coordinates(10, 10) print(cx, cy) >>> (10, 10) 

J'avais besoin de contrôler l'accès à l'attribut x . L'approche correcte serait de le remplacer par un property , maintenant ainsi un contrat avec le monde extérieur.


 class Coordinates: _x = 0 def __init__(self, x, y): self.x = x self.y = y @property def x(self): return self._x @x.setter def x(self, val): if val > 10: self._x = val else: raise ValueError('x should be greater than 10') c = Coordinates(20, 10) cx = 5 >>> ValueError: x should be greater than 10 

Astuce 5: Comme dans Python, le concept de champs privés et de méthodes de classe est basé sur une convention établie. Ne soyez pas offensé par les auteurs des bibliothèques si "tout a cessé de fonctionner" parce que vous avez utilisé activement les champs privés de leurs classes. En fin de compte, nous sommes tous des adultes ici :) .


Un peu sur les exceptions


La culture Python a une approche unique des exceptions. En plus de l'interception et du traitement habituels Ă  la C ++ / Java, vous rencontrerez l'utilisation d'exceptions dans le contexte


"Plus facile de demander pardon, que la permission - EAFP."

Pour paraphraser - n'écrivez pas trop if , dans la plupart des cas, l'exécution se fera sur cette branche. Au lieu de cela, try..except la logique dans try..except .


Exemple: imaginez un gestionnaire de requêtes POST qui crée un utilisateur dans une base de données conditionnelle. A l'entrée de la fonction se trouve un dictionnaire de type valeur-clé:


 def create_user_handler(data: Dict[str, str]): try: database.user.persist( username=data['username'], password=data['password'] ) except KeyError: print('There was a missing field in data passed for user creation') 

Nous n'avons pas pollué le code avec des vérifications "si le username ou le password est contenu dans les data ". Nous espérons qu'ils seront probablement là. Nous ne demandons pas de «permission» pour utiliser ces champs, mais nous nous «excusons» lorsque le prochain kulhacker publiera un formulaire avec des données manquantes.


Ne l'amenez pas au point d'absurdité!

Par exemple, vous souhaitez vérifier si le nom de famille de l'utilisateur est présent dans les données et sinon le définir sur une valeur vide. if ici serait beaucoup plus approprié:


 def create_user_handler(data): if 'last_name' not in data: data['last_name'] = '' try: database.user.persist( username=data['username'], password=data['password'], last_name=data['last_name'] ) except KeyError: print('There was a missing field in data passed for user creation') 

Les erreurs ne doivent jamais passer silencieusement. - n'ignorez pas les exceptions! Modern Python a une merveilleuse raise from construction qui vous permet de maintenir le contexte de la chaîne d'exceptions. Par exemple:


 class MyProductError(Exception): def __init__(self): super().__init__('There has been a terrible product error') def calculate(x): try: return 10 / x except ZeroDivisionError as e: raise MyProductError() from e 

Sans raise from e chaîne d'exceptions se casse sur MyProductError , et nous ne pouvons pas savoir quelle était exactement la cause de cette erreur. Avec la raise from X , la raison (c'est-à-dire X ) de l'exception levée est stockée dans l'attribut __cause__ :


 try: calculate(0) except MyProductError as e: print(e.__cause__) >>> division by zero 

Mais il y a une petite nuance dans le cas de l'itération: StopIteration

Dans le cas d'une itération, le lancement d'une exception StopIteration est le moyen officiel de signaler que l'itérateur est terminé.


 class PositiveIntegers: def __init__(self, limit): self.counter = 0 self.limit = limit def __iter__(self): return self def __next__(self): self.counter += 1 if self.counter == self.limit: #  hasNext()  moveNext(), #  ,   raise StopIteration() return self.counter for i in PositiveIntegers(5): print(i) > 1 > 2 > 3 > 4 

Astuce 6: Nous ne payons la gestion des exceptions que dans des situations exceptionnelles. Ne les négligez pas!


Il devrait y avoir une - et de préférence une seule - la manière précédente de le faire.


switch ou correspondance de motifs? - utilisez if et dictionnaires. do- ? - pour cela il y a un while et for . goto ? Je pense que vous avez deviné. Il en va de même pour certaines techniques et modèles de conception qui semblent être considérés comme acquis dans d'autres langues. La chose la plus étonnante est qu'il n'y a pas de restrictions techniques sur leur mise en œuvre, c'est juste "nous n'avons pas cette façon".


Par exemple, en Python, vous ne voyez pas souvent le modèle "Builder". Au lieu de cela, il utilise la possibilité de passer et de demander explicitement des arguments de nom à la fonction. Au lieu de cela


 human = HumanBuilder.withName("Alex").withLastName("Black").ofAge(20).withHobbies(['tennis', 'programming']).build() 

sera


 human = Human( name="Alex" last_name="Black" age=20 hobbies=['tennis', 'programming'] ) 

La bibliothèque standard n'utilise pas de chaînes de méthodes pour travailler avec des collections . Je me souviens comment un collègue venu du monde de Kotlin m'a montré le code du sens suivant (extrait de la documentation officielle de Kotlin):


 val shortGreetings = people .filter { it.name.length < 10 } .map { "Hello, ${it.name}!" } 

En Python, map() , filter() et bien d'autres, sont des fonctions et non des méthodes de collecte. En réécrivant ce code un par un, on obtient:


 short_greetings = map(lambda h: f"Hello, {h.name}", filter(lambda h: len(h.name) < 10, people)) 

À mon avis, cela a l'air horrible. Par conséquent, pour les paquets longs comme .takewhile().filter().map().reduce() il est préférable d'utiliser ce que l'on appelle inclusion (compréhension), ou bons vieux cycles. Soit dit en passant, le même exemple sur Kotlin est donné sous la forme de la liste de compréhension correspondante. Et sur Python, cela ressemble à ceci:


 short_greetings = [ f"Hello {h.name}" for h in people if len(h.name) < 10 ] 

Pour ceux qui manquent les chaînes

Il existe des bibliothèques comme Pipe ou py_linq !


Les chaînes de méthodes sont utilisées lorsqu'elles sont plus efficaces que les outils standard. Par exemple, dans le framework Web Django, les chaînes sont utilisées pour créer un objet de requête de base de données:


 query = User.objects \ .filter(last_visited__gte='2019-05-01') \ .order_by('username') \ .values('username', 'last_visited') \ [:5] 

Astuce 7: Avant de faire quelque chose de très familier de l'expérience passée, mais pas familier en Python, demandez-vous quelle décision un pythoniste expérimenté prendrait-il?


Python lent


Oui


Oui, en ce qui concerne la vitesse d'exécution par rapport aux langages typés et compilés statiquement.


Mais semblez-vous vouloir une réponse détaillée?


L'implémentation de référence Python (CPython) est loin d'être son implémentation la plus efficace. L'une des raisons importantes est le désir des développeurs de ne pas le compliquer. Et la logique est compréhensible - un code pas trop abstrus signifie moins d'erreurs, une meilleure opportunité d'apporter des modifications, et au final, plus de personnes qui veulent lire, comprendre et compléter ce code.


Jake VanderPlas dans son blog analyse ce qui se passe dans CPython sous le capot lors de l'ajout de deux variables contenant des valeurs entières:


 a = 1 b = 2 c = a + b 

Même si nous n'allons pas profondément dans la jungle de CPython, nous pouvons dire que pour stocker les variables a , b et c , l'interpréteur devra créer trois objets sur le tas, dans lesquels le type et les (pointeurs vers) les valeurs seront stockés; binary_add<int, int>(a->val, b->val) à binary_add<int, int>(a->val, b->val) le type et les valeurs pendant l'opération d'addition pour appeler quelque chose comme binary_add<int, int>(a->val, b->val) ; écrire le résultat dans c .
C'est terriblement inefficace par rapport Ă  un programme C similaire.


Un autre problème avec CPython est le soi-disant Verrou d'interprète global (GIL). Ce mécanisme, essentiellement une valeur booléenne entourée d'un mutex, est utilisé pour synchroniser l'exécution du bytecode. GIL simplifie le développement de code s'exécutant dans un environnement multi-thread: CPython n'a pas besoin de penser à synchroniser l'accès aux variables ou aux blocages. Vous devez payer pour cela car un seul thread a accès et exécute le bytecode à un moment donné:



UPD: Mais cela ne signifie pas que le programme sur Python fonctionnera comme par magie dans un environnement multi-thread! Le code sur Python n'est pas transféré au bytecode un par un et il n'y a aucune garantie quant à la compatibilité du bytecode entre les versions! Par conséquent, vous devez toujours synchroniser les threads dans le code. Heureusement, ici Python dispose d'un riche ensemble d'outils, par exemple, vous permettant de basculer entre un modèle d'exécution multi-thread et multi-processus.


Si vous êtes curieux de savoir quels efforts sont déployés pour éliminer le GIL

?


  1. . ( CFFI ) . API (extensions) C/C++. , Rust, Go Kotlin Native !
  2. , :

8: , . , IO (, , ) , , , , :)



? Linux MacOS, 95% . , 3., 2.7. Windows . : Docker, Windows Subsystem for Linux, Cygwin, , .


9: . , — - .


"Hello world" ? Super! machine learning- - Python Package Index (PyPI).


(packages), .. (virtual environments). , . - . pip . pip . , pipenv poetry — npm, bundler, cargo ..


0xA: — pip virtualenv . — , , . , — sys.path — , .


?


? . :


Dive into python...

, . , , :)


, !

Source: https://habr.com/ru/post/fr450724/


All Articles