繁体   English   中英

python中的参数类

[英]Parametric classes in python

我想定义一对几乎相同的类,只是类方法以两种不同的方式进行修饰。 目前,我只有一个工厂函数,它将装饰器作为参数,使用该装饰器构造类,然后返回该类。 大大简化了,像这样的工作:

# Defined in mymodule.py

def class_factory(decorator):
    class C:
        @decorator
        def fancy_func(self, x):
            # some fanciness
            return x
    return C

C1 = class_factory(decorator1)
C2 = class_factory(decorator2)

我可以像往常一样使用这些:

import mymodule

c1 = mymodule.C1()
c2 = mymodule.C2()

出于多种原因,我对此并不完全满意。 首先,纯粹出于审美原因:两个对象的类型都显示为mymodule.class_factory.<locals>.C 它们实际上并不相同,但它们看起来很像,并且会导致文档出现问题。 其次,我的课很复杂。 我实际上喜欢使用继承和混合等等,但无论如何,那些其他类也需要访问装饰器。 所以目前我做了几个工厂,在子类工厂内部调用父类工厂,子类​​继承了这样创建的父类。 但这意味着我不能真正将结果父母用作工厂外的类。

所以我的问题是

  1. 这种事情有没有更好的设计模式? 如果有某种方法可以使用继承,那将非常方便,其中装饰器实际上是类中的方法,而我以两种不同的方式继承。

  2. 通过在返回之前更改C.__qualname__来更改类名的<locals>部分有什么问题吗?


更具体地说:我希望该类的一个版本能够非常快速地处理 numpy 数组,而我希望该类的另一个版本能够处理任意 Python 对象——尤其是 sympy 表达式。 所以首先,我用@numba.guvectorize (和亲戚)装饰。 这意味着我实际上需要向 numba 传递一些签名,所以我不能仅仅依靠 numba 回退到第二种情况的对象模式。 但为了简单起见,我认为我们可以在这里忽略签名的问题。 对于第二种情况,我基本上制作了一个无操作装饰器,它忽略签名并且对函数没有任何作用。

这是使用__init_subclass__的方法。 我在这里使用关键字参数,但您可以轻松更改它,以便将装饰器定义为C1C2上的方法并应用于__init_subclass__

def passthru(f):
    return f

class BaseC:
    def __init_subclass__(cls, /, decorator=passthru, **kwargs):
        super().__init_subclass__(**kwargs)
        # if you also have class attributes or methods you don't want to decorate,
        # you might need to maintain an explicit list of decoratable methods
        for attr in dir(cls):
            if not attr.startswith('__'):
                setattr(cls, attr, decorator(getattr(cls, attr)))
    def fancy_func(self, x):
        # some fanciness
        return x

def two(f):
    return lambda self, x: "surprise"

class C1(BaseC):
    pass

class C2(BaseC, decorator=two):
    pass

print(C1().fancy_func(42))
print(C2().fancy_func(42))

# further subclassing
class C3(C2):
    pass

print(C3().fancy_func(42))

我接受了@Jasmijn 使用__init_subclass__的建议。 但由于我确实需要多个装饰器(jit、guvectorize,有时甚至在将 numba 与其他方法一起使用时都不需要),我对其进行了一些调整。 我没有对每个公共方法进行抖动,而是使用装饰器来标记带有解释如何编译它们的属性的方法。

我像最初那样装饰各个方法,指示是否jit或诸如此类。 但是这些装饰器实际上不做任何编译; 他们只是向函数添加隐藏属性,指示是否以及如何应用实际的装饰器。 然后,当创建子类时, __init_subclass__循环遍历,在所有子类的方法上查找这些属性,并应用任何请求的编译。

我把它变成了一个非常通用的类,在下面命名为Jitter 任何想要以多种方式选择 jitting 的类都可以从这个类继承并使用Jitter.jitJitter.guvectorize装饰方法。 默认情况下,这些函数不会发生任何变化,因此 Jitter 的第一个子类可以与 sympy 一起使用。 但是我也可以从这样的类继承,同时将相关关键字添加到类定义中,从而在子类中启用 jitting。 这是Jitter类:

class Jitter:
    def jit(f):
        f._jit = True
        return f

    def guvectorize(*args, **kwargs):
        def wrapper(f):
            f._guvectorize = (args, kwargs)
            return f
        return wrapper

    def __init_subclass__(cls, /, jit=None, guvectorize=None, **kwargs):
        super().__init_subclass__(**kwargs)
        for attr_name in dir(cls):
            attr = getattr(cls, attr_name)
            if jit is not None and hasattr(attr, '_jit'):
                setattr(cls, attr_name, jit(attr))
            elif guvectorize is not None and hasattr(attr, '_guvectorize'):
                args, kwargs = getattr(attr, '_guvectorize')
                setattr(cls, attr_name, guvectorize(*args, **kwargs)(attr))

现在,我可以非常方便地继承这个类:

import numba as nb


class Adder(Jitter):
    @Jitter.jit
    def add(x, y):
        return x + y


class NumbaAdder(Adder, jit=nb.njit):
    pass

这里, Adder.add是一个普通的 Python 函数,恰好有一个_jit属性,但NumbaAdder.add是一个 numba jit 函数。 对于更真实的代码,我将使用相同的Jitter类和相同的NumbaAdder类,但会将所有复杂性放入Adder类中。

请注意,我们可以使用Adder.jit装饰,但这与使用Adder.jit进行装饰Jitter.jit ,因为Adder.jit在类定义中的装饰器已经被应用之前不会改变(如果有的话) ,所以我们还是需要通过循环与应用JIT功能__init_subclass__

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM