2009-09-22 4 views
27

J'essaie de trouver un moyen de charger paresseusement une variable au niveau du module. En particulier, j'ai écrit une petite bibliothèque Python pour parler à iTunes, et je veux avoir une variable de module DOWNLOAD_FOLDER_PATH. Malheureusement, iTunes ne vous dira pas où se trouve son dossier de téléchargement, j'ai donc écrit une fonction qui saisit le chemin de fichier de quelques pistes de podcast et remonte l'arborescence de répertoires jusqu'à ce qu'il trouve le répertoire "Téléchargements". Cela prend une seconde ou deux, donc je voudrais l'évaluer paresseusement, plutôt que lors de l'importation du module.Variables de module paresseux - peut-il être fait?

Est-il possible d'attribuer paresseusement une variable de module lors de son premier accès ou devrais-je me fier à une fonction?

Répondre

52

Vous ne pouvez pas le faire avec des modules, mais vous pouvez masquer une classe "comme si" il était un module, par exemple, dans itun.py, code ...:

import sys 

class _Sneaky(object): 
    def __init__(self): 
    self.download = None 

    @property 
    def DOWNLOAD_PATH(self): 
    if not self.download: 
     self.download = heavyComputations() 
    return self.download 

    def __getattr__(self, name): 
    return globals()[name] 

# other parts of itun that you WANT to code in 
# module-ish ways 

sys.modules[__name__] = _Sneaky() 

Maintenant tout le monde peut import itun. .. et obtenez en fait votre instance itun._Sneaky(). Le __getattr__ est là pour vous accéder quoi que ce soit d'autre dans itun.py qui peut être plus pratique pour vous permettre de coder comme un objet de module haut niveau, qu'à l'intérieur _Sneaky! _)

+7

C'est absolument génial. Un maître au travail. – wbg

0

Si cette variable vivait dans une classe plutôt que dans un module, alors vous pourriez surcharger getattr, ou mieux encore, le peupler dans init.

+0

Ouais, je sais. Je le voulais dans un module pour le bien de l'API. Dans une classe, j'utiliserais 'property' pour le faire, cependant. Idéal pour le chargement paresseux. – wbg

3

Est-il possible d'attribuer paresseusement une variable de module lors de son premier accès ou devrais-je me fier à une fonction?

Je pense que vous avez raison de dire qu'une fonction est la meilleure solution à votre problème ici. Je vais vous donner un bref exemple à illustrer.

#myfile.py - an example module with some expensive module level code. 

import os 
# expensive operation to crawl up in directory structure 

L'opération coûteuse sera exécutée à l'importation si elle est au niveau du module. Il n'y a pas moyen d'arrêter cela, à moins d'importer paresseusement tout le module !!

#myfile2.py - a module with expensive code placed inside a function. 

import os 

def getdownloadsfolder(curdir=None): 
    """a function that will search upward from the user's current directory 
     to find the 'Downloads' folder.""" 
    # expensive operation now here. 

Vous suivrez les meilleures pratiques en utilisant cette méthode.

+0

Hmmmm. C'est la façon la plus évidente et la plus simple de le faire, donc en ligne avec le Zen de Python, mais je n'aime pas ça API-sage. – wbg

11

J'utilisé la mise en œuvre d'Alex sur Python 3.3, mais crashes lamentablement: Le code

def __getattr__(self, name): 
    return globals()[name] 

n'est pas correct parce qu'un AttributeError devrait être soulevée, pas KeyError. Ce est écrasé immédiatement sous Python 3.3, parce que beaucoup d'introspection est fait lors de l'importation, la recherche d'attributs comme __path__, __loader__ etc.

Voici la version que nous utilisons maintenant dans notre projet pour permettre les importations paresseux dans un module.Le __init__ du module est retardé jusqu'à ce que le premier accès d'attribut qui n'a pas un nom spécial:

""" config.py """ 
# lazy initialization of this module to avoid circular import. 
# the trick is to replace this module by an instance! 
# modelled after a post from Alex Martelli :-) 

Lazy module variables--can it be done?

class _Sneaky(object): 
    def __init__(self, name): 
     self.module = sys.modules[name] 
     sys.modules[name] = self 
     self.initializing = True 

    def __getattr__(self, name): 
     # call module.__init__ after import introspection is done 
     if self.initializing and not name[:2] == '__' == name[-2:]: 
      self.initializing = False 
      __init__(self.module) 
     return getattr(self.module, name) 

_Sneaky(__name__) 

Le module doit maintenant définir une fonction init. Cette fonction peut être utilisée pour importer des modules qui pourraient nous importer:

def __init__(module): 
    ... 
    # do something that imports config.py again 
    ... 

Le code peut être mis dans un autre module, et il peut être étendu avec des propriétés comme dans les exemples ci-dessus.

Peut-être que c'est utile pour quelqu'un.

2

Récemment, j'ai rencontré le même problème et j'ai trouvé un moyen de le faire.

class LazyObject(object): 
    def __init__(self): 
     self.initialized = False 
     setattr(self, 'data', None) 

    def init(self, *args): 
     #print 'initializing' 
     pass 

    def __len__(self): return len(self.data) 
    def __repr__(self): return repr(self.data) 

    def __getattribute__(self, key): 
     if object.__getattribute__(self, 'initialized') == False: 
      object.__getattribute__(self, 'init')(self) 
      setattr(self, 'initialized', True) 

     if key == 'data': 
      return object.__getattribute__(self, 'data') 
     else: 
      try: 
       return object.__getattribute__(self, 'data').__getattribute__(key) 
      except AttributeError: 
       return super(LazyObject, self).__getattribute__(key) 

Avec cette LazyObject, vous pouvez définir une méthode init pour l'objet, et l'objet sera initialisé paresseusement, exemple de code ressemble à:

o = LazyObject() 
def slow_init(self): 
    time.sleep(1) # simulate slow initialization 
    self.data = 'done' 
o.init = slow_init 

l'objet o ci-dessus auront exactement le même méthodes tout objet 'done' ont, par exemple, vous pouvez faire:

# o will be initialized, then apply the `len` method 
assert len(o) == 4 

complète code avec des tests (fonctionne en 2.7) se trouve ici:

https://gist.github.com/observerss/007fedc5b74c74f3ea08

Questions connexes