Python et modules — Les-mathematiques.net The most powerful custom community solution in the world

Python et modules

Bonjour,

Dans l'exemple ci-dessous (sur la base de doc_modules), je me suis créé un petit exemple pour appeler mon propre module. Jusqu'à présent j'ai fait l'impasse sur ce problème, mais je suis obligé d'importer dans chaque fichier les modules Python (numpy et math ici - peut-être une histoire de tables de symboles locaux, je n'ai pas encore bien compris), alors que j'aimerais le faire une bonne fois pour toute dans le fichier principal: où puis-je trouver comment procéder?

Merci

Paul
Fichier principal:
import module_Fibo
import numpy as np
from math import sqrt

n = 10
A = np.zeros(n, dtype=np.int32)
i = np.arange(n)
A = module_Fibo.Fibo(i)

Fichier "module_Fibo.py"
from math import sqrt
import numpy as np

def Fibo(n):  
    phi = (0.5*(1 + sqrt(5)))**n
    phi_p = (0.5*(1 - sqrt(5)))**n
    return np.int32((phi - phi_p)/sqrt(5))  

Réponses

  • À mon avis, ce que tu souhaites faire n'est pas une très bonne idée. Quand on ouvre un fichier .py et que l'on tombe sur un identifiant (une variable, une fonction, une classe...) en se demandant « mais d'où vient ce machin et que fait-il ? », il est extrêmement pratique de pouvoir se dire : je regarde les imports en haut du fichier et j'y trouverai la réponse. C'est d'ailleurs une des raisons pour lesquelles, sauf cas particuliers, ce genre de syntaxe :
    from machin import *
    
    est plutôt à éviter. Un des cas particuliers notables où cette syntaxe n'est pas contre-indiquée est celui du scientifique qui travaille dans un ipython ou autre truc interactif (un REPL, quoi) et ne veut pas taper des tonnes de préfixes partout pour son travail d'exploration. Mais pour une base de code pas toute petite et qui va être maintenue dans le temps, pouvoir trouver facilement d'où provient tel ou tel objet utilisé dans un fichier .py est très utile.

    Maintenant, si tu tiens vraiment à faire cette factorisation, tu peux créer un module my_common_imports.py :
    import numpy as np
    from math import sqrt
    
    et faire :
    from my_common_imports import *
    
    dans tes différents modules.
  • En fait, ton problème n'en est pas un.

    Si tu es CERTAIN que tes fichiers "secondaires" ne seront JAMAIS utilisés seuls, tu peux effectivement te passer de faire les importations dans ces fichiers. Tu fais une importation au début de ton fichier principal, puis, dans ce fichier principal, tu importes les autres fichiers.

    Dans tous les autres cas, ça ne coûte absolument rien de mettre les importations dans tes fichiers secondaires. Cela prend certes quelques lignes et quelques octets de données... mais ces importations seront simplement ignorées si les modules en question sont déjà importés, donc ne feront perdre aucun temps à l'exécution.

    Il faut cependant faire attention à ne pas mélanger les noms d'un fichier à l'autre...
  • Dans mon fichier "module_Fibo.py" (secondaire), si je ne fais pas l'import pour Numpy, il ne reconnait pas "np" (peut-être une question de table de symboles qui est propre à chaque fichier).
  • Non, c'est parce que tu as importé ton fichier AVANT d'importer numpy.
    Si tu le fais dans l'autre sens, ça devrait marcher.
  • Non ça ne change rien chez moi ; est-ce que ça peut venir du fait que je suis sous Anaconda et Spyder ?
  • Il me semble que c'est tout à fait normal : un module implémente un namespace (espace de nommage, ce que vous voulez). S'il récupérait automatiquement tout ce qui a été importé ou défini dans les modules qui l'ont lui-même importé, ce serait un gros foutoir...
  • Si je comprends bien, il faut importer dans chaque fichier les modules nécessaires $\rightarrow$ tout devient "local" et il y a autant de copies qu'il y a d'appels pour éviter les écrasements.
  • Bon, dans ce cas, on oublie totalement la première façon... et on lit le document doc_modules cité plus haut jusqu'à arriver à la partie 6.4 : Les paquets.
    Cela semble correspondre un peu plus à ce que tu voudrais faire.
  • @paul18

    « Tout devient local » : oui au niveau espace de nommage, non au niveau occupation mémoire.

    Au sein d'un interpréteur Python donné, un module donné est chargé au plus une fois. Les imports suivants d'un module déjà chargé faits au cours de la même exécution du programme sont immédiats et n'accroissent pas l'occupation mémoire car ils ne font rien du tout, sinon ajouter un « pointeur » dans le module courant vers le module déjà chargé.

    Ceci est implémenté à l'aide d'un dictionnaire, sys.modules :
    >>> import sys
    >>> sys.modules["base64"]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: 'base64'
    >>> import base64
    >>> sys.modules["base64"]
    <module 'base64' from '/usr/lib/python3.8/base64.py'>
    >>> base64
    <module 'base64' from '/usr/lib/python3.8/base64.py'>
    >>> base64 is sys.modules["base64"]
    True
    >>>
    
    Le 'base64' tout seul ci-dessus « pointe » vers l'objet <module 'base64' from '/usr/lib/python3.8/base64.py'>. Comme le montre la dernière commande, sys.modules["base64"] pointe vers exactement le même objet, et si 'base64' est importé par un autre module au cours de la même exécution, un « pointeur » top-level pointera aussi, comme le 'base64' tout seul ci-dessus, vers le même objet.

    Bon, en fait, il n'y a pas explicitement de pointeurs en Python, c'est juste que toute variable, tout attribut d'un objet est un pointeur, dans les faits. Cela économise beaucoup la mémoire mais peut jouer des tours lorsqu'on modifie un objet mutable référencé (pointé) depuis divers endroits. On a eu une discussion à ce sujet initiée par GaBuZoMeu il y a quatre mois sur ce forum.
  • @bisam
    je l'ai lu, mais je ne comprends toujours pas; ce paragraphe décrit comment déclarer tous les fichiers .py qui constituent mon module dans un fichier__init__.py; je n'imagine pas qu'il me faille déclarer un numpy.py ou math.py dans ce __init__.py ...
  • @paul18 Autre possibilité : étant donné que tu disposes déjà de numpy et de sqrt dans ton module module_Fibo.py tu n'as plus besoin de les importer à nouveau dans ton fichier principal, tu peux les appeler directement en faisant
    module_Fibo.np
    

    et
    module_Fibo.sqrt
    

    si ça te casse les pieds de préfixer ces appels par module.Fibo tu n'as qu'à déclarer au début de ton script une variable np :
    np=module_Fibo.np
    

    ton script principal deviendrait :
    import module_Fibo
    
    np=module_Fibo.np
    n = 10
    A = np.zeros(n, dtype=np.int32)
    i = np.arange(n)
    A = module_Fibo.Fibo(i)
    
  • @raoul.S

    Quel est l'intérêt de faire :
    np = module_Fibo.np
    
    au lieu de
    import numpy as np
    
    ? À part si l'on souhaite vraiment ajouter un niveau d'indirection, je ne vois pas trop. :-S
  • effectivement...
  • si une solution a été apportée (en dehors de continuer à faire les import dans chaque fichier), et bien je ne l'ai pas vue et donc compris
  • Difficile de dire si tu as lu mes messages, notamment la fin du premier. Tant pis.
  • j'avais bien vu cette solution de contournement qui consiste à créer un fichier (c'est-à-dire un script) qui comporte tous les import, et de l'appeler dans chaque fichier .py du module.

    Dans ton troisième message, tu dis bien que les modules ne sont chargés qu'une fois, pour autant il faut les déclarer dans chaque fichier: j'espérais trouver un moyen de n'appeler les modules que dans le fichier principal.

    J'entrevois la réponse finale qui va dire que c'est lié à des choix techniques (table de symboles, espace de nommage, etc) des développeurs Python, et qu'il faut vivre avec, non ?
  • J'ai corrigé la fonction Fibo, qui étrangement donnait un nombre négatif à partir de n=47 (le nombre passe pourtant en 32 bits)
    def Fibo(n):  
        phi = (0.5*(1. + sqrt(5.)))**n
        phi_p = (0.5*(1. - sqrt(5.)))**n
        return np.round((phi - phi_p)/sqrt(5.), decimals=0)
    
  • @paul18

    À mon avis, point de raisons techniques à cela, mais plutôt de propreté du langage — comme expliqué dans mon premier message.

    Voici ce que tu souhaites apparemment faire. Attention, ça pique les yeux. Je ne le conseille pas du tout.

    main.py :
    import numpy as np
    
    
    class PseudoModule:
    
        def __init__(self, d):
            self.dict = d
    
        def __getattr__(self, key):
            return self.dict[key]
    
    
    def pseudo_import_dégeu(*args):
        mes_imports_implicites = "import numpy as np; from math import sqrt"
        for fichier in args:
            d = {}
            with open(fichier + ".py", "r", encoding="utf-8") as f:
                exec(mes_imports_implicites + "\n" + f.read(), d, d)
            globals()[fichier] = PseudoModule(d)
    
    
    pseudo_import_dégeu("module_Fibo") #  plusieurs arguments permis
    
    n = 10
    A = np.zeros(n, dtype=np.int32)
    i = np.arange(n)
    A = module_Fibo.Fibo(i)
    print(A)
    
    module_Fibo.py :
    def Fibo(n):
        phi = (0.5*(1. + sqrt(5.)))**n
        phi_p = (0.5*(1. - sqrt(5.)))**n
        return np.round((phi - phi_p)/sqrt(5.), decimals=0)
    
    Test :
    $ python3 main.py
    [ 0.  1.  1.  2.  3.  5.  8. 13. 21. 34.]
    $
    
  • ouais c'est tordu :-D

    Ta première solution reste la plus simple. Je ne comprends pas que cela ne soit nativement pas possible dans Python, mais je vais arrêter de me poser la question.

    Merci pour la solution

    Paul
  • Bonjour.

    Parce que tu veux faire du ski, sans avoir à mettre des skis. 8-)

    Solution: soit tu as des skis en permanence, soit tu dévales la pente à pied. Dans les deux cas, c'est absurde. Voilà pourquoi cela n'est envisagé par personne.

    Prends tes imports comme une porte d'entrée obligatoire dans ton espace merveilleux où tout fonctionne.
Connectez-vous ou Inscrivez-vous pour répondre.
Success message!