Python元类
Python的关键概念是对象。 Python中几乎所有的东西都是一个对象,它包括函数和类。因此,函数和类可以作为参数传递,可以作为实例存在,等等。最重要的是,对象的概念让类可以生成其他类。
生成其他类的类被定义为元类。在本节中,我们将讨论元类的概念以及使用它们的具体方法。在本节中,我们将介绍以下主题:
- 类型
- 编写元类
- 元类用例
类型
一个类定义了它的对象的属性和可用的动作,它也作为一个创建对象的工厂。让我们通过直接使用类型创建一个类来了解这个过程。用于类实例化的确切类称为类型。通常,我们使用一种称为class 关键字的特殊语法来定义一个类,但这种语法是type class的替代品。让我们用一个例子来说明:
首先,我们研究使用class 关键字创建类的场景。让我们检查以下代码:
Python3
class FoodType(object):
def __init__(self, ftype):
self.ftype = ftype
def getFtype(self):
return self.ftype
def main():
fType = FoodType(ftype = 'Vegetarian')
print(fType.getFtype())
main()
Python3
def init(self, ftype):
self.ftype = ftype
def getFtype(self):
return self.ftype
FoodType = type('FoodType', (object, ), {
'__init__': init,
'getFtype' : getFtype,
})
fType = FoodType(ftype ='Vegetarian')
print(fType.getFtype())
Python3
class FoodType(object):
def __init__(self, ftype):
self.ftype = ftype
def getFtype(self):
return self.ftype
class VegType(FoodType):
def vegFoods(self):
return {'Spinach', 'Bitter Guard'}
def main():
vType = VegType(ftype = 'Vegetarian')
print(vType.getFtype())
print(vType.vegFoods())
main()
Python3
def init(self, ftype):
self.ftype = ftype
def getFtype(self):
return self.ftype
FoodType = type('FoodType', (object, ), {
'__init__': init,
'getFtype' : getFtype,
})
def vegFoods(self):
return {'Spinach', 'Bitter Guard'}
## creating subclass using type
VegType = type('VegType', (FoodType, ), {
'vegFoods' : vegFoods,
})
vType = VegType(ftype ='Vegetarian')
print(vType.getFtype())
print(vType.vegFoods())
Python3
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, superclasses, attributedict):
print("clsname:", clsname)
print("superclasses:", superclasses)
print("attrdict:", attributedict)
return super(MetaCls, cls).__new__(cls, \
clsname, superclasses, attributedict)
C = MetaCls('C', (object, ), {})
print("class type:", type(C))
Python3
class S(object):
pass
print(type(S))
Python3
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
return super(MetaCls, cls).__new__(cls, clsname, supercls, attrdict)
C = MetaCls('C', (object, ), {})
## class A inherits from MetaCls
class A(C):
pass
print(type(A))
Python3
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
return super(MetaCls, cls).__new__(cls, clsname, supercls, attrdict)
## a class of the type metclass
A = MetaCls('A', (object, ), {})
print('Type of class A:', type(A))
class B(object):
pass
print('Type of class B:', type(B))
## class C inherits from both the class, A and B
class C(A, B):
pass
print('Type of class C:', type(C))
Python3
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
return super(MetaCls, cls).__new__(cls, clsname, supercls, attrdict)
A = MetaCls('A', (object, ), {})
class NewMetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
return super(NewMetaCls, cls).__new__(cls, clsname, supercls, attrdict)
NewA = NewMetaCls('NewA', (object, ), {})
class C(A, NewA):
pass
Python3
class MainClass(type):
def __new__(cls, name, bases, attrs):
if 'foo' in attrs and 'bar' in attrs:
raise TypeError('Class % s cannot contain both foo and bar \
attributes.' % name)
if 'foo' not in attrs and 'bar' not in attrs:
raise TypeError('Class % s must provide either a foo \
attribute or a bar attribute.' % name)
else:
print('Success')
return super(MainClass, cls).__new__(cls, name, bases, attrs)
class SubClass(metaclass = MainClass):
foo = 42
bar = 34
subCls = SubClass()
Python3
class MetaCls(type):
def __new__(cls, name, bases, attrs):
# If abstract class, then skip the metaclass function
if attrs.pop('abstract', False):
print('Abstract Class:', name)
return super(MetaCls, cls).__new__(cls, name, bases, attrs)
# metaclass functionality
if 'foo' in attrs and 'bar' in attrs:
raise TypeError('Class % s cannot contain both foo and bar \
attributes.' % name)
if 'foo' not in attrs and 'bar' not in attrs:
raise TypeError('Class % s must provide either a foo \
attribute or a bar attribute.' % name)
print('Normal Class:', name)
return super(MetaCls, cls).__new__(cls, name, bases, attrs)
class AbsCls(metaclass = MetaCls):
abstract = True
class NormCls(metaclass = MetaCls):
foo = 42
Python3
class FoodType(object):
events = []
def __init__(self, ftype, items):
self.ftype = ftype
self.items = items
FoodType.events.append(self)
def run(self):
print("Food Type: % s" %(self.ftype))
print("Food Menu:", self.items)
@staticmethod
def run_events():
for e in FoodType.events:
e.run()
def sub_food(ftype):
class_name = ftype.capitalize()
def __init__(self, items):
FoodType.__init__(self, ftype, items)
# dynamic class creation and defining it as a global attribute
globals()[class_name] = \
type(class_name, (FoodType, ), dict(__init__ = __init__))
if __name__ == "__main__":
foodType = ["Vegetarian", "Nonvegetarian"]
foodItems = "Vegetarian(['Spinach', 'Bitter Guard']);\
Nonvegetarian(['Meat', 'Fish'])"
# invoking method for dynamic class creation.
[sub_food(ftype) for ftype in foodType]
# executing dynamic classes.
exec(foodItems)
FoodType.run_events()
Vegetarian
在这里,我们使用class关键字创建了一个名为FoodType的类。此类关键字充当类型语法的替代品。现在让我们看看如何使用type e 关键字。让我们看一下下面的代码:
Python3
def init(self, ftype):
self.ftype = ftype
def getFtype(self):
return self.ftype
FoodType = type('FoodType', (object, ), {
'__init__': init,
'getFtype' : getFtype,
})
fType = FoodType(ftype ='Vegetarian')
print(fType.getFtype())
Vegetarian
让我们关注类型。它有三个参数,它们如下:
- 第一个参数是一个字符串——FoodType 。该字符串被指定为类名。
- 第二个参数是一个元组 – (object, ) 。这表明FoodType类继承自对象类。在这里,结尾的逗号帮助Python解释器将其识别为元组。
- 这里,第三个参数是一个字典,它提到了一个类的属性。在这种情况下,该类有两个方法——init和getFtype。
使用类型创建子类
让我们看看创建子类的正常场景,即使用class 关键字。在这里,我们将创建一个子类VegType ,其中主类是FoodType 。
Python3
class FoodType(object):
def __init__(self, ftype):
self.ftype = ftype
def getFtype(self):
return self.ftype
class VegType(FoodType):
def vegFoods(self):
return {'Spinach', 'Bitter Guard'}
def main():
vType = VegType(ftype = 'Vegetarian')
print(vType.getFtype())
print(vType.vegFoods())
main()
Vegetarian
{'Spinach', 'Bitter Guard'}
现在让我们看看如何使用type转换上面的代码。对于FoodType类,类型的第二个参数是对象类——超类——,即FoodType是Object 类的子类。同样, VegType类是FoodType的子类,因此在创建VegType类时, type的第二个参数指的是FoodType类。
Python3
def init(self, ftype):
self.ftype = ftype
def getFtype(self):
return self.ftype
FoodType = type('FoodType', (object, ), {
'__init__': init,
'getFtype' : getFtype,
})
def vegFoods(self):
return {'Spinach', 'Bitter Guard'}
## creating subclass using type
VegType = type('VegType', (FoodType, ), {
'vegFoods' : vegFoods,
})
vType = VegType(ftype ='Vegetarian')
print(vType.getFtype())
print(vType.vegFoods())
Vegetarian
{'Spinach', 'Bitter Guard'}
编写元类
元类是直接从类型继承的类。自定义元类应该实现的方法是 __new__ 方法。元类的 __new__ 方法中提到的参数反映在类类型的 __new__ 方法中。它有四个位置参数。它们如下:
- 第一个参数是元类本身。
- 第二个参数是类名。
- 第三个参数是超类(元组形式)
- 第四个参数是类的属性(字典形式)
让我们看看下面的代码。
Python3
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, superclasses, attributedict):
print("clsname:", clsname)
print("superclasses:", superclasses)
print("attrdict:", attributedict)
return super(MetaCls, cls).__new__(cls, \
clsname, superclasses, attributedict)
C = MetaCls('C', (object, ), {})
print("class type:", type(C))
clsname: C
superclasses: (, )
attrdict: {}
class type:
你可以注意到 C 类的类型是一个元类—— MetaCls 。让我们检查一下普通类的类型。
Python3
class S(object):
pass
print(type(S))
元类继承
让我们看看,如何从元类继承。
Python3
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
return super(MetaCls, cls).__new__(cls, clsname, supercls, attrdict)
C = MetaCls('C', (object, ), {})
## class A inherits from MetaCls
class A(C):
pass
print(type(A))
在这种情况下,您可以看到类 A 是元类的一个实例。这是因为它的超类 C 是元类的一个实例。现在我们将考虑具有两个或多个不同类的类子类的场景。让我们看看下面的示例代码:
Python3
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
return super(MetaCls, cls).__new__(cls, clsname, supercls, attrdict)
## a class of the type metclass
A = MetaCls('A', (object, ), {})
print('Type of class A:', type(A))
class B(object):
pass
print('Type of class B:', type(B))
## class C inherits from both the class, A and B
class C(A, B):
pass
print('Type of class C:', type(C))
Type of class A:
Type of class B:
Type of class C:
在这里,您可以看到 C 类继承自 A 类(元类)和 B 类(类型类)。但是这里 C 类的类型是元类。这是因为当Python解释器检查超类时,它发现元类是类型本身的子类。因此它将元类视为 C 类的类型以避免任何冲突。
让我们看看另一个示例代码,其中一个类继承自两个不同的元类。
Python3
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
return super(MetaCls, cls).__new__(cls, clsname, supercls, attrdict)
A = MetaCls('A', (object, ), {})
class NewMetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
return super(NewMetaCls, cls).__new__(cls, clsname, supercls, attrdict)
NewA = NewMetaCls('NewA', (object, ), {})
class C(A, NewA):
pass
在这里,您将在尝试从两个不同的元类继承时收到以下错误消息。
Traceback (most recent call last):
File "/home/eb81e4ecb05868f83d5f375ffc78e237.py", line 18, in
class C(A, NewA):
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict)
subclass of the metaclasses of all its bases
这是因为Python只能有一个类的元类。在这里, C级不能 从两个元类继承,这会导致歧义。
元类用例
在大多数情况下,我们不需要使用元类,普通代码将适合类和对象。元类的无意义使用增加了编码的复杂性。但是在某些情况下,元类提供了清晰有效的解决方案。让我们看看几个用例。
类验证
如果您需要设计一个同意特定接口的类,那么元类是正确的解决方案。我们可以考虑一个示例代码,其中一个类需要设置任一属性。让我们看一下代码。
Python3
class MainClass(type):
def __new__(cls, name, bases, attrs):
if 'foo' in attrs and 'bar' in attrs:
raise TypeError('Class % s cannot contain both foo and bar \
attributes.' % name)
if 'foo' not in attrs and 'bar' not in attrs:
raise TypeError('Class % s must provide either a foo \
attribute or a bar attribute.' % name)
else:
print('Success')
return super(MainClass, cls).__new__(cls, name, bases, attrs)
class SubClass(metaclass = MainClass):
foo = 42
bar = 34
subCls = SubClass()
这里我们尝试设置两个属性。因此,设计阻止它设置并引发以下错误。
Traceback (most recent call last):
File "/home/fe76a380911f384c4517f07a8de312a4.py", line 13, in
class SubClass(metaclass = MainClass):
File "/home/fe76a380911f384c4517f07a8de312a4.py", line 5, in __new__
attributes.' %name)
TypeError: Class SubClass cannot both foo and bar attributes.
您可能会认为,使用装饰器可以轻松创建符合特定标准的类。但是使用类装饰器的缺点是它必须显式地应用于每个子类。
防止继承属性
元类是防止子类继承某些类函数的有效工具。这种情况可以在抽象类的情况下得到最好的解释。创建抽象类时,不需要运行类的功能。让我们看看下面的代码。
Python3
class MetaCls(type):
def __new__(cls, name, bases, attrs):
# If abstract class, then skip the metaclass function
if attrs.pop('abstract', False):
print('Abstract Class:', name)
return super(MetaCls, cls).__new__(cls, name, bases, attrs)
# metaclass functionality
if 'foo' in attrs and 'bar' in attrs:
raise TypeError('Class % s cannot contain both foo and bar \
attributes.' % name)
if 'foo' not in attrs and 'bar' not in attrs:
raise TypeError('Class % s must provide either a foo \
attribute or a bar attribute.' % name)
print('Normal Class:', name)
return super(MetaCls, cls).__new__(cls, name, bases, attrs)
class AbsCls(metaclass = MetaCls):
abstract = True
class NormCls(metaclass = MetaCls):
foo = 42
Abstract Class: AbsCls
Normal Class: NormCls
类的动态生成
类的动态生成开辟了许多可能性。让我们看看如何使用 type 动态生成类。
Python3
class FoodType(object):
events = []
def __init__(self, ftype, items):
self.ftype = ftype
self.items = items
FoodType.events.append(self)
def run(self):
print("Food Type: % s" %(self.ftype))
print("Food Menu:", self.items)
@staticmethod
def run_events():
for e in FoodType.events:
e.run()
def sub_food(ftype):
class_name = ftype.capitalize()
def __init__(self, items):
FoodType.__init__(self, ftype, items)
# dynamic class creation and defining it as a global attribute
globals()[class_name] = \
type(class_name, (FoodType, ), dict(__init__ = __init__))
if __name__ == "__main__":
foodType = ["Vegetarian", "Nonvegetarian"]
foodItems = "Vegetarian(['Spinach', 'Bitter Guard']);\
Nonvegetarian(['Meat', 'Fish'])"
# invoking method for dynamic class creation.
[sub_food(ftype) for ftype in foodType]
# executing dynamic classes.
exec(foodItems)
FoodType.run_events()
Food Type: Vegetarian
Food Menu: ['Spinach', 'Bitter Guard']
Food Type: Nonvegetarian
Food Menu: ['Meat', 'Fish']
在这种情况下,我们动态地创建了两个子类——Vegetarian 和 NonVegetarian,它们继承自 FoodType 类。
概括
使用 class 关键字设计的普通类将 type 作为其元类,而 type 是主要的元类。元类是Python中一个强大的工具,可以克服许多限制。但是大多数开发人员有一个误解,即元类很难掌握。