2009-09-22 9 views
27

Estoy tratando de encontrar una forma de cargar de forma perezosa una variable de nivel de módulo.Variables de módulos diferidos: ¿se puede hacer?

Específicamente, he escrito una pequeña biblioteca de Python para hablar con iTunes, y quiero tener una variable de módulo DOWNLOAD_FOLDER_PATH. Desafortunadamente, iTunes no te dirá dónde está su carpeta de descargas, así que he escrito una función que toma el camino de archivo de algunas pistas de podcasts y sube una copia de seguridad del árbol de directorios hasta que encuentra el directorio "Descargas".

Esto lleva uno o dos segundos, por lo que me gustaría que se evalúe de forma diferida, en lugar de en el momento de la importación del módulo.

¿Hay alguna forma de asignar perezosamente una variable de módulo cuando se accede por primera vez o tendré que depender de una función?

Respuesta

52

Usted no puede hacerlo con los módulos, pero se puede ocultar una clase "como si" se trataba de un módulo, por ejemplo, en itun.py, código ...:

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() 

Ahora cualquiera puede import itun. .. y obtener de hecho su instancia itun._Sneaky(). El __getattr__ está ahí para que pueda acceder cualquier otra cosa en itun.py que puede ser más conveniente para su código como un objeto de módulo de nivel superior, que en el interior _Sneaky! _)

+7

Eso es absolutamente brillante. Un maestro en el trabajo. – wbg

0

Si esa variable viviera en una clase en lugar de en un módulo, entonces podría sobrecargar getattr, o mejor aún, poblarlo en init.

+0

Sí, lo sé. Lo quería en un módulo por el bien de la API. En una clase, usaría 'property' para hacerlo. Ideal para cargas perezosas. – wbg

3

¿Hay alguna forma de asignar perezosamente una variable de módulo cuando se accede por primera vez o tendré que depender de una función?

Creo que tiene razón al decir que una función es la mejor solución a su problema aquí. Le daré un breve ejemplo para ilustrar.

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

import os 
# expensive operation to crawl up in directory structure 

La costosa operación se ejecutará en la importación si está a nivel de módulo. ¡No hay forma de detener esto, salvo importar perezosamente todo el módulo!

#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. 

Seguirá las mejores prácticas al usar este método.

+0

Hmmmm. Es la forma más simple y obvia de hacerlo, por lo que está en línea con el Zen de Python, pero simplemente no me gusta API-wise. – wbg

11

que utiliza la aplicación de Alex en Python 3.3, pero esto accidentes miserablemente: El código

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

no es correcto, ya que un AttributeError debería elevarse, no un KeyError. Este estrelló inmediatamente debajo de Python 3.3, debido a que una gran cantidad de introspección se hace durante la importación, en busca de atributos como __path__, etc. __loader__

Aquí es la versión que se utiliza ahora en nuestro proyecto para permitir la importación perezosos en un móduloEl __init__ del módulo se retrasa hasta el primer acceso atributo que no tiene un nombre especial:

""" 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__) 

El módulo ahora tiene que definir una función init . Esta función se puede utilizar para importar módulos que podrían importar a nosotros mismos:

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

El código se puede poner en otro módulo, y se puede extender con propiedades como en los ejemplos anteriores.

Quizás sea útil para alguien.

2

Recientemente me encontré con el mismo problema y encontré la manera de hacerlo.

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) 

Con esta LazyObject, se puede definir un método init para el objeto, y el objeto se inicializará con pereza, código de ejemplo que parece:

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

el objeto o anterior tendrá exactamente el mismo métodos 'done' cualquier objeto tiene, por ejemplo, se pueden hacer:

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

completa código con pruebas (que funciona en 2.7) se puede encontrar aquí:

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

Cuestiones relacionadas