Python: 如何通过元类创建实例属性?
Python: How to create instance attribute by metaclass?
我正在使用 metaclass 为新的 class 创建 属性,如下所示:
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 只生成 getter
和 setter
方法。实际的实例属性是在您第一次设置值时创建的。所以我想知道是否有办法通过 metaclass.
为 class 创建实例属性
这是我现在正在使用的解决方法,但我一直想知道是否有更好的方法:
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)
这是我所说的注入新 __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__
来初始化秘密属性。然后在创建 class 之前将其注入 class 命名空间,但存储对原始 __init__
(如果有)的引用,以便在需要时调用它。
我正在使用 metaclass 为新的 class 创建 属性,如下所示:
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 只生成 getter
和 setter
方法。实际的实例属性是在您第一次设置值时创建的。所以我想知道是否有办法通过 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)
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)
这是我所说的注入新 __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__
来初始化秘密属性。然后在创建 class 之前将其注入 class 命名空间,但存储对原始 __init__
(如果有)的引用,以便在需要时调用它。