|
11 | 11 |
|
12 | 12 | __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
|
13 | 13 | 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial',
|
14 |
| - 'partialmethod', 'singledispatch'] |
| 14 | + 'partialmethod', 'singledispatch', 'cached_property'] |
15 | 15 |
|
16 | 16 | try:
|
17 | 17 | from _functools import reduce
|
@@ -814,3 +814,77 @@ def wrapper(*args, **kw):
|
814 | 814 | wrapper._clear_cache = dispatch_cache.clear
|
815 | 815 | update_wrapper(wrapper, func)
|
816 | 816 | return wrapper
|
| 817 | + |
| 818 | +################################################################################ |
| 819 | +### for ipaddress |
| 820 | +### copied from https://github.com/penguinolog/backports.cached_property |
| 821 | +################################################################################ |
| 822 | +"""Backport of python 3.8 functools.cached_property. |
| 823 | +
|
| 824 | +cached_property() - computed once per instance, cached as attribute |
| 825 | +""" |
| 826 | +# noinspection PyPep8Naming |
| 827 | +_NOT_FOUND = object() |
| 828 | + |
| 829 | +class cached_property: # NOSONAR # pylint: disable=invalid-name # noqa: N801 |
| 830 | + """Cached property implementation. |
| 831 | +
|
| 832 | + Transform a method of a class into a property whose value is computed once |
| 833 | + and then cached as a normal attribute for the life of the instance. |
| 834 | + Similar to property(), with the addition of caching. |
| 835 | + Useful for expensive computed properties of instances |
| 836 | + that are otherwise effectively immutable. |
| 837 | + """ |
| 838 | + |
| 839 | + def __init__(self, func) -> None: |
| 840 | + """Cached property implementation.""" |
| 841 | + self.func = func |
| 842 | + self.attrname = None |
| 843 | + self.__doc__ = func.__doc__ |
| 844 | + self.lock = RLock() |
| 845 | + |
| 846 | + def __set_name__(self, owner, name): |
| 847 | + """Assign attribute name and owner.""" |
| 848 | + if self.attrname is None: |
| 849 | + self.attrname = name |
| 850 | + elif name != self.attrname: |
| 851 | + raise TypeError( |
| 852 | + "Cannot assign the same cached_property to two different names " |
| 853 | + f"({self.attrname!r} and {name!r})." |
| 854 | + ) |
| 855 | + |
| 856 | + def __get__(self, instance, owner = None): |
| 857 | + """Property-like getter implementation. |
| 858 | +
|
| 859 | + :return: property instance if requested on class or value/cached value if requested on instance. |
| 860 | + :rtype: Union[cached_property[self._T], self._T] |
| 861 | + :raises TypeError: call without calling __set_name__ or no '__dict__' attribute |
| 862 | + """ |
| 863 | + if instance is None: |
| 864 | + return self |
| 865 | + if self.attrname is None: |
| 866 | + raise TypeError("Cannot use cached_property instance without calling __set_name__ on it.") |
| 867 | + try: |
| 868 | + cache = instance.__dict__ |
| 869 | + except AttributeError: # not all objects have __dict__ (e.g. class defines slots) |
| 870 | + msg = ( |
| 871 | + f"No '__dict__' attribute on {type(instance).__name__!r} " |
| 872 | + f"instance to cache {self.attrname!r} property." |
| 873 | + ) |
| 874 | + raise TypeError(msg) from None |
| 875 | + val = cache.get(self.attrname, _NOT_FOUND) |
| 876 | + if val is _NOT_FOUND: |
| 877 | + with self.lock: |
| 878 | + # check if another thread filled cache while we awaited lock |
| 879 | + val = cache.get(self.attrname, _NOT_FOUND) |
| 880 | + if val is _NOT_FOUND: |
| 881 | + val = self.func(instance) |
| 882 | + try: |
| 883 | + cache[self.attrname] = val |
| 884 | + except TypeError: |
| 885 | + msg = ( |
| 886 | + f"The '__dict__' attribute on {type(instance).__name__!r} instance " |
| 887 | + f"does not support item assignment for caching {self.attrname!r} property." |
| 888 | + ) |
| 889 | + raise TypeError(msg) from None |
| 890 | + return val |
0 commit comments