Python mixin/decorator/__metaclass__ 用于基类增强
Posted
技术标签:
【中文标题】Python mixin/decorator/__metaclass__ 用于基类增强【英文标题】:Python mixin/decorator/__metaclass__ for base class enhancement 【发布时间】:2019-02-15 05:22:51 【问题描述】:我正在为 Django REST API 实现内容感知缓存系统。我想开发一个组件,它可以添加到现有视图中,通过检查缓存并在未命中时回退到基类行为来修改基类的行为。
基本上,我有这样的事情:
class Base:
def get(self, request, *args, **kwargs):
....
return Response
class AnotherBase:
def get(self, request, *args, **kwargs):
....
return Response
class Derived(Base):
pass
class OtherDerived(AnotherBase):
pass
我最初的想法是按照
class Cacheable:
def get(self, request, *args, **kwargs):
cache_key = self.get_cache_key(request)
base_get = #.... and this is the problem
return cache.get(cache_key, base_get(request, *args, **kwargs))
def get_cache_key(self, request):
# .... do stuff
class Derived(Cacheable, Base):
pass
class AnotherDerived(Cacheable, AnotherBase):
pass
很明显这不起作用,因为我不知道如何,或者是否可能,或者是否建议从 mixin 访问兄弟超类。
我的目标是一个实现,它允许我向现有视图添加缓存行为,而无需触及现有类的内部。
给定一个视图类,C
,s.t. C.get(request, *args, **kwargs) -> Response
,有没有函数,F
,s.t. F(C).get(...
在回退到 C.get
之前是否会检查缓存?在这个准正式的符号中,我们会说在类定义中向最左边的父类添加一个 mixin 算作一个函数。
使用方法装饰器是否更合适?或者类装饰器如何工作?
然后我在研究这个时看到了对__metaclass__
的引用,但我不清楚这种方法是什么样的。
这是 Python 3.6
【问题讨论】:
我认为问题的一部分也是cache.get(cache_key, base_get(request, *args, **kwargs))
。您正在不必要地计算基本获取。
【参考方案1】:
简单示例:
def Base:
def _get_data(self):
# get the data eg from database
return self._get_data_native()
def get(self, request, *args, **kwargs):
return Response(self._get_data())
def Cacheable(Base):
def _get_data(self):
# get from cache ...
result = ...
if result is None:
# or from base ...
result = ...
return result
def Derived(Cacheable):
def _get_data_native(self):
# get the data eg from database
...
通过从 Cacheable 继承,您可以在此处包含缓存,因为 _get_data
在那里被覆盖。
对于这个问题,如果您只想在一个地方添加缓存,则不需要元类或装饰器。
当然,装饰器可以用于以更通用的方式包含缓存。
例如看这个答案:Is there a decorator to simply cache function return values?
【讨论】:
我会在 cmets 中详细说明,但您提供的示例代码并没有解决我的设计问题 并非所有视图都继承自同一个基类,但都可以预期有一个公共方法get(self, request, *args, **kwargs)
当然,它们是:都继承自对象。我只是想提供一个如何完成的示例,而不是为您提供完整的解决方案。完整的解决方案必须由您在完整程序的上下文中定义。另外:您不需要具有相同的基类。只需定义可以覆盖的正确方法。如果所有这些都不适合您的问题,您可以查看装饰器解决方案(我在上面提供了链接)。我认为您不需要针对此类问题的元类。
我了解如何通过简单的继承来做到这一点。我正在寻找的是一种不需要更改现有类的内部结构的技术。此方法需要重构所有现有视图。
在所有情况下都无法在不更改类的情况下添加新功能。你可以看看装饰器技术。如果它有效,那么你很幸运。如果没有,您可能需要进行一些重构。它属于架构远见,以某种方式构建您的类,可以轻松添加额外的功能。没有“灵丹妙药”可以无条件地为您的设计神奇地添加东西。这就是为什么架构和类设计需要很多远见和经验的原因。【参考方案2】:
答案是装饰器和一些Django
特定的库。
from django.utils.decorators import method_decorator
from django.core.cache import cache
def cached_get(cache_key_func=None):
"""
Decorator to be applied via django.utils.decorators.method_decorator
Implements content-aware cache fetching by decorating the "get" method
on a django View
:param cache_key_func: a function of fn(request, *args, **kwargs) --> String
which determines the cache key for the request
"""
def decorator(func):
def cached_func(request, *args, **kwargs):
assert cache_key_func is not None, "cache_key_function is required"
key = cache_key_func(request, *args, **kwargs)
result = cache.get(key)
if result is None:
return func(request, *args, **kwargs)
return Response(result)
return cached_func
return decorator
@method_decorator(cached_get(cache_key_func=get_cache_key), name="get")
class SomeView(BaseView):
...
def get_cache_key(request):
# do arbitrary processing on request, the following is the naïve melody
key = urllib.urlencode(request.query_params)
return key
因此解决方案是使用 Django 的内置 method_decorator
,它将其第一个参数,一个装饰器,应用于由第二个参数命名的装饰类的方法,name
,到method_decorator
。我定义了一个高阶函数cached_get
,它接受另一个函数作为它的参数,并返回一个柯里化函数(闭包,所谓的)。通过使用函数get_cache_key
(而不是,请注意,调用该函数)调用它,我有一个装饰器,它将应用于SomeView
上的“get”方法。
装饰器本身是一个简单的 Python 装饰器——在这个应用程序中,它是 cached_func
,而原始的、未装饰的 get
方法是 func
。因此,cached_func
替换了SomeView.get
,所以当SomeView.get
被调用时,它首先检查缓存,但在未命中时回退到未修饰的方法。
我希望这种方法能够在通用适用性与内容感知密钥派生之间取得平衡。
【讨论】:
【参考方案3】:我的两分钱:
-
您正在这里走进不为人知的领域。熟悉所有相关概念,尝试一些,然后再决定。
Here 是一个很好的元类教程。
Here 有一篇关于装饰器的文章。
我绝不隶属于该网站。
【讨论】:
这是一个很好的评论,但我认为作为答案,它只是链接。您唯一真正的内容是“玩它”。 确实,我熟悉所有的概念,我目前正在玩这些选项。我来到这里是因为其他人可能有见解,并且因为描述问题以便其他人能够理解是发展我自己的理解的最有效策略。以上是关于Python mixin/decorator/__metaclass__ 用于基类增强的主要内容,如果未能解决你的问题,请参考以下文章