web-dev-qa-db-fra.com

tableau d'objets numpy

J'essaie d'implémenter une simulation pour un modèle de réseau (Lattice Boltzmann) en Python. Chaque site du réseau possède un certain nombre de propriétés et interagit avec les sites voisins selon certaines règles. J'ai pensé qu'il pourrait être intelligent de créer une classe avec toutes les propriétés et de créer une grille d'instances de cette classe. (Comme je suis inexpérimenté avec Python, ce n'est peut-être pas du tout une bonne idée, alors n'hésitez pas à commenter mon approche.)

Voici un exemple de jouet de ce que je fais

class site:
    def __init__(self,a,...):
        self.a = a
        .... other properties ...
    def set_a(self, new_a):
        self.a = new_a

Maintenant, je veux traiter un réseau 2D/3D (grille) de ces sites, j'ai donc essayé de faire ce qui suit (voici une grille 2D 3x3 à titre d'exemple, mais en simulation, j'aurais besoin de l'ordre de> 1000x1000X1000)

lattice = np.empty( (3,3), dtype=object)
lattice[:,:] = site(3)

Maintenant, le problème est que chaque point de réseau fait référence à la même instance, par exemple

lattice[0,0].set_a(5)

définira également la valeur de lattice [0,2] .a sur 5. Ce comportement n'est pas souhaité. Pour éviter le problème, je peux faire une boucle sur chaque point de la grille et affecter les objets élément par élément, comme

for i in range(3):
    for j in range(3):
        lattice[i,j] = site(a)

Mais existe-t-il une meilleure façon (sans impliquer les boucles) d'affecter des objets à un tableau multidimensionnel?

Merci

23
jonalm

Vous pouvez vectoriser la fonction __init__ De la classe:

import numpy as np

class Site:
    def __init__(self, a):
        self.a = a
    def set_a(self, new_a):
        self.a = new_a

vSite = np.vectorize(Site)

init_arry = np.arange(9).reshape((3,3))

lattice = np.empty((3,3), dtype=object)
lattice[:,:] = vSite(init_arry)

Cela peut sembler plus propre, mais n'a aucun avantage en termes de performances par rapport à votre solution de bouclage. Les réponses de compréhension de liste créent une liste intermédiaire python qui entraînerait un impact sur les performances.

27
Paul

La pièce manquante pour vous est que Python traite tout comme une référence. (Il y a des objets, des chaînes et des nombres et des tuples "immuables", qui sont traités plus comme des valeurs.) Quand vous le faites

lattice[:,:] = site(3)

vous dites "Python: créez un nouvel objet site, et dites à chaque élément de lattice de pointer vers cet objet." Pour voir que c'est vraiment le cas, imprimez le tableau pour voir que les adresses mémoire des objets sont toutes les mêmes:

array([[<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>],
       [<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>],
       [<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>]], dtype=object)

La boucle est une bonne façon de procéder. Avec des tableaux numpy, cela peut être votre meilleure option; avec Python listes, vous pouvez également utiliser une compréhension de liste:

lattice = [ [Site(i + j) for i in range(3)] for j in range(3) ]

Vous pouvez utiliser une compréhension de liste avec le numpy.array construction:

lattice = np.array( [ [Site(i + j) for i in range(3)] for j in range(3) ],
                    dtype=object)

Maintenant, quand vous imprimez lattice, c'est

array([[<__main__.Site object at 0x1029d53d0>,
        <__main__.Site object at 0x1029d50d0>,
        <__main__.Site object at 0x1029d5390>],
       [<__main__.Site object at 0x1029d5750>,
        <__main__.Site object at 0x1029d57d0>,
        <__main__.Site object at 0x1029d5990>],
       [<__main__.Site object at 0x1029d59d0>,
        <__main__.Site object at 0x1029d5a10>,
        <__main__.Site object at 0x1029d5a50>]], dtype=object)

de sorte que vous pouvez voir que chaque objet est unique.

Vous devez également noter que les méthodes "setter" et "getter" (par exemple, set_a) ne sont pas Pythonic. Il est préférable de définir et d'obtenir des attributs directement, puis d'utiliser le @property décorateur si vous avez VRAIMENT besoin d'empêcher l'accès en écriture à un attribut.

Notez également qu'il est standard pour les classes Python à écrire en utilisant CamelCase, pas en minuscules.

7
Seth Johnson

Je ne sais pas mieux, mais comme alternative à un ensemble explicite de boucles, vous pouvez écrire

lattice = np.empty( (3,3), dtype=object)
lattice.flat = [site(3) for _ in lattice.flat]

qui devrait fonctionner quelle que soit la forme du réseau.

6
DSM