Python:如何通过元类创建实例属性?

时间:2015-10-12 03:22:32

标签: python metaclass

我使用元类为这样的新类创建属性:

class Property(object):
    def __init__(self, internal_name, type_, default_value):
        self._internal_name = internal_name
        self._type = type_
        self._default_value = default_value

    def generate_property(self):
        def getter(object_):
            return getattr(object_, self._internal_name)
        def setter(object_, value):
            if not isinstance(value, self._type):
                raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value)))
            else:
                setattr(object_, self._internal_name, value)
        return property(getter, setter)

class AutoPropertyMeta(type):
    def __new__(cls, name, bases, attributes):
        for name, value in attributes.iteritems():
            if isinstance(value, Property):
                attributes[name] = value.generate_property()
        return super(AutoPropertyMeta, cls).__new__(cls, name, bases, attributes)

通过这种方式,我可以编写如下代码:

class SomeClassWithALotAttributes(object):
    __metaclass__ = AutoPropertyMeta
    attribute_a = Property("_attribute_a", int, 0)
    ...
    attribute_z = Property("_attribute_z", float, 1.0)

而不是:

class SomeClassWithALotAttributes(object):
    def __init__(self):
        self._attribute_a = 0
        ...
        self._attribute_z = 1.0

    def get_attribute_a(self):
        return self._attribute_a

    def set_attribute_a(self, value):
        if not isinstance(value, int):
            raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value))
        else:
            self._attribute_a = value

    attribute_a = property(get_attribute_a, set_attribute_a)
    ...

如果您始终在获取属性值之前设置值,则效果很好,因为AutoPropertyMeta仅生成gettersetter方法。第一次设置值时会创建实际的实例属性。所以我想知道是否有一种方法可以通过元类为类创建实例属性。

这是我现在使用的解决方法,但我总是想知道是否有更好的方法:

class Property(object):
    def __init__(self, internal_name, type_, default_value):
        self._internal_name = internal_name
        self._type = type_
        self._default_value = default_value

    def generate_property(self):
        def getter(object_):
            return getattr(object_, self._internal_name)
        def setter(object_, value):
            if not isinstance(value, self._type):
                raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value)))
            else:
                setattr(object_, self._internal_name, value)
        return property(getter, setter)

    def generate_attribute(self, object_):
        setattr(object_, self._internal_name, self._default_value)

class AutoPropertyMeta(type):
    def __new__(cls, name, bases, attributes):
        property_list = []
        for name, value in attributes.iteritems():
            if isinstance(value, Property):
                attributes[name] = value.generate_property()
                property_list.append(value)
        attributes["_property_list"] = property_list
        return super(AutoPropertyMeta, cls).__new__(cls, name, bases, attributes)

class AutoPropertyClass(object):
    __metaclass__ = AutoPropertyMeta
    def __init__(self):
        for property_ in self._property_list:
            property_.generate_attribute(self)

class SomeClassWithALotAttributes(AutoPropertyClass):
    attribute_a = Property("_attribute_a", int, 0)

1 个答案:

答案 0 :(得分:1)

以下是关于注入新__init__的意思的示例。请注意,这只是为了好玩,你不应该这样做。

class Property(object):
    def __init__(self, type_, default_value):
        self._type = type_
        self._default_value = default_value

    def generate_property(self, name):
        self._internal_name = '_' + name
        def getter(object_):
            return getattr(object_, self._internal_name)
        def setter(object_, value):
            if not isinstance(value, self._type):
                raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value)))
            else:
                setattr(object_, self._internal_name, value)
        return property(getter, setter)

class AutoPropertyMeta(type):
    def __new__(meta, name, bases, attributes):
        defaults = {}
        for name, value in attributes.iteritems():
            if isinstance(value, Property):
                attributes[name] = value.generate_property(name)
                defaults[name] = value._default_value
        # create __init__ to inject into the class
        # our __init__ sets up our secret attributes
        if '__init__' in attributes:
            realInit = attributes['__init__']
            # we do a deepcopy in case default is mutable
            # but beware, this might not always work
            def injectedInit(self, *args, **kwargs):
                for name, value in defaults.iteritems():
                    setattr(self, '_' + name, copy.deepcopy(value))
                # call the "real" __init__ that we hid with our injected one
                realInit(self, *args, **kwargs)
        else:
             def injectedInit(self, *args, **kwargs):
                for name, value in defaults.iteritems():
                    setattr(self, '_' + name, copy.deepcopy(value))
        # inject it
        attributes['__init__'] = injectedInit
        return super(AutoPropertyMeta, meta).__new__(meta, name, bases, attributes)

然后:

class SomeClassWithALotAttributes(object):
    __metaclass__ = AutoPropertyMeta
    attribute_a = Property(int, 0)
    attribute_z = Property(list, [1, 2, 3])

    def __init__(self):
        print("This __init__ is still called")

>>> x = SomeClassWithALotAttributes()
This __init__ is still called
>>> y = SomeClassWithALotAttributes()
This __init__ is still called
>>> x.attribute_a
0
>>> y.attribute_a
0
>>> x.attribute_a = 88
>>> x.attribute_a
88
>>> y.attribute_a
0
>>> x.attribute_z.append(88)
>>> x.attribute_z
[1, 2, 3, 88]
>>> y.attribute_z
[1, 2, 3]
>>> x.attribute_z = 88
Traceback (most recent call last):
  File "<pyshell#76>", line 1, in <module>
    x.attribute_z = 88
  File "<pyshell#41>", line 12, in setter
    raise TypeError("Expect type {0}, got {1}.".format(self._type, type(value)))
TypeError: Expect type <type 'list'>, got <type 'int'>.

我们的想法是编写自己的__init__来执行秘密属性的初始化。然后在创建类之前将其注入类命名空间,但存储对原始__init__(如果有)的引用,以便您可以在需要时调用它。