📜  Python元类

📅  最后修改于: 2022-05-13 01:55:32.968000             🧑  作者: Mango

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解释器将其识别为元组。
  • 这里,第三个参数是一个字典,它提到了一个类的属性。在这种情况下,该类有两个方法——initgetFtype。

使用类型创建子类

让我们看看创建子类的正常场景,即使用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__ 方法中。它有四个位置参数。它们如下:

  1. 第一个参数是元类本身。
  2. 第二个参数是类名。
  3. 第三个参数是超类(元组形式)
  4. 第四个参数是类的属性(字典形式)

让我们看看下面的代码。

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中一个强大的工具,可以克服许多限制。但是大多数开发人员有一个误解,即元类很难掌握。