2009-09-22 11 views
27

Tôi đang cố gắng tìm cách để tải một biến cấp mô-đun.Biến mô-đun lười biếng - có thể thực hiện được không?

Cụ thể, tôi đã viết một thư viện Python nhỏ để nói chuyện với iTunes và tôi muốn có biến mô-đun DOWNLOAD_FOLDER_PATH. Thật không may, iTunes sẽ không cho bạn biết thư mục tải xuống của nó ở đâu, vì vậy tôi đã viết một hàm lấy tập tin của một vài bài hát podcast và trèo lên cây thư mục cho đến khi tìm thấy thư mục "Tải xuống".

Quá trình này mất một hoặc hai giây, vì vậy tôi muốn đánh giá nó một cách lười biếng, thay vì ở thời gian nhập mô-đun.

Có cách nào để bỏ qua một biến mô-đun khi lần đầu tiên được truy cập hoặc tôi sẽ phải dựa vào một hàm không?

Trả lời

52

Bạn không thể làm điều đó với mô-đun, nhưng bạn có thể che giấu một lớp "như thể" đó là một mô-đun, ví dụ, trong itun.py, mã ...:

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

Bây giờ ai có thể import itun. .. và thực tế là ví dụ itun._Sneaky() của bạn. Các __getattr__ là có để cho phép bạn truy cập vào bất cứ điều gì khác trong itun.py mà bạn có thể thuận tiện hơn để bạn có thể mã hóa như một đối tượng mô-đun cấp cao nhất, so với bên _Sneaky! _)

+7

Điều đó hoàn toàn rực rỡ. Một bậc thầy tại nơi làm việc. – wbg

0

Nếu biến đó sống trong một lớp thay vì một mô-đun, thì bạn có thể quá tải getattr, hoặc tốt hơn, hãy điền nó vào init.

+0

Vâng, tôi biết. Tôi muốn nó trong một mô-đun vì lợi ích của API. Trong một lớp, tôi sẽ sử dụng thuộc tính '' để làm điều đó. Tuyệt vời cho tải chậm. – wbg

3

Có cách nào để chỉ định một biến mô-đun khi truy cập lần đầu tiên hoặc tôi sẽ phải dựa vào một hàm không?

Tôi nghĩ bạn đúng khi nói rằng một hàm là giải pháp tốt nhất cho vấn đề của bạn ở đây. Tôi sẽ cung cấp cho bạn một ví dụ ngắn để minh họa.

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

import os 
# expensive operation to crawl up in directory structure 

Thao tác tốn kém sẽ được thực hiện khi nhập nếu ở cấp mô-đun. Không có cách nào để ngăn chặn điều này, thiếu lười biếng nhập khẩu toàn bộ mô-đun !!

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

Bạn sẽ làm theo phương pháp tốt nhất bằng cách sử dụng phương pháp này.

+0

Hmmmm. Đó là cách rõ ràng và đơn giản nhất để làm điều đó, do đó, phù hợp với Zen của Python, nhưng tôi không thích nó một cách khôn ngoan. – wbg

11

Tôi đã từng thực hiện Alex' trên Python 3.3, nhưng điều này treo thảm hại: mã

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

là không đúng vì một AttributeError nên được nâng lên, không phải là một KeyError. này bị rơi ngay dưới Python 3.3, vì có rất nhiều mẫn được thực hiện trong việc nhập khẩu, tìm kiếm các thuộc tính như __path__, __loader__, vv

Dưới đây là phiên bản mà chúng ta sử dụng hiện nay trong dự án của chúng tôi để cho phép nhập khẩu lười biếng trong một mô-đun.Các __init__ của các mô-đun bị trì hoãn cho đến khi truy cập thuộc tính đầu tiên rằng không có một cái tên đặc biệt:

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

Module bây giờ cần xác định một init chức năng. Chức năng này có thể được sử dụng để nhập khẩu module có thể nhập mình:

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

Mã này có thể được đưa vào mô-đun khác, và nó có thể được mở rộng với tính như trong ví dụ trên.

Có thể điều đó rất hữu ích cho ai đó.

2

Gần đây, tôi đã gặp phải vấn đề tương tự và đã tìm ra cách để thực hiện.

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) 

Với LazyObject này, bạn có thể định nghĩa một phương pháp init cho đối tượng, và đối tượng sẽ được khởi tạo một cách lười biếng, ví dụ mã trông giống như:

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

đối tượng o trên sẽ có giống hệt nhau các phương thức bất kỳ đối tượng 'done' có, ví dụ: bạn có thể làm:

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

hoàn thành mã có thử nghiệm (hoạt động ở 2.7) có thể được tìm thấy tại đây:

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