Il était une fois, dans mes jours d'étudiant, j'ai été mordu par un python, bien que la période d'incubation ait été retardée et il s'est avéré que je suis devenu un programmeur de perles.
Cependant, à un moment donné, la perle s'est épuisée et j'ai décidé de prendre du python, au début, j'ai juste fait quelque chose et compris ce qui était nécessaire pour cette tâche, puis j'ai réalisé que j'avais besoin d'une sorte de connaissance systématique et lu plusieurs livres:
- Bill Lyubanovich «Simple Python. Style de programmation moderne »
- Dan Bader "Pure Python. Les subtilités de la programmation pour les pros »
- Brett Slatkin «Secrets Python: 59 conseils pour écrire un code efficace»
Ce qui m'a semblé tout à fait approprié pour comprendre les subtilités de base de la langue, bien que je ne me souvienne pas y avoir mentionné des fentes , mais je ne suis pas sûr que ce soit une fonctionnalité vraiment nécessaire - si je l'ai pressé de mémoire, alors cette méthode ne sera probablement pas suffisante, mais bien sûr tout dépend de la situation.
En conséquence, j'ai accumulé quelques notes sur les fonctionnalités de python, qui, il me semble, peuvent être utiles à quelqu'un qui souhaite y migrer à partir d'autres langues.
J'ai remarqué que lors des entretiens sur python, ils posent assez souvent des questions sur des choses qui ne sont pas liées au développement réel, comme ce qui pourrait être la clé du dictionnaire (ou ce que x = yield y
signifie), enfin les mecs, dans la vraie vie, la clé peut être seulement un nombre ou une chaîne, dans ces cas uniques où ce n'est pas le cas, vous pouvez lire la documentation et comprendre pourquoi demander cela? Pour trouver ce que la personne interrogée ne sait pas? Donc à la fin, tout le monde se souviendra de la réponse à cette question particulière et elle cessera de fonctionner.
Je considère que les versions de python supérieures à 3.5 sont pertinentes ( il est temps d'oublier le deuxième python depuis longtemps) car c'est la version dans debian stable, ce qui signifie qu'à tous les autres endroits il y a des versions plus récentes)
Comme je ne suis pas du tout un gourou du python, j'espère qu'ils me corrigeront dans les commentaires si j'ai soudainement gelé une sorte de stupidité.
Dactylographie
Python est un langage typé dynamiquement, c'est-à-dire il vérifie la correspondance des types lors de l'exécution, par exemple:
cat type.py a=5 b='5' print(a+b)
effectuer:
python3 type.py ... TypeError: unsupported operand type(s) for +: 'int' and 'str'
Cependant, si votre projet a mûri au besoin de typage statique, alors python fournit également une telle opportunité en utilisant l'analyseur statique mypy
:
mypy type.py type.py:3: error: Unsupported operand types for + ("int" and "str")
Certes, toutes les erreurs ne sont pas détectées de cette façon:
cat type2.py def greeting(name): return 'Hello ' + name greeting(5)
mypy ne jurera pas ici, mais une erreur se produira pendant l'exécution, donc les versions actuelles de python prennent en charge une syntaxe spéciale pour spécifier les types d'arguments de fonction:
cat type3.py def greeting(name: str) -> str: return 'Hello ' + name greeting(5)
et maintenant:
mypy type3.py type3.py:4: error: Argument 1 to "greeting" has incompatible type "int"; expected "str"
Variables et données
Les variables en python ne stockent pas de données, mais s'y réfèrent uniquement, et les données peuvent être mutables (mutables) et immuables (immuables).
Cela conduit à un comportement différent selon le type de données dans des situations presque identiques, par exemple, un tel code:
x = 1 y = x x = 2 print(y)
conduit au fait que les variables x
et y
réfèrent à des données différentes, et ceci:
x = [1, 2, 3] y = x x[0] = 7 print(y)
non, x
et y
restent des liens vers la même liste (bien que, comme indiqué dans les commentaires, l' exemple ne soit pas très réussi, mais je n'ai pas encore pensé à un meilleur) qu'en passant en python vous pouvez vérifier avec l'opérateur is
(je suis sûr que le créateur de Java perdra à jamais un bon sommeil) de honte quand j'ai découvert cet opérateur en python).
Bien que les lignes ressemblent à une liste, elles sont un type de données immuable, cela signifie que la chaîne elle-même ne peut pas être modifiée, vous pouvez uniquement en créer une nouvelle, mais vous pouvez attribuer une valeur différente à la variable, bien que les données d'origine ne changeront pas:
>>> mystr = 'sss' >>> newstr = mystr # >>> mystr[0] = 'a' ... TypeError: 'str' object does not support item assignment >>> mystr = 'ssa' # >>> newstr # 'sss'
En parlant de chaînes, en raison de leur immunité, concaténer une très grande liste de chaînes en ajoutant ou en ajoutant dans une boucle peut ne pas être très efficace (selon l'implémentation dans un compilateur / version particulier), généralement dans de tels cas, il est recommandé d'utiliser la méthode join , qui se comporte un peu inattendu:
>>> str_list = ['ss', 'dd', 'gg'] >>> 'XXX'.join(str_list) 'ssXXXddXXXgg' >>> str = 'hello' >>> 'XXX'.join(str) 'hXXXeXXXlXXXlXXXo'
Premièrement, la ligne où la méthode est appelée devient un séparateur, et non le début d'une nouvelle ligne comme on pourrait le penser, et deuxièmement, vous devez passer une liste (un objet itérable), et non une ligne séparée, car c'est aussi un objet itérable et sera symbolisé .
Comme les variables sont des liens, il est tout à fait normal de vouloir faire une copie d'un objet afin de ne pas casser l'objet d'origine, mais il y a un piège - la fonction de copie ne copie qu'un niveau, ce qui n'est clairement pas ce qui est attendu d'une fonction avec ce nom, alors utilisez deepcopy
.
Un problème similaire avec la copie peut se produire lorsqu'une collection est multipliée par un scalaire, comme expliqué ici .
Portée
Le sujet de la portée mérite probablement un article séparé, mais il y a une bonne réponse à SO .
En bref, la portée est lexicale et il y a six domaines de visibilité - les variables dans le corps de la fonction, en fermeture, dans le module, dans le corps de la classe, les fonctions python intégrées et les variables dans la liste et d'autres inclusions.
Il y a une subtilité - la variable par défaut est lisible dans les espaces de noms imbriqués lexicalement, mais la modification nécessite l'utilisation de mots clés spéciaux nonlocal
et global
pour modifier les variables d'un niveau supérieur ou d'une visibilité globale, respectivement.
Par exemple, un code comme celui-ci:
x = 7 print(id(x)) def func(): print(id(x)) return x print(func())
Cela fonctionne avec une variable globale, et celle-ci:
x = 7 print(id(x)) def func(): x = 1 print(id(x)) return x print(func()) print(x)
en génère déjà un local.
De mon point de vue, ce n'est pas très bon, en principe, toute utilisation de variables non locales dans une fonction fait partie de l'interface publique de la fonction, sa signature, ce qui signifie qu'elle doit être déclarée explicitement et visible au début de la fonction. De plus, les mots clés ne sont pas très informatifs - global
sonne comme une définition d'une fonction globale, mais signifie en fait use global
.
En python, il n'y a pas de point d'entrée obligatoire à partir duquel le programme démarre, comme cela se fait dans de nombreuses langues, tout ce qui est écrit au niveau du module est exécuté séquentiellement, cependant, puisque les variables au niveau du module sont des variables globales, de mon point de vue, cela devrait être une bonne pratique cramming le code principal dans la fonction main()
, suivi de son appel à la fin du fichier:
if __name__ == '__main__': main()
cette condition fonctionnera si le fichier est appelé en tant que script et non importé en tant que module.
Arguments de fonction
Python offre des opportunités tout simplement chics pour définir des arguments de fonction - positionnels, arguments nommés et leurs combinaisons.
Mais vous devez comprendre comment les arguments sont transmis - car en python, toutes les variables sont des liens vers des données, alors vous pouvez deviner que le transfert est par référence, mais il y a une particularité - le lien lui-même est passé par valeur, c'est-à-dire vous pouvez modifier la valeur mutable par référence:
def add_element(mylist): mylist.append(3) mylist = [1,2] add_element(mylist) print(mylist)
effectuer:
python3 arg_modify.py [1, 2, 3]
cependant, vous ne pouvez pas remplacer le lien d'origine dans une fonction:
def try_del(mylist): mylist = [] return mylist mylist = [1,2] try_del(mylist) print(mylist)
le lien source est vivant et fonctionne:
python3 arg_kill.py [1, 2]
Vous pouvez également définir des valeurs par défaut pour les arguments, mais il y a une chose non évidente à retenir: les valeurs par défaut sont calculées une fois lors de la définition de la fonction, cela ne crée aucun problème si vous transmettez des données inchangées comme valeur par défaut et si vous passez données variables ou valeur dynamique, le résultat sera un peu inattendu:
données mutables:
cat arg_list.py def func(arg = []): arg.append('x') return arg print(func()) print(func()) print(func())
résultat:
python3 arg_list.py ['x'] ['x', 'x'] ['x', 'x', 'x']
valeur dynamique:
cat arg_now.py from datetime import datetime def func(arg = datetime.now()): return arg print(func()) print(func()) print(func())
nous obtenons:
python3 arg_now.py 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879
OOP
La POO en python a été faite de manière très intéressante (certaines propriétés en valent la peine) et c'est un gros sujet, mais les sapiens familiers avec la POO peuvent bien tout google (ou le trouver sur le hub ), donc il n'a pas besoin de le répéter, même s'il vaut la peine de dire que le python devrait être un peu une philosophie différente - est qu'un programmeur plus intelligent des machines, et ne sont pas une menace (UPD: plus ), de sorte que le défaut de python est pas habituel pour d' autres langues modificateurs d'accès: méthodes privées mises en œuvre par l' ajout d' un double trait de soulignement (qui modifie l'exécution du nom de la méthode n'est pas OPair Discussion chance de l'utiliser), et protégé d'un trait de soulignement (qui ne fait rien, il est juste une convention de nommage).
Ceux qui manquent les fonctionnalités habituelles peuvent rechercher des tentatives pour apporter de telles opportunités à python, quelques options ( lang , python-access ) ont été googlé par moi, mais je ne les ai pas testées ni étudiées.
Le seul inconvénient des classes standard est le code passe-partout dans toutes les méthodes Dunder , j'aime personnellement la bibliothèque attrs , elle est beaucoup plus pythonique.
Il convient de mentionner que, car en Python tous les objets, y compris les fonctions et les classes, les classes peuvent être créées dynamiquement (sans utiliser eval
) par la fonction type .
Il vaut également la peine d'être lu sur les métaclasses ( sur le Habr ) et les descripteurs ( Habr ).
Une particularité qui mérite d'être rappelée est que les attributs d'une classe et d'un objet ne sont pas la même chose, dans le cas d'attributs immuables, cela ne pose pas de problème car les attributs sont «ombragés» - les attributs de l'objet avec le même nom sont créés automatiquement, mais dans le cas d'attributs mutables, vous pouvez obtenir pas tout à fait ce qui était attendu:
cat class_attr.py class MyClass: storage = [7,] def __init__(self, number): self.number = number obj = MyClass(1) obj2 = MyClass(2) obj.number = 5 obj.storage.append(8) print(obj2.storage, obj2.number)
nous obtenons:
python3 class_attr.py [7, 8] 2
comme vous pouvez le voir - ils ont changé obj
, et le storage
changé dans obj2
. cet attribut (contrairement au number
) n'appartient pas à l'instance, mais à la classe.
Constantes
Comme dans le cas des modificateurs d'accès, python n'essaie pas de limiter le développeur, par conséquent, il est impossible de définir une variable scalaire protégée contre la modification de la manière standard, il y a simplement un accord que les variables avec un nom en majuscule doivent être considérées comme des constantes.
Python, d'autre part, a des structures de données immuables telles que tuple, donc si vous voulez rendre une structure globale comme une configuration immuable et ne voulez pas de dépendances supplémentaires, namedtuple est un bon choix, bien qu'il faudra un peu plus d'efforts pour décrire les types, donc J'aime l'implémentation alternative de la structure immuable avec la notation par points - Box (voir le paramètre freez_box).
Eh bien, si vous voulez des constantes scalaires, alors vous pouvez implémenter le contrôle d'accès au stade de la "compilation" ie vérifie par mypy, exemple et détails .
.sort () vs trié ()
Il existe deux façons de trier une liste en python. La première est la méthode .sort()
qui modifie la liste d'origine et ne renvoie rien (None) i.e. ne peut pas faire cela:
my_list = my_list.sort()
La seconde est la fonction sorted()
, qui génère une nouvelle liste et peut fonctionner avec tous les objets itérables. Qui veut plus d'informations devrait commencer par SO .
Bibliothèque standard
Habituellement, la bibliothèque Python standard comprend d'excellentes solutions aux problèmes courants, mais cela vaut la peine d'être critique, car il y a suffisamment de bizarreries. Certes, il arrive aussi que ce qui semble étrange à première vue se révèle être la meilleure solution, il suffit de connaître toutes les conditions (voir ci-dessous pour la portée), mais il y a encore des bizarreries.
Par exemple, le module unitaire le plus complet fourni avec le kit n'a rien à voir avec le python et les odeurs de Java, par conséquent, comme le dit l' auteur du python : "Eveybody utilise py.test ...". Bien que très intéressant, mais pas toujours adapté, le module doctest est proposé en standard.
Le module urllib fourni n'a pas une si belle interface que le module de requêtes tierces.
La même histoire avec le module d'analyse des paramètres de ligne de commande - l' argparse fourni est une démonstration de la POO du cerveau, et le module docopt semble être juste une solution intelligente - l'auto-documentation ultime! Bien que, selon les rumeurs, malgré le docopt et pour le clic, une niche demeure.
Avec le débogueur aussi - si je comprends bien, peu de gens utilisent le pdb inclus dans le package, il existe de nombreuses alternatives, mais il semble que la majorité des développeurs utilisent ipdb , qui, à mon avis, est plus pratique à utiliser via le module de débogage .
Il permet au lieu d' import ipdb;ipdb.set_trace()
écrire simplement le import debug
, il ajoute également un module see pour une inspection facile des objets.
Pour remplacer le module de sérialisation standard, le cornichon est fait à l' aneth , en passant, il convient de se rappeler que ces modules ne sont pas adaptés à l'échange de données dans des systèmes externes, car la restauration d'objets arbitraires reçus d'une source non contrôlée n'est pas sûre, dans de tels cas, il existe json (pour REST) et gRPC (pour RPC).
Pour remplacer le module standard de traitement des expressions régulières, re fabrique le module regex avec toutes sortes de bonus supplémentaires, tels que les classes de caractères ala \p{Cyrillic}
.
Soit dit en passant, quelque chose n'est pas apparu pour Python, un débogueur amusant pour les expressions rationnelles similaires à l' orge perlé .
Voici un autre exemple - une personne a créé son module sur place pour corriger la courbure et l'incomplétude de l'API du module d' entrée de fichier standard dans la partie en place des fichiers d'édition.
Eh bien, je pense beaucoup à de tels cas, puisque j'en ai même rencontré plus d'un, alors soyez prudent et n'oubliez pas de regarder toutes sortes de listes impressionnantes utiles, je pense qu'un bon nutritionniste a un parfum à la mesure de la nature nutritionnelle de la solution, c'est d'ailleurs un sujet pour une autre discussion - selon mes sentiments (bien sûr, il n'y a pas de statistiques à ce sujet et ne peut apparemment pas l'être) dans le monde python le niveau de spécialistes est supérieur à la moyenne, car souvent les bons logiciels s'avèrent être écrits en python, écrivez dans les commentaires ce que vous en pensez.
Concurrence et concurrence
Python offre de nombreuses opportunités pour une programmation parallèle et compétitive, mais pas sans fonctionnalités.
Si vous avez besoin de parallélisme, et cela se produit lorsque vos tâches nécessitent un calcul, alors vous devez faire attention au module de multitraitement .
Et si vos tâches ont beaucoup d'attentes en matière d'E / S, alors python fournit un grand nombre d'options parmi lesquelles choisir, des threads et gevent , à asyncio .
Toutes ces options semblent tout à fait utilisables (bien que les threads nécessitent beaucoup plus de ressources), mais on a le sentiment qu'asyncio élimine lentement le reste, y compris grâce à toutes sortes de goodies comme uvloop .
Si quelqu'un n'a pas remarqué - en python, les threads ne sont pas sur le parallélisme, je ne suis pas assez compétent pour bien parler de GIL , mais il y a suffisamment de matériel sur ce sujet, donc il n'y a pas un tel besoin, la principale chose à retenir est que les threads en python (plus précisément dans CPython), ils se comportent différemment des autres langages de programmation - ils s'exécutent sur un seul cœur, ce qui signifie qu'ils ne conviennent pas aux cas où vous avez besoin d'un réel parallélisme, cependant, l'exécution du thread s'interrompt en attendant l'entrée / la sortie, afin qu'ils puissent être utilisés pour rivaliser.
Autres bizarreries
En python, a = a + b
pas toujours équivalent à a += b
:
a = [1] a = a + (2,3) TypeError: can only concatenate list (not "tuple") to list a += (2,3) a [1, 2, 3]
Je l'envoie à SO pour plus de détails, jusqu'à ce que j'aie trouvé le temps de comprendre pourquoi il en est ainsi, dans le sens pour quelle raison ils l'ont fait, comme ceci est à nouveau une question de mutabilité.
Des bizarreries qui ne sont pas des bizarreries
À première vue, il me semblait étrange que le type de gamme n'inclue pas la bordure droite, mais ensuite une personne gentille m'a dit d'ignorer où je devais apprendre et il s'est avéré que tout est assez logique.
Un autre sujet important est l'arrondi (bien que ce problème soit commun à presque tous les langages de programmation), en plus d'utiliser l'arrondi comme vous le souhaitez, sauf que tout le monde a étudié au cours de mathématiques de l'école, de sorte que les problèmes de représentation des nombres à virgule flottante y sont toujours superposés, je me réfère à article détaillé.
En gros, au lieu de l'algorithme demi- tour habituel pour les mathématiques scolaires, l'algorithme demi-pair est utilisé, ce qui réduit le risque de distorsion dans l'analyse statistique et est donc recommandé par la norme IEEE 754.
De plus, je ne pouvais pas comprendre pourquoi -22//10=-3
, puis une autre personne gentille a souligné que cela découle inévitablement de la définition mathématique elle-même, selon laquelle le reste ne peut pas être négatif, ce qui conduit à un comportement aussi inhabituel pour nombres négatifs.
ACHTUNG! Maintenant, c'est encore une chose étrange et je ne comprends rien, voir ce fil .
Débogage des expressions régulières
Et ici, il s'est avéré que dans le monde python, il n'y a pas d'outil pour le débogage interactif d'expressions régulières similaires à l'excellent module perle Regexp :: Debugger ( présentation vidéo ), bien sûr, il y a un tas d'outils en ligne, il existe une sorte de solutions propriétaires de Windows, mais pour moi ce n'est pas ça, peut-être que cela vaut la peine d'utiliser un outil de barre de perles, parce que les rexes en python ne sont pas très différents de la barre de perles, j'écrirai une instruction pour ceux qui ne possèdent pas de barre de perles:
sudo apt install cpanminus cpanm Regexp::Debugger perl -I ~/perl5/lib/perl5/ -E "use Regexp::Debugger; 'ababc' =~ /(a|b) b+ c/x"
Je pense que même une personne peu familière avec la perle comprendra où il est nécessaire d'entrer dans la ligne, et où est l'expression régulière, x
est un drapeau similaire au python re.VERBOSE.
Appuyez sur s
et parcourez l'expression régulière, une description détaillée des commandes disponibles dans la documentation .
La documentation
Il existe une fonction d'aide en python, qui vous permet d'obtenir de l'aide sur n'importe quelle fonction chargée (tirée de sa docstring), le nom de la fonction est passé en paramètre:
$ python3 >>> help(help)
mais ce n'est pas toujours un moyen pratique et il est souvent plus pratique d'utiliser l'utilitaire pydoc:
pydoc3 urllib.parse.urlparse
l'utilitaire vous permet de rechercher par mots clés et même de démarrer un serveur local avec la documentation html, mais je n'ai pas testé ce dernier.