Skip to content

Instantly share code, notes, and snippets.

@odigity
Last active October 6, 2023 13:05
Show Gist options
  • Save odigity/53151ffad127966ce3cf28b23be96f44 to your computer and use it in GitHub Desktop.
Save odigity/53151ffad127966ce3cf28b23be96f44 to your computer and use it in GitHub Desktop.
Funktools - cobbling together a superset of Python's functools
A collection of Python functools-inspired extensions from other sources.
from random import randint
from funktools import module_property, cached_module_property
@module_property
def my_module_property():
print("my_module_property()")
@cached_module_property
def my_cached_module_property():
result = randint(100,999)
print(f"my_cached_module_property() -> {result}")
return result
from random import randint
from funktools import class_property, cached_class_property, cache, cached_property
import bar
def my_function():
print("my_function()")
@cache
def my_cached_function():
result = randint(100,999)
print(f"my_cached_function() -> {result}")
return result
class MyClass:
## Static Method
@staticmethod
def my_static_method():
print("my_static_method()")
## Class Method
@classmethod
def my_class_method(cls):
print(f"my_class_method({cls})")
## Class Property
@class_property
def my_class_property(cls):
print(f"my_class_property({cls})")
## Cached Class Property
@cached_class_property
def my_cached_class_property(cls):
result = randint(100,999)
print(f"my_cached_class_property({cls}) -> {result}")
return result
## Instance Method
def my_instance_method(self):
print(f"my_instance_method({self})")
## Instance Property
@property
def my_instance_property(self):
print(f"my_instance_property({self})")
## Cached Instance Property
@cached_property
def my_cached_instance_property(self):
result = randint(100,999)
print(f"my_cached_instance_property({self}) -> {result}")
return result
my_function()
my_cached_function()
my_cached_function()
bar.my_module_property
bar.my_cached_module_property
bar.my_cached_module_property
MyClass.my_static_method()
MyClass.my_class_method()
MyClass.my_class_property
MyClass.my_cached_class_property
MyClass.my_cached_class_property
a = MyClass()
a.my_instance_method()
a.my_instance_property
a.my_cached_instance_property
a.my_cached_instance_property
import sys
import types
from functools import cache, cached_property, wraps # noqa (for re-export)
## Copied from: https://gist.github.com/pankajp/4fb4857b80c159c56f05ab3ad874b757
def _update_module_class(mod):
class CachingModule(types.ModuleType):
pass
mod.__class__ = CachingModule
def module_property(func, cached=False):
func_name = func.__name__
func_mod = sys.modules[func.__module__]
if func_mod.__class__ == types.ModuleType:
_update_module_class(func_mod)
elif func_mod.__class__.__name__ != 'CachingModule':
raise RuntimeError(f'mod_property incompatible with module type: {func_mod.__name__}({func_mod.__class__.__qualname__})')
@wraps(func)
def wrapper(mod):
value = func()
if cached:
setattr(func_mod.__class__, func_name, value)
delattr(func_mod, func_name)
return value
wrapper.__name__ = func_name
setattr(func_mod.__class__, func_name, property(wrapper))
return wrapper
def cached_module_property(func):
return module_property(func, cached=True)
## Copied from: https://github.com/dssg/dickens/blob/2.0.0/src/descriptors.py
class class_property:
"""Descriptor decorator implementing a class-level read-only property."""
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, cls=None):
if cls is None:
cls = type(instance)
return self.__func__(cls)
class cached_class_property:
"""Descriptor decorator implementing a class-level, read-only property,
which caches its results on the class(es) on which it operates.
Inheritance is supported, insofar as the descriptor is never hidden by its cache;
rather, it stores values under its access name with added underscores. For example,
when wrapping getters named "choices", "choices_" or "_choices", each class's result
is stored on the class at "_choices_"; decoration of a getter named "_choices_"
would raise an exception."""
class AliasConflict(ValueError):
pass
def __init__(self, func):
self.__func__ = func
self.__cache_name__ = '_{}_'.format(func.__name__.strip('_'))
if self.__cache_name__ == func.__name__:
raise self.AliasConflict(self.__cache_name__)
def __get__(self, instance, cls=None):
if cls is None:
cls = type(instance)
try:
return vars(cls)[self.__cache_name__]
except KeyError:
result = self.__func__(cls)
setattr(cls, self.__cache_name__, result)
return result
my_function()
my_cached_function() -> 480
my_module_property()
my_cached_module_property() -> 300
my_static_method()
my_class_method(<class '__main__.MyClass'>)
my_class_property(<class '__main__.MyClass'>)
my_cached_class_property(<class '__main__.MyClass'>) -> 681
my_instance_method(<__main__.MyClass object at 0x7f3d49fbda90>)
my_instance_property(<__main__.MyClass object at 0x7f3d49fbda90>)
my_cached_instance_property(<__main__.MyClass object at 0x7f3d49fbda90>) -> 592
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment