Python子类方法从超类方法继承装饰器

时间:2019-07-19 00:56:37

标签: python-3.x inheritance python-decorators metaclass class-decorator

我有一个超类,该超类具有retrieve()方法,其子类每个都实现自己的retrieve()方法。我希望每个retrieve()方法都经过修饰以在返回相同的args时缓存返回值,而不必在每个子类中都修饰该方法。

装饰器似乎没有被继承。我可能可以调用超类的方法来设置缓存,但目前我的超类引发了NotImplemented异常,这是我喜欢的。

import json
import operator
from cachetools import cachedmethod, TTLCache

def simple_decorator(func):
    def wrapper(*args, **kwargs):
        #check cache
        print("simple decorator")
        func(*args, **kwargs)
        #set cache
    return wrapper


class AbstractInput(object):
    def __init__(self, cacheparams = {'maxsize': 10, 'ttl': 300}):
        self.cache = TTLCache(**cacheparams)
        super().__init__()

    @simple_decorator
    def retrieve(self, params):
        print("AbstractInput retrieve")
        raise NotImplementedError("AbstractInput inheritors must implement retrieve() method")

class JsonInput(AbstractInput):
    def retrieve(self, params):
        print("JsonInput retrieve")
        return json.dumps(params)

class SillyJsonInput(JsonInput):
    def retrieve(self, params):
        print("SillyJsonInput retrieve")
        params["silly"] = True
        return json.dumps(params)

实际结果:

>>> ai.retrieve(params)
ai.retrieve(params)
simple decorator
AbstractInput retrieve
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 8, in wrapper
  File "<string>", line 22, in retrieve
NotImplementedError: AbstractInput inheritors must implement retrieve() method
>>> ji.retrieve(params)
ji.retrieve(params)
JsonInput retrieve
'{"happy": "go lucky", "angry": "as a wasp"}'

所需结果:

>>> ai.retrieve(params)
ai.retrieve(params)
simple decorator
AbstractInput retrieve
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 8, in wrapper
  File "<string>", line 22, in retrieve
NotImplementedError: AbstractInput inheritors must implement retrieve() method
>>> ji.retrieve(params)
simple decorator
ji.retrieve(params)
JsonInput retrieve
'{"happy": "go lucky", "angry": "as a wasp"}'

2 个答案:

答案 0 :(得分:0)

好的,似乎我可以“装饰”超类中的方法,并且即使该方法在子类中使用元类覆盖了,子类也可以继承该修饰。在这种情况下,我将使用名为CacheRetrieval的元类使用simple_decorator装饰AbstractInput及其子类中的所有“检索”方法。

val externalUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI

val cursor = context.contentResolver
                .query(
                    externalUri,
                    arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATE_MODIFIED),
                    null,
                    null,
                    "${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
                )

if(cursor != null){
    while(cursor.moveToNext()){
        val id = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns._ID))
        val uri = ContentUris.withAppendedId(externalUri, id.toLong())

        //do whatever you need with the uri
    }
}

cursor?.close()

此页面为我提供了帮助: https://stackabuse.com/python-metaclasses-and-metaprogramming/

答案 1 :(得分:0)

是的,使用元类将装饰器强制为特定的方法,因为您输入自己的答案是正确的。只需进行一些更改,就可以使装饰的方法不固定-例如,装饰功能中设置的属性可以用作“标记”,这样的装饰器应在覆盖方法时被强制使用。 / p>

此外,自Python 3.6起,存在一个新的类级别机制-特殊方法__init_subclass__,其特定目的是减少对元类的需求。元类可能很复杂,如果您的类层次结构需要组合多个元类,那么您可能会有些头疼。

__init_subclass__方法放在基类上,并且每次创建子类时都会调用一次。包装逻辑可以放在此处。

基本上,您可以修改装饰器以放置我上面提到的标记,并将该类添加到继承层次结构中-可以将它作为mixin类进行多次继承,因此可以将其重用于各种类树,如果需要:

def simple_decorator(func):
    def wrapper(*args, **kwargs):
        print("check cache")
        rt = func(*args, **kwargs)
        print("set cache")
        return rt
    wrapper.inherit_decorator = simple_decorator
    return wrapper

class InheritDecoratorsMixin:
    def __init_subclass__(cls, *args, **kwargs):
         super().__init_subclass__(*args, **kwargs)
         decorator_registry = getattr(cls, "_decorator_registry", {}).copy()
         cls._decorator_registry = decorator_registry
         # Check for decorated objects in the mixin itself- optional:
         for name, obj in __class__.__dict__.items():
              if getattr(obj, "inherit_decorator", False) and not name in decorator_registry:
                  decorator_registry[name] = obj.inherit_decorator
         # annotate newly decorated methods in the current subclass:
         for name, obj in cls.__dict__.items():
              if getattr(obj, "inherit_decorator", False) and not name in decorator_registry:
                  decorator_registry[name] = obj.inherit_decorator
         # finally, decorate all methods anottated in the registry:
         for name, decorator in decorator_registry.items():
              if name in cls.__dict__ and getattr(getattr(cls, name), "inherit_decorator", None) != decorator:
                    setattr(cls, name, decorator(cls.__dict__[name]))

就是这样-每个新的子类都将具有自己的_decorator_registry属性,其中所有祖先中装饰方法的名称以及要应用的装饰器 都将被注释。

如果该装饰器应一次使用该方法,并且在被覆盖的方法对其祖先执行super()调用时不重复使用装饰器(当您装饰高速缓存时则不是如此),因为超级方法将不会变得更加棘手-可以做到。

但是,这样做很棘手-因为超类中的装饰器实例将是子类上的装饰器实例以外的其他实例-一种将信息传递给“该方法的装饰器代码已在此链中运行”的方式“调用”将使用实例级标记-如果代码要支持并行性,则该标记应为线程局部变量。

所有这些检查将导致相当复杂的样板放入可能是一个简单的装饰器中-因此我们可以为要一次性运行的“装饰器”创建一个“装饰器”。另外,用childmost装饰的decoratos仅在“最子级”类上运行,而在调用super()

时不在超类中的相应方法上运行


import threading

def childmost(decorator_func):

    def inheritable_decorator_that_runs_once(func):
        decorated_func = decorator_func(func)
        name = func.__name__
        def wrapper(self, *args, **kw):
            if not hasattr(self, f"_running_{name}"):
                setattr(self, f"_running_{name}", threading.local())
            running_registry = getattr(self, f"_running_{name}")
            try:
                if not getattr(running_registry, "running", False):
                    running_registry.running = True
                    rt = decorated_func(self, *args, **kw)
                else:
                    rt = func(self, *args, **kw)
            finally:
                running_registry.running = False
            return rt

        wrapper.inherit_decorator = inheritable_decorator_that_runs_once
        return wrapper
    return inheritable_decorator_that_runs_once

使用第一个清单的示例:

class A: pass

class B(A, InheritDecoratorsMixin):
    @simple_decorator
    def method(self):
        print(__class__, "method called")

class C(B):
   def method(self):
       print(__class__, "method called")
       super().method()

然后将清单1和这些A = B-C类粘贴到 解释器,结果是这样的:

In [9]: C().method()                                                                         
check cache
<class '__main__.C'> method called
check cache
<class '__main__.B'> method called
set cache
set cache

(这里的“ A”类是完全可选的,可以省略)


使用第二个清单的示例:


# Decorating the same decorator above:

@childmost
def simple_decorator2(func):
    def wrapper(*args, **kwargs):
        print("check cache")
        rt = func(*args, **kwargs)
        print("set cache")
        return rt
    return wrapper

class D: pass

class E(D, InheritDecoratorsMixin):
    @simple_decorator2
    def method(self):
        print(__class__, "method called")

class F(E):
   def method(self):
       print(__class__, "method called")
       super().method()

结果:


In [19]: F().method()                                                                        
check cache
<class '__main__.F'> method called
<class '__main__.E'> method called
set cache