2013-07-05 33 views
11

Lớp foo có một thanh. Thanh không được tải cho đến khi nó được truy cập. Tiếp tục truy cập vào thanh nên không phải trả phí.Python - Tải các thuộc tính lớp học một cách lười biếng

class Foo(object): 

    def get_bar(self): 
     print "initializing" 
     self.bar = "12345" 
     self.get_bar = self._get_bar 
     return self.bar 

    def _get_bar(self): 
     print "accessing" 
     return self.bar 

Có thể làm điều gì đó như thế này bằng cách sử dụng thuộc tính hoặc, tốt hơn, thuộc tính, thay vì sử dụng phương pháp getter?

Mục đích là để tải lười biếng mà không cần chi phí trên tất cả các truy cập tiếp theo ...

+0

Bạn có thể làm điều đó tự động với mô tả: http://jeetworks.org/node/62 – schlamar

+1

Werkzeug có thực hiện tốt hơn với mở rộng nhận xét: https://github.com/mitsuhiko/werkzeug/blob/10b4b8b6918a83712170fdaabd3ec61cf07f23ff/werkzeug/utils.py#L35 – schlamar

+0

Xem thêm: [Python lazy property decorator] (http://stackoverflow.com/questions/3012421/python- lazy-property-decorator). – detly

Trả lời

11

Có một số vấn đề với câu trả lời hiện tại. Giải pháp với thuộc tính yêu cầu bạn chỉ định một thuộc tính lớp bổ sung và có chi phí kiểm tra thuộc tính này trên mỗi lần tra cứu. Giải pháp với __getattr__ có vấn đề là nó ẩn thuộc tính này cho đến lần truy cập đầu tiên. Điều này là không tốt cho sự quan tâm và cách giải quyết với __dir__ là bất tiện.

Giải pháp tốt hơn so với hai đề xuất đang sử dụng trực tiếp mô tả. Thư viện werkzeug đã có giải pháp là werkzeug.utils.cached_property. Nó có một thực hiện đơn giản, do đó bạn có thể trực tiếp sử dụng nó mà không cần phải Werkzeug như phụ thuộc:

_missing = object() 

class cached_property(object): 
    """A decorator that converts a function into a lazy property. The 
    function wrapped is called the first time to retrieve the result 
    and then that calculated result is used the next time you access 
    the value:: 

     class Foo(object): 

      @cached_property 
      def foo(self): 
       # calculate something important here 
       return 42 

    The class has to have a `__dict__` in order for this property to 
    work. 
    """ 

    # implementation detail: this property is implemented as non-data 
    # descriptor. non-data descriptors are only invoked if there is 
    # no entry with the same name in the instance's __dict__. 
    # this allows us to completely get rid of the access function call 
    # overhead. If one choses to invoke __get__ by hand the property 
    # will still work as expected because the lookup logic is replicated 
    # in __get__ for manual invocation. 

    def __init__(self, func, name=None, doc=None): 
     self.__name__ = name or func.__name__ 
     self.__module__ = func.__module__ 
     self.__doc__ = doc or func.__doc__ 
     self.func = func 

    def __get__(self, obj, type=None): 
     if obj is None: 
      return self 
     value = obj.__dict__.get(self.__name__, _missing) 
     if value is _missing: 
      value = self.func(obj) 
      obj.__dict__[self.__name__] = value 
     return value 
+4

Vấn đề này nằm ngoài phạm vi của một khuôn khổ web (Werkzueg, Django, Chai, Pyramid, et al), điều này không làm việc tốt với chủ đề. Xem https://github.com/pydanny/cached-property/issues/6 (chúng tôi đã đóng) – pydanny

8

Chắc chắn, chỉ có tài sản của bạn thiết lập thuộc tính một trường hợp mà được trả lại về tiếp cận sau:

class Foo(object): 
    _cached_bar = None 

    @property 
    def bar(self): 
     if not self._cached_bar: 
      self._cached_bar = self._get_expensive_bar_expression() 
     return self._cached_bar 

Bộ mô tả property là một bộ mô tả dữ liệu (nó thực hiện __get__, __set____delete__ móc mô tả), vì vậy nó sẽ được gọi ngay cả khi thuộc tính bar tồn tại trên cá thể, với kết quả cuối cùng là Python bỏ qua thuộc tính đó, do đó cần phải kiểm tra riêng biệt người gác cửa ute trên mỗi truy cập.

Bạn có thể viết mô tả của riêng bạn mà chỉ thực hiện __get__, tại thời điểm đó Python sử dụng một thuộc tính trên dụ trên mô tả nếu nó tồn tại:

class CachedProperty(object): 
    def __init__(self, func, name=None): 
     self.func = func 
     self.name = name if name is not None else func.__name__ 
     self.__doc__ = func.__doc__ 

    def __get__(self, instance, class_): 
     if instance is None: 
      return self 
     res = self.func(instance) 
     setattr(instance, self.name, res) 
     return res 

class Foo(object): 
    @CachedProperty 
    def bar(self): 
     return self._get_expensive_bar_expression() 

Nếu bạn thích một cách tiếp cận __getattr__ (trong đó có cái gì đó để nói cho nó), mà muốn được:

class Foo(object): 
    def __getattr__(self, name): 
     if name == 'bar': 
      bar = self.bar = self._get_expensive_bar_expression() 
      return bar 
     return super(Foo, self).__getattr__(name) 

truy cập tiếp theo sẽ tìm thấy bar thuộc tính trên các ví dụ và __getattr__ sẽ không được tham khảo ý kiến.

Demo:

>>> class FooExpensive(object): 
...  def _get_expensive_bar_expression(self): 
...   print 'Doing something expensive' 
...   return 'Spam ham & eggs' 
... 
>>> class FooProperty(FooExpensive): 
...  _cached_bar = None 
...  @property 
...  def bar(self): 
...   if not self._cached_bar: 
...    self._cached_bar = self._get_expensive_bar_expression() 
...   return self._cached_bar 
... 
>>> f = FooProperty() 
>>> f.bar 
Doing something expensive 
'Spam ham & eggs' 
>>> f.bar 
'Spam ham & eggs' 
>>> vars(f) 
{'_cached_bar': 'Spam ham & eggs'} 
>>> class FooDescriptor(FooExpensive): 
...  bar = CachedProperty(FooExpensive._get_expensive_bar_expression, 'bar') 
... 
>>> f = FooDescriptor() 
>>> f.bar 
Doing something expensive 
'Spam ham & eggs' 
>>> f.bar 
'Spam ham & eggs' 
>>> vars(f) 
{'bar': 'Spam ham & eggs'} 

>>> class FooGetAttr(FooExpensive): 
...  def __getattr__(self, name): 
...   if name == 'bar': 
...    bar = self.bar = self._get_expensive_bar_expression() 
...    return bar 
...   return super(Foo, self).__getatt__(name) 
... 
>>> f = FooGetAttr() 
>>> f.bar 
Doing something expensive 
'Spam ham & eggs' 
>>> f.bar 
'Spam ham & eggs' 
>>> vars(f) 
{'bar': 'Spam ham & eggs'} 
+0

Điều này cho biết thêm chi phí của một "nếu" bổ sung trên mọi truy cập. Có thể xác định lại thuộc tính lần đầu tiên được gọi không? –

+0

Bạn sẽ cần một lá cờ ở đâu đó, một cái gì đó cho bạn biết liệu bạn đã khởi tạo thuộc tính hay chưa. –

+1

@whatscanasta: Không phải với thuộc tính ', bởi vì Python cung cấp ưu tiên mô tả dữ liệu trên các thuộc tính ví dụ. Nhưng với '__getattr__' bạn * có thể * (xem cập nhật). –

1

Chắc chắn đó là, hãy thử:

class Foo(object): 
    def __init__(self): 
     self._bar = None # Initial value 

    @property 
    def bar(self): 
     if self._bar is None: 
      self._bar = HeavyObject() 
     return self._bar 

Lưu ý rằng đây không phải là thread-safe. cPython có GIL, do đó, nó là một vấn đề tương đối, nhưng nếu bạn có kế hoạch sử dụng điều này trong một ngăn xếp Python đa luồng thực sự (nói, Jython), bạn có thể muốn thực hiện một số dạng khóa an toàn.

Các vấn đề liên quan