Python Language Liste de multiplication et références communes


Exemple

Prenons le cas de la création d'une structure de liste imbriquée en multipliant:

li = [[]] * 3
print(li)
# Out: [[], [], []]

À première vue, nous pensons avoir une liste de trois listes imbriquées différentes. Essayons d'ajouter 1 au premier:

li[0].append(1)
print(li)
# Out: [[1], [1], [1]]

1 mais j'ai ajoutées à toutes les listes li .

La raison en est que [[]] * 3 ne crée pas une list de 3 list différentes. Au contraire, il crée une list contenant 3 références au même objet list . En tant que tel, lorsque nous ajoutons à li[0] le changement est visible dans tous les sous-éléments de li . Ceci est équivalent à:

li = []
element = [[]]
li = element + element + element
print(li)
# Out: [[], [], []]
element.append(1)
print(li)
# Out: [[1], [1], [1]]

Cela peut être corroboré si nous imprimons les adresses mémoire de la list contenue en utilisant id :

li = [[]] * 3
print([id(inner_list) for inner_list in li])
# Out: [6830760, 6830760, 6830760]

La solution consiste à créer les listes internes avec une boucle:

li = [[] for _ in range(3)]

Au lieu de créer une list unique et d'y faire 3 références, nous créons maintenant 3 listes distinctes. Ceci, encore une fois, peut être vérifié en utilisant la fonction id :

print([id(inner_list) for inner_list in li])
# Out: [6331048, 6331528, 6331488]

Vous pouvez aussi le faire. Il provoque une nouvelle liste vide à créer dans chaque append appel.

>>> li = []
>>> li.append([])
>>> li.append([])
>>> li.append([])
>>> for k in li: print(id(k))
... 
4315469256
4315564552
4315564808

N'utilisez pas d'index pour faire une boucle sur une séquence.

Ne pas:

for i in range(len(tab)):
    print(tab[i])

Faire :

for elem in tab:
    print(elem)

for va automatiser la plupart des opérations d'itération pour vous.

Utilisez énumérer si vous avez vraiment besoin de l'index et de l'élément .

for i, elem in enumerate(tab):
     print((i, elem))

Soyez prudent lorsque vous utilisez "==" pour vérifier si vrai ou faux

if (var == True):
    # this will execute if var is True or 1, 1.0, 1L

if (var != True):
    # this will execute if var is neither True nor 1

if (var == False):
    # this will execute if var is False or 0 (or 0.0, 0L, 0j)

if (var == None):
    # only execute if var is None

if var:
    # execute if var is a non-empty string/list/dictionary/tuple, non-0, etc

if not var:
    # execute if var is "", {}, [], (), 0, None, etc.

if var is True:
    # only execute if var is boolean True, not 1

if var is False:
    # only execute if var is boolean False, not 0

if var is None:
    # same as var == None

Ne vérifiez pas si vous le pouvez, faites-le simplement et gérez l'erreur

Les pythonistes disent généralement "C'est plus facile de demander le pardon que la permission".

Ne pas:

if os.path.isfile(file_path):
    file = open(file_path)
else:
    # do something

Faire:

try:
    file = open(file_path)
except OSError as e:
    # do something

Ou encore mieux avec Python 2.6+ :

with open(file_path) as file:

C'est beaucoup mieux parce que c'est beaucoup plus générique. Vous pouvez appliquer try/except à presque tout. Vous n'avez pas besoin de vous soucier de ce qu'il faut faire pour le prévenir, prenez simplement soin de l'erreur que vous courez.

Ne pas vérifier avec le type

Python est typé dynamiquement, par conséquent, la vérification de type vous fait perdre de la flexibilité. Au lieu de cela, utilisez le typage de canard en vérifiant le comportement. Si vous attendez une chaîne dans une fonction, utilisez str() pour convertir n'importe quel objet en chaîne. Si vous vous attendez à une liste, utilisez list() pour convertir n'importe quelle liste pouvant être itérée.

Ne pas:

def foo(name):
    if isinstance(name, str):
        print(name.lower())

def bar(listing):
    if isinstance(listing, list):
        listing.extend((1, 2, 3))
        return ", ".join(listing)

Faire:

def foo(name) :
    print(str(name).lower())

def bar(listing) :
    l = list(listing)
    l.extend((1, 2, 3))
    return ", ".join(l)

En utilisant la dernière manière, foo acceptera n'importe quel objet. bar acceptera les chaînes, les tuples, les ensembles, les listes et bien plus encore. Pas cher SEC.

Ne mélangez pas les espaces et les tabulations

Utiliser l' objet comme premier parent

C'est délicat, mais il va vous mordre à mesure que votre programme se développe. Il existe des classes anciennes et nouvelles dans Python 2.x Les anciens sont, eh bien, vieux. Ils manquent de certaines fonctionnalités et peuvent avoir un comportement difficile avec l'héritage. Pour être utilisable, n'importe laquelle de vos classes doit être du "nouveau style". Pour ce faire, faites-le hériter de l' object .

Ne pas:

class Father:
    pass

class Child(Father):
    pass

Faire:

class Father(object):
    pass


class Child(Father):
    pass

Dans Python 3.x toutes les classes sont de nouveau style, vous n'avez donc pas besoin de le faire.

Ne pas initialiser les attributs de classe en dehors de la méthode init

Les personnes venant d'autres langues le trouvent tentant car c'est ce que vous faites en Java ou en PHP. Vous écrivez le nom de la classe, puis listez vos attributs et donnez-leur une valeur par défaut. Il semble fonctionner en Python, cependant, cela ne fonctionne pas comme vous le pensez. Si vous configurez les attributs de classe (attributs statiques), vous obtiendrez sa valeur à moins que ce ne soit vide. Dans ce cas, il retournera les attributs de classe. Cela implique deux grands risques:

  • Si l'attribut de classe est modifié, la valeur initiale est modifiée.

  • Si vous définissez un objet mutable comme valeur par défaut, vous obtiendrez le même objet partagé entre les instances.

Ne (sauf si vous voulez statique):

class Car(object):
    color = "red"
    wheels = [Wheel(), Wheel(), Wheel(), Wheel()]

Faire :

class Car(object):
    def __init__(self):
        self.color = "red"
        self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]