Helpex - Trao đổi & giúp đỡ Đăng nhập
83

Tôi đang viết một lớp bằng python và tôi có một thuộc tính sẽ mất một thời gian tương đối lâu để tính toán, vì vậy tôi chỉ muốn thực hiện điều đó một lần . Ngoài ra, nó sẽ không cần thiết cho mọi phiên bản của lớp, vì vậy tôi không muốn làm điều đó theo mặc định trong __init__.

Tôi mới làm quen với Python, nhưng không phải lập trình. Tôi có thể nghĩ ra một cách để thực hiện điều này khá dễ dàng, nhưng tôi đã lặp đi lặp lại rằng cách làm của 'Pythonic' thường đơn giản hơn nhiều so với những gì tôi nghĩ ra khi sử dụng kinh nghiệm của mình ở các ngôn ngữ khác.

Có cách nào 'đúng' để làm điều này bằng Python không?

83 hữu ích 1 bình luận 39k xem chia sẻ
10 trả lời 10
106

Python ≥ 3,8 @property@functools.lru_cacheđã được kết hợp thành @cached_property.

import functools
class MyClass:
    @functools.cached_property
    def foo(self):
        print("long calculation here")
        return 21 * 2

Python ≥ 3.2 <3.8

Bạn nên sử dụng cả hai @property@functools.lru_cachedecorator:

import functools
class MyClass:
    @property
    @functools.lru_cache()
    def foo(self):
        print("long calculation here")
        return 21 * 2

Câu trả lời này có nhiều ví dụ chi tiết hơn và cũng đề cập đến một backport cho các phiên bản Python trước.

Python <3.2

Python wiki có trình trang trí thuộc tính được lưu trong bộ nhớ cache (được cấp phép MIT) có thể được sử dụng như sau:

import random
# the class containing the property must be a new-style class
class MyClass(object):
   # create property whose value is cached for ten minutes
   @cached_property(ttl=600)
   def randint(self):
       # will only be evaluated every 10 min. at maximum.
       return random.randint(0, 100)

Hoặc bất kỳ triển khai nào được đề cập trong các câu trả lời khác phù hợp với nhu cầu của bạn.
Hoặc backport đã đề cập ở trên.

106 hữu ích 5 bình luận chia sẻ
51

Tôi đã từng làm điều này theo cách mà gnibbler đề nghị, nhưng cuối cùng tôi cảm thấy mệt mỏi với các bước dọn dẹp nhà cửa nhỏ.

Vì vậy, tôi đã xây dựng bộ mô tả của riêng mình:

class cached_property(object):
    """
    Descriptor (non-data) for building an attribute on-demand on first use.
    """
    def __init__(self, factory):
        """
        <factory> is called such: factory(instance) to build the attribute.
        """
        self._attr_name = factory.__name__
        self._factory = factory

    def __get__(self, instance, owner):
        # Build the attribute.
        attr = self._factory(instance)

        # Cache the value; hide ourselves.
        setattr(instance, self._attr_name, attr)

        return attr

Đây là cách bạn sử dụng nó:

class Spam(object):

    @cached_property
    def eggs(self):
        print 'long calculation here'
        return 6*2

s = Spam()
s.eggs      # Calculates the value.
s.eggs      # Uses cached value.
51 hữu ích 3 bình luận chia sẻ
41

Cách thông thường sẽ làm cho các thuộc tính một tài sản và lưu trữ các giá trị lần đầu tiên nó được tính

import time

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

    @property
    def bar(self):
        if self._bar is None:
            print "starting long calculation"
            time.sleep(5)
            self._bar = 2*2
            print "finished long caclulation"
        return self._bar

foo=Foo()
print "Accessing foo.bar"
print foo.bar
print "Accessing foo.bar"
print foo.bar
41 hữu ích 3 bình luận chia sẻ
19

Python 3.8 bao gồm trình functools.cached_propertytrang trí.

Chuyển đổi một phương thức của một lớp thành một thuộc tính có giá trị được tính một lần và sau đó được lưu vào bộ nhớ đệm như một thuộc tính bình thường cho vòng đời của cá thể. Tương tự như property(), với việc bổ sung bộ nhớ đệm. Hữu ích cho các thuộc tính được tính toán đắt tiền của các cá thể mà về mặt hiệu quả là bất biến.

Ví dụ này là trực tiếp từ tài liệu:

from functools import cached_property

class DataSet:
    def __init__(self, sequence_of_numbers):
        self._data = sequence_of_numbers

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

    @cached_property
    def variance(self):
        return statistics.variance(self._data)

Hạn chế là đối tượng có thuộc tính được lưu vào bộ nhớ đệm phải có __dict__thuộc tính là ánh xạ có thể thay đổi, loại trừ các lớp có __slots__trừ khi __dict__được định nghĩa trong __slots__.

19 hữu ích 0 bình luận chia sẻ
2
class MemoizeTest:

      _cache = {}
      def __init__(self, a):
          if a in MemoizeTest._cache:
              self.a = MemoizeTest._cache[a]
          else:
              self.a = a**5000
              MemoizeTest._cache.update({a:self.a})
2 hữu ích 0 bình luận chia sẻ
2

Các dickensgói (không phải của tôi) Mời cachedproperty, classpropertycachedclasspropertytrang trí.

Để lưu vào bộ nhớ cache một thuộc tính lớp :

from descriptors import cachedclassproperty

class MyClass:
    @cachedclassproperty
    def approx_pi(cls):
        return 22 / 7
2 hữu ích 0 bình luận chia sẻ
1

Bạn có thể thử xem xét ghi nhớ. Cách hoạt động của nó là nếu bạn truyền vào một hàm các đối số giống nhau, nó sẽ trả về kết quả được lưu trong bộ nhớ cache. Bạn có thể tìm thêm thông tin về cách triển khai nó trong python tại đây .

Ngoài ra, tùy thuộc vào cách mã của bạn được thiết lập (bạn nói rằng nó không cần thiết cho tất cả các trường hợp), bạn có thể thử sử dụng một số loại mô hình flyweight hoặc tải chậm.

1 hữu ích 0 bình luận chia sẻ
0

Hầu hết nếu không phải tất cả các câu trả lời hiện tại là về thuộc tính phiên bản bộ nhớ đệm . Để lưu vào bộ nhớ cache các thuộc tính của lớp , bạn có thể chỉ cần sử dụng một từ điển. Điều này đảm bảo các thuộc tính được tính một lần cho mỗi lớp, thay vì một lần cho mỗi trường hợp.

mapping = {}

class A:
    def __init__(self):
        if self.__class__.__name__ not in mapping:
            print('Expansive calculation')
            mapping[self.__class__.__name__] = self.__class__.__name__
        self.cached = mapping[self.__class__.__name__]

Để minh họa,

foo = A()
bar = A()
print(foo.cached, bar.cached)

cho

Expansive calculation
A A
0 hữu ích 0 bình luận chia sẻ
-3

Cách đơn giản nhất để làm điều này có lẽ là chỉ viết một phương thức (thay vì sử dụng một thuộc tính) bao bọc xung quanh thuộc tính (phương thức getter). Trong lần gọi đầu tiên, các phương thức này sẽ tính toán, lưu và trả về giá trị; sau đó nó chỉ trả về giá trị đã lưu.

-3 hữu ích 0 bình luận chia sẻ
-4

Với Python 2, nhưng không phải Python 3, đây là những gì tôi làm. Điều này là hiệu quả nhất mà bạn có thể nhận được:

class X:
    @property
    def foo(self):
        r = 33
        self.foo = r
        return r

Giải thích: Về cơ bản, tôi chỉ đang nạp chồng một phương thức thuộc tính với giá trị được tính toán. Vì vậy, sau lần đầu tiên bạn truy cập thuộc tính (đối với trường hợp đó), foosẽ không còn là thuộc tính và trở thành thuộc tính cá thể. Ưu điểm của cách tiếp cận này là một lần truy cập bộ nhớ cache càng rẻ càng tốt vì self.__dict__đang được sử dụng làm bộ nhớ cache và không có chi phí cá thể nếu thuộc tính không được sử dụng.

Cách tiếp cận này không hoạt động với Python 3.

-4 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ python memoization , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading