Intéressant et utilité de python. 3e partie

Dans les parties précédentes, nous avons examiné les tranches, les collections de déballage / emballage et certaines fonctionnalités des opérations et types booléens.

Les commentaires mentionnaient la possibilité de multiplier les collections par un scalaire:

a = [0] * 3 s = 'a' * 2 print(a, s) # -> [0, 0, 0], 'aa' 

Un développeur python plus ou moins expérimenté sait qu'il manque un mécanisme de copie lors de l'écriture

 a = [0] b = a b[0] = 1 print(a, b) # -> [1], [1] 

Que produira alors le code suivant?

 b = a * 2 b[0] = 2 print(a, b) 

Python dans ce cas fonctionne sur le principe de la moindre surprise: dans la variable a nous avons une unité, c'est-à-dire que b pourrait être déclaré et comment

 b = [1] * 2 

Dans ce cas, le comportement sera le même:

 b = a * 2 b[0] = 2 print(a, b) # -> [1], [2, 1] 

Mais pas si simple, python copie le contenu de la liste autant de fois que vous l'avez multiplié et construit une nouvelle liste. Et dans les listes, les liens vers les valeurs sont stockés, et si, lors de la copie de références à des types immuables de cette manière, tout va bien, mais avec mutable, l'effet de l'absence de copie pendant l'écriture apparaît.

 row = [0] * 2 matrix = [row] * 2 print(matrix) # -> [[0, 0], [0, 0]] matrix[0][0] = 1 print(matrix) # -> [[1, 0], [1, 0]] 

Liste des générateurs et numpy pour vous aider dans ce cas.

Des listes peuvent être ajoutées et même incrémentées, et tout itérateur peut être à droite:

 a = [0] a += (1,) a += {2} a += "ab" a += {1: 2} print(a) # -> [0, 1, 2, 'a', 'b', 1] ,     #       

Question avec une astuce (pour une interview): en python, les paramètres sont passés par référence ou par valeur?

 def inc(a): a += 1 return a a = 5 print(inc(a)) print(a) # -> 5 

Comme nous le voyons, les modifications de la valeur dans le corps de la fonction n'ont pas changé la valeur de l'objet en dehors de celui-ci, à partir de cela, nous pouvons conclure que les paramètres sont passés par valeur et écrire le code suivant:

 def appended(a): a += [1] return a a = [5] print(appended(a)) # -> [5, 1] print(a) # -> [5, 1] 


Des langages comme C ++ ont des variables stockées sur la pile et dans la mémoire dynamique. Lorsque la fonction est appelée, nous plaçons tous les arguments sur la pile, après quoi nous transférons le contrôle à la fonction. Elle connaît les tailles et les décalages des variables sur la pile, elle peut donc les interpréter correctement.
Dans le même temps, nous avons deux options: copier la mémoire de la variable sur la pile ou mettre une référence à l'objet dans la mémoire dynamique (ou à des niveaux supérieurs de la pile).
Évidemment, lors du changement de valeurs sur la pile de fonctions, les valeurs de la mémoire dynamique ne changeront pas, et lors du changement de la zone mémoire par référence, nous modifions la mémoire partagée, respectivement, tous les liens vers la même zone mémoire "verront" la nouvelle valeur.

En python, ils ont abandonné un mécanisme similaire, le remplacement est le mécanisme d'affectation du nom de variable avec l'objet, par exemple, lors de la création d'une variable:
 var = "john" 


L'interpréteur crée l'objet "john" et le var "name", puis associe l'objet au nom donné.
Lorsqu'une fonction est appelée, aucun nouvel objet n'est créé; à la place, un nom est créé dans sa portée qui est associé à un objet existant.
Mais python a des types mutables et immuables. Le second, par exemple, comprend des nombres: lors des opérations arithmétiques, les objets existants ne changent pas, mais un nouvel objet est créé, auquel le nom existant est alors associé. Si, après cela, aucun nom n'est associé à l'ancien objet, il sera supprimé à l'aide du mécanisme de comptage de liens.
Si le nom est associé à une variable de type variable, alors pendant les opérations avec lui la mémoire de l'objet change, en conséquence, tous les noms associés à cette zone de mémoire "voient" les changements.

Vous pouvez lire à ce sujet dans la documentation , décrite plus en détail ici .

Un autre exemple:

 a = [1, 2, 3, 4, 5] def rev(l): l.reverse() return l l = a print(a, l) # -> [1, 2, 3, 4, 5], [1, 2, 3, 4, 5] l = rev(l) print(a, l) # -> [5, 4, 3, 2, 1], [5, 4, 3, 2, 1] 

Mais que se passe-t-il si nous décidons de changer la variable en dehors de la fonction? Dans ce cas, le modificateur global nous aidera à:

 def change(): global a a += 1 a = 5 change() print(a) 

Remarque: ne faites pas cela (non, sérieusement, n'utilisez pas de variables globales dans vos programmes, et surtout pas dans les vôtres). Mieux vaut simplement renvoyer quelques valeurs de la fonction:
 def func(a, b): return a + 1, b + 1 


Cependant, en python, il existe une autre portée et un mot clé correspondant:

 def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter vget, vset = private(42) print(vget()) # -> 42 vset(0) print(vget()) # -> 0 

Dans cet exemple, nous avons créé une variable qui peut être modifiée (et dont la valeur est obtenue) uniquement via des méthodes, vous pouvez utiliser un mécanisme similaire dans les classes:

 def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter class Person: def __init__(self, name): self.getid, self.setid = private(name) adam = Person("adam") print(adam.getid()) print(adam.setid("john")) print(adam.getid()) print(dir(adam)) 

Mais peut-être vaudrait-il mieux se limiter aux propriétés ou à la définition de __getattr__ , __setattr__ .

Vous pouvez même définir __delattr__ .

Une autre caractéristique de python est la présence de deux méthodes pour obtenir l'attribut: __getattr__ et __getattribute__ .

Quelle est la différence entre eux? Le premier est appelé uniquement si l'attribut de la classe est introuvable et le second est inconditionnel. Si les deux sont déclarés dans la classe, __getattr__ n'est appelé que s'il est explicitement appelé dans __getattribute__ ou si __getattribute__ a déclenché une AttributeError.

 class Person(): def __getattr__(self, item): print("__getattr__") if item == "name": return "john" raise AttributeError def __getattribute__(self, item): print("__getattribute__") raise AttributeError person = Person() print(person.name) # -> __getattribute__ # -> __getattr__ # -> john 

Et enfin, un exemple de la façon dont python manipule librement les variables et les étendues:

  e = 42 try: 1 / 0 except Exception as e: pass print(e) # -> NameError: name 'e' is not defined 

Soit dit en passant, c'est peut-être le seul exemple où le deuxième python est meilleur que le troisième, car il génère:

  ... print(e) # -> float division by zero 

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


All Articles