Django Form源码分析之Metaclass的应用

引言

在上一篇对BaseForm的源码分析中,还有部分的迷惑没有解决。

  • Question:

    • 在BaseForm的初始化函数中,可以看到有如下一段代码,究竟self.base_fields从何而来?Form是如何实现Form和Field的结合?

      
      # The base_fields class attribute is the *class-wide* definition of
      
      
      # fields. Because a particular *instance* of the class might want to
      
      
      # alter self.fields, we create self.fields here by copying base_fields.
      
      
      # Instances should always modify self.fields; they should not modify
      
      
      # self.base_fields.
      
      self.fields = copy.deepcopy(self.base_fields)
    • 在使用Django Form的时候,继承的是forms.Form而并非BaseForm,为何?

  • Conclusion:以上问题都可以通过对Metaclass的分析解决。

Metaclass

首先了解到底什么是Metaclass?推荐这篇文章深刻理解Python中的元类(metaclass) 。
在Python中,一切皆为对象,包括类也是一种对象。既然类是一种对象,那么肯定会有生成类的类,这就是元类(Metaclass),元类负责生成类这种对象。

type()

一般使用type函数来确定函数的类型,但是type函数还发挥了另一个作用:作为元类动态地生成类。
Usage: type(name, base, attr)
Example:

>>> Person = type('Person', (), {'age': 20, 'gender': 'male'})
>>> me = Person()
>>> me.age
20
>>> me.gender
'male'
>>> type(me)
<class '__main__.Person'> >>> type(Person) <type 'type'>

像常用的int,str等内置类型,它们的元类也都是type,而type的元类恰恰是它自身。

>>> type(1)
<type 'int'>
>>> type(int)
<type 'type'>
>>> type('1')
<type 'str'>
>>> type(str)
<type 'type'>
>>> type(type)
<type 'type'>

forms.Form not BaseForm

现在可以解决第二个问题为什么使用forms.Form。
先看源码:

class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
    "A collection of Fields, plus their associated data."
    # This is a separate class from BaseForm in order to abstract the way
    # self.fields is specified. This class (Form) is the one that does the
    # fancy metaclass stuff purely for the semantic sugar -- it allows one
    # to define a form using declarative syntax.
    # BaseForm itself has no way of designating self.fields.

可以把真正能使用的Form类看成是经过经过元类加工之后的BaseForm,首先把BaseForm的name,base,attr传进当前元类,调用当前元类的__new__方法(其中cls参数其实就是元类的实例),对attr进行一定程度的加工,再调用父类的__new__方法直到type,返回一个元类的实例,即所需要的类Form。

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(meta):

        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)
    return type.__new__(metaclass, 'temporary_class', (), {})

with_metaclass为了做到python2和python3的兼容,直接调用元类生成一个实例,最后通过type返回这个实例。

DeclarativeFieldsMetaclass(实现Form与Field的结合)

接下来分析Form的元类。

class DeclarativeFieldsMetaclass(MediaDefiningClass):
    """ Metaclass that collects Fields declared on the base classes. """
    def __new__(mcs, name, bases, attrs):
        # Collect fields from current class.
        current_fields = []
        for key, value in list(attrs.items()):
            if isinstance(value, Field):
                current_fields.append((key, value))
                attrs.pop(key)
        current_fields.sort(key=lambda x: x[1].creation_counter)
        attrs['declared_fields'] = OrderedDict(current_fields)

        new_class = (super(DeclarativeFieldsMetaclass, mcs)
            .__new__(mcs, name, bases, attrs))

        # Walk through the MRO.
        declared_fields = OrderedDict()
        for base in reversed(new_class.__mro__):
            # Collect fields from base class.
            if hasattr(base, 'declared_fields'):
                declared_fields.update(base.declared_fields)

            # Field shadowing.
            for attr, value in base.__dict__.items():
                if value is None and attr in declared_fields:
                    declared_fields.pop(attr)

        new_class.base_fields = declared_fields
        new_class.declared_fields = declared_fields

        return new_class

首先将所有Field的实例收集到current_fields中,并按照Field.creation_counter进行排序,在attr中添加了declared_fields属性后调用父类(这个父类是指metaclass的父类)的new方法直到type。接着是继承base(base是指实际的类的父类)中的field属性,可以看到源码中遍历了MRO(相反了之后就是从远到近),并更新了declared_fields属性,最后返回了field属性拼接完成的类(即类中已经声明了base_fields属性)。

你可能感兴趣的:(源码,django,Class)