使用元类自动分配类的成员变量

时间:2016-10-25 14:31:46

标签: python metaclass

在Python类中,我想自动将成员变量分配给__init__函数参数,如下所示:

class Foo(object):
    def __init__(self, arg1, arg2 = 1):
        self.arg1 = arg1
        self.arg2 = arg2
  • 我想在 init 中明确地拥有参数名称 为了清晰的代码而起作用。
  • 出于同样的原因,我不想使用装饰器。

是否可以使用自定义元类实现此目的?

1 个答案:

答案 0 :(得分:1)

首先,免责声明。 Python对象的创建和初始化可能很复杂且非常动态。这意味着很难找到适合角落案例的解决方案。此外,解决方案倾向于使用更暗的魔法,所以当它们不可避免地出错时,它们很难调试。

其次,您的类具有如此多的初始化参数这一事实可能暗示它具有太多许多参数。其中一些可能是相关的,可以在较小的类中组合在一起。例如,如果我正在制造一辆汽车,最好有:

class Car:

    def __init__(self, tires, engine):
        self.tires = tires
        self.engine = engine

class Tire:

    def __init__(self, radius, winter=False):
        self.radius = radius
        self.winter = winter

class Engine:

    def __init__(self, big=True, loud=True):
        self.big = big
        self.loud = loud

而不是

class Car:

    def __init__(self, tire_radius, winter_tires=False, 
                 engine_big=True, engine_loud=True):
        self.tire_radius = tire_radius
        self.winter_tires winter_tires
        self.engine_big = engine_big
        self.engine_loud = engine_loud

所有这些都说,这是一个解决方案。我没有在我自己的代码中使用它,所以它不是“经过实战考验”。但它至少似乎适用于简单的情况。使用风险自负。

首先,这里不需要元类,我们可以在__init__方法上使用简单的装饰器。我认为这更具可读性,因为很明显我们只修改了__init__的行为,而不是更深层次的类创建。

import inspect
import functools

def unpack(__init__):
    sig = inspect.signature(__init__)

    @functools.wraps(__init__)
    def __init_wrapped__(self, *args, **kwargs):
        bound = sig.bind(self, *args, **kwargs)
        bound.apply_defaults()

        # first entry is the instance, should not be set
        # discard it, taking only the rest
        attrs = list(bound.arguments.items())[1:]

        for attr, value in attrs:
            setattr(self, attr, value)

        return __init__(self, *args, **kwargs)

    return __init_wrapped__

此装饰器使用inspect模块来检索__init__方法的签名。然后我们简单地遍历属性并使用setattr来分配它们。

在使用中,它看起来像:

class Foo(object):

    @unpack
    def __init__(self, a, b=88):
        print('This still runs!')

这样

>>> foo = Foo(42)
This still runs!
>>> foo.a
42
>>> foo.b
88

我不确定每个内省工具都会看到装饰__init__的正确签名。特别是,我不确定Sphinx是否能做出“正确的事”。但至少inspect模块的signature函数将返回包装函数的签名,可以进行测试。

如果确实想要一个元类解决方案,那么它就足够简单了(但是可读性更差,更有魔力,IMO)。您只需要编写一个应用unpack装饰器的类工厂:

def unpackmeta(clsname, bases, dct):
    dct['__init__'] = unpack(dct['__init__'])
    return type(clsname, bases, dct)

class Foo(metaclass=unpackmeta):

    def __init__(self, a, b=88):
        print('This still runs!')

输出与上面的例子相同。