Django的models达成分析

by admin on 2019年4月3日

python元类:*Django的models达成分析。*type()   **

 

 

Python 中 Meta Classes详解,pythonclasses

接触过 Django 的同学都应该丰硕了解它的 O福特ExplorerM 系统。对于 python
新手而言,那是一项大约能够被称作“黑科学技术”的特色:只要你在models.py中不管定义四个Model的子类,Django
便得以:

  1. 获得它的字段定义,并转移成表结构
  2. 读取Meta内部类,并转化成相应的布置音讯。对于尤其的Model(如abstract、proxy),还要开始展览对应的转移
  3. 为未有定义objects的Model加上多少个暗许的Manager

支出之余,小编也曾脑补过其幕后的原理。曾经,笔者觉得是那般的:

运维时,遍历models.py中的全数属性,找到Model的子类,并对其开始展览上述的改动。
当初,笔者还认为本身触碰到了真理,并曾将其应用到实际生产中——为 SAE 的 KVDB
写了3个类 O奥迪Q7M
系统。可是在促成的经过中,小编精通感受到了那种艺术的难看,而且质量并不出彩(因为要遍历全部的概念模块)。

那么实际上,Django 是怎么落实的呢?

自古以来我们制造东西的方法都是“自上而下”的,是用切削、分割、组合的方法来制造。然而,生命是自下而上地,自发地建造起来的,这个过程极为低廉。
——王晋康 《水星播种》

那句话揭破了生命的神奇所在:真正的性命都以由基本物质自发组成的,而非造物主流水生产线式的加工。

那正是说,要是 类
也有生命来说,对它和谐的修饰就不应该由调用者来成功,而应当是天生的。

幸好,python 提供了上帝的接口——那正是 Meta Classes,恐怕叫做“元类”。

元类 是什么?

简单说:元类就是类的类。

首先,要有二个定义:

python 中,壹切都以对象。

没错,一切,包括 类 本身。

既然,类 是 对象,对象 是 类的实例,那么——类 也相应有 类 才对。

类的类:type

在 python 中,大家得以用type检验二个目的的类,如:

print type(1) # <type 'int'>

若果对三个类操作呢?

print type(int) # <type 'type'>

class MyClass(object): pass

print type(MyClass) # <type 'type'>

print type(type) # <type 'type'>

那注脚:type其实是七个品类,全数类——蕴涵type自个儿——的类都以type。

type 简介

从 官方文书档案 中,大家能够精晓:

和 dict 类似,type 也是二个厂子构造函数,调用其将赶回
二个type类型的实例(即 类)。
type 有八个重载版本:

  • `type(object)`,即大家最常用的本子。
  • `type(name, bases, dict)`,一个更强硬的本子。通过点名
    类名称(`name`)、父类列表(`bases`)和 属性字典(`dict`)
    动态合成四个类。

下边三个语句等价:

class Integer(int):

  name = 'my integer'

  def increase(self, num):
    return num + 1

  # -------------------

  Integer = type('Integer', (int, ), {
  'name': 'my integer',
  'increase': lambda self, num: \
          num + 1  # 很酷的写法,不是么
  })

约等于说:类的概念进程,其实是type类型实例化的经过。

然而这和修饰二个已定义的类有怎样关系啊?

理所当然有啦~既然“类的定义”便是“type类型的先河化进度”,那里面必定会调用到type的构造函数(__new__()
或 __init__())。只要大家后续 type类 并修改其
__new__函数,在那里面动手脚就足以啦。

接下去咱们将通过一个板栗感受 python
的黑魔法,可是以前,大家要先领会三个语法糖。

__metaclass__ 属性

有没觉得上面第二段示例有些鬼畜呢?它勒令程序员将类的积极分子写成多少个字典,俨然是反人类。如若大家的确是要由此改动
元类 来改变 类 的作为的话,就像就无法不运用那种艺术了~~几乎可怕~~

万幸,python 二.二 时引进了3个语法糖:__metaclass__。

class Integer(int):

  __metaclass__ = IntMeta

当今将会等价于:

Integer = IntMeta('Integer', (int, ), {})

由此一来,我们在使用守旧类定义的还要,也足以应用元类啦。

栗子:子类净化器

必要描述

您是三个有语言洁癖的开发者,平日容不得外人讲一句脏话,在支付时也是那般。今后,你写出了四个不行棒的框架,并立刻要将它公之于众了。可是,你的磨牙又犯了:若是你的使用者在代码中写满了脏话,如何做?岂不是玷污了和谐的清白?
若是你正是以此丧心病狂的开发者,你会怎么做?

在知晓元类以前,你大概会无从出手。可是,这些难题你能够用 元类
轻松消除——只要在类定义时过滤掉不到头的单词就好了(百度贴吧的做事~~)。

笔者们的元类看起来会是如此的:

sensitive_words_list = ['asshole', 'fuck', 'shit']

def detect_sensitive_words(string):
  '''检测敏感词汇'''
  words_detected = filter(lambda word: word in string.lower(), sensitive_words_list)

  if words_detected:
    raise NameError('Sensitive words {0} detected in the string "{1}".' \
      .format(
        ', '.join(map(lambda s: '"%s"' % s, words_detected)),
        string
      )
    )

class CleanerMeta(type):

  def __new__(cls, class_name, bases, attrs):
    detect_sensitive_words(class_name) # 检查类名
    map(detect_sensitive_words, attrs.iterkeys()) # 检查属性名

    print "Well done! You are a polite coder!" # 如无异常,输出祝贺消息

    return super(CleanerMeta, cls).__new__(cls, class_name, bases, attrs)
    # 重要!这行一定不能漏!!这回调用内建的类构造器来构造类,否则定义好的类将会变成 None
现在,只需这样定义基类:

class APIBase(object):

  __metaclass__ = CleanerMeta

  # ...
那么所有 APIBase 的派生类都会接受安全审查(奸笑~~):

class ImAGoodBoy(APIBase):

  a_polite_attribute = 1

# [Output] Well done! You are a polite coder!

class FuckMyBoss(APIBase):

  pass

# [Output] NameError: Sensitive words "fuck" detected in the string "FuckMyBoss".

class PretendToBePolite(APIBase):

  def __fuck_your_asshole(self):
    pass

# [Output] NameError: Sensitive words "asshole", "fuck" detected in the string "_PretendToBePolite__fuck_your_asshole".

看,固然像最终1个例子中的私有属性也难逃审查,因为它们本质都以同壹的。

甚至,你还是能对有题指标性质进行私行的改动,比如
让不文明的函数在调用时打出一行警告 等等,这里就不多说了。

元类 在其实支付中的应用

日常花费时,元类 常用吧?

Django的models达成分析。当然,Django 的 O汉兰达M 就是贰个例证,远近盛名的 SQLAlchemy
也用了那种黑魔法。

别的,在一部分微型的库中,也有 元类 的人影。比如
abc(奇怪的名字~~)——那是 python 的多个内建库,用于模拟
抽象基类(Abstract Base Classes)。开发者能够应用 abc.abstractmethod
装饰器,将 钦点了 __metaclass__ = abc.ABCMeta 的类的办法定义成
抽象方法,同时这些类也成了
抽象基类,抽象基类是不可实例化的。那便实现了对 抽象基类 的模仿。

假如你也有亟待动态修改类定义的要求,不要紧也尝试那种“黑魔法”。

小结

  1. 类 也是 对象,全数的类都以type的实例
  2. 元类(Meta Classes)是类的类
  3. __metaclass__ = Meta 是 Meta(name, bases, dict) 的 语法糖
  4. 能够因而重载元类的 __new__ 方法,修改 类定义 的行为

元类是python高阶语法.
合理的行使能够减小大气重复性的代码.

1      引子

1      引子

你也许感兴趣的篇章:

  • Python中的Classes和Metaclasses详解

中 Meta Classes详解,pythonclasses 接触过
Django 的校友都应有特别耳熟能详它的 O昂CoraM 系统。对于 python
新手而言,那是壹项大致能够被称作“黑科…

 

1.1     神奇的Django中的models

笔者们先来看一段在Django项目中常用的代码:

设置数据库models代码:

class Students(models.Model):
    name = models.CharField()
    age = models.IntegerField()

那边有多少个神奇的位置,涉及到了python中最隐衷的多少个特色。

先看下有怎么样神奇的地方:

  • 字段名称name\age自动转换为了数据库中的字段名称
  • 自动校验数据类型,models.Integer菲尔德(),会校验设置的数据类型

此间用的是python的八个语法特性:

  • 讲述符协议
  • 元类

咱俩来一步一步解开神秘面纱。

1.1     神奇的Django中的models

小编们先来看一段在Django项目中常用的代码:

安装数据库models代码:

class Students(models.Model):
    name = models.CharField()
    age = models.IntegerField()

此处有多少个神奇的地点,涉及到了python中最隐衷的多少个特点。

先看下有哪些神奇的地点:

  • 字段名称name\age自动转换为了数据库中的字段名称
  • 自动校验数据类型,models.Integer菲尔德(),会校验设置的数据类型

那边用的是python的八个语法个性:

  • 叙述符协议
  • 元类

我们来一步一步解开神秘面纱。

元类实际上做了以下叁方面包车型地铁劳作:

二      数据校验

二      数据校验

 

贰.一     数据校验难题

Python固然是强类型的脚本语言,可是在概念变量时却无法钦点变量的花色。

比如说,大家在Student类中定义三个age字段,合法值一般为包罗0的正整数,不过在python中无正整数的品类,只可以协调来校验。

class Student:
    def __init__(self, name, age):
        if isinstance(name,str):
            self.name = name
        else:
            raise TypeError("Must be a string")

        if isinstance(int, age):
            self.age = age
        else:
            raise TypeError("Must be an int")

 

可是,即使更大龄时就会遇到难点,不只怕重用校验逻辑。

有未有简短的情势吗?

二.一     数据校验难题

Python就算是强类型的脚本语言,可是在概念变量时却无力回天钦命变量的品种。

诸如,我们在Student类中定义2个age字段,合法值一般为包涵0的正整数,但是在python中无正整数的品类,只好本身来校验。

class Student:
    def __init__(self, name, age):
        if isinstance(name,str):
            self.name = name
        else:
            raise TypeError("Must be a string")

        if isinstance(int, age):
            self.age = age
        else:
            raise TypeError("Must be an int")

 

可是,借使更新岁龄时就会赶上难点,不能够重用校验逻辑。

有没有简短的章程吗?

  • 干预创造类的长河
  • 修改类
  • 再次来到修改以往的类

2.2     使用property装饰器

选拔property也是二个办法,能够针对种种属性来安装,可是假设三个类有四个属性,代码就会卓殊的多,并且爆发大批量的冗余,就好像这么。

bf88必发唯一官网 1bf88必发唯一官网 2

class Student:
    def __init__(self, name, age, class_no, address, phone):
        self._name = None
        self._age = None
        self.__class_no = None
        self._address = None
        self._phone = None

        self.name = name
        self.age = age
        self.class_no = class_no
        self.address = address
        self.phone = phone

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise ValueError("Must be string")
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if isinstance(value, int) and value > 0:
            self._age = value
        else:
            raise ValueError("age value error")

    @property
    def address(self):
        return self._address

    @address.setter
    def address(self, value):
        if not isinstance(value, str):
            raise ValueError("Must be string")
        self._address = value

View Code

 

代码冗余太多,每一个检查str的都要复制贰回代码。

2.2     使用property装饰器

利用property也是三个方法,能够针对各类属性来设置,可是倘使一个类有四个特性,代码就会10分的多,并且发生多量的冗余,就好像这么。

bf88必发唯一官网 3bf88必发唯一官网 4

class Student:
    def __init__(self, name, age, class_no, address, phone):
        self._name = None
        self._age = None
        self.__class_no = None
        self._address = None
        self._phone = None

        self.name = name
        self.age = age
        self.class_no = class_no
        self.address = address
        self.phone = phone

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise ValueError("Must be string")
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if isinstance(value, int) and value > 0:
            self._age = value
        else:
            raise ValueError("age value error")

    @property
    def address(self):
        return self._address

    @address.setter
    def address(self, value):
        if not isinstance(value, str):
            raise ValueError("Must be string")
        self._address = value

View Code

 

代码冗余太多,每一种检查str的都要复制3遍代码。

 

3      Python描述符

叙述符提供了优雅、简洁、健壮和可采取的缓解方案。简单的说,叁个叙述符正是贰个对象,该对象表示了3脾品质的值。

那就代表要是一个Student对象有二脾品质“name”,那么描述符正是另三个能够用来表示属性“name”持有值的目的。

讲述符协议中“定义了__get__”、“__set__”或”__delete__”
这么些特别措施,描述符是完结在那之中三个或多个艺术的指标。

3      Python描述符

叙述符提供了优雅、简洁、健壮和可选取的缓解方案。简单的说,三个叙述符正是二个指标,该目的表示了3个脾气的值。

那就象征一旦四个Student对象有二个属性“name”,那么描述符正是另贰个力所能及用来代表属性“name”持有值的靶子。

叙述符协议中“定义了__get__”、“__set__”或”__delete__”
这几个突出格局,描述符是实现个中1个或多少个格局的靶子。

怎么使用元类?

 

为啥要使用元类那种歪曲且易于出错的效应?
貌似景况下,大家并不会选用元类,9九%的开发者并不会用到元类,所以1般不要思量这一个题材。
元类主用用于创制API,2个一级的事例便是Django的O奥迪Q3M。
它让我们能够那样定义1个类:

 

class Person(models.Model):
  name = models.CharField(max_length=30)
  age = models.IntegerField()

 

运作上边包车型大巴代码:

guy = Person(name='bob', age='35')
print(guy.age)

回去的结果是int花色而不是IntegerField目的。那是因为models.Model选取了元类,它会将Python中定义的字段转换到数据库中的字段。
透过利用元类,Django将复杂的接口转换到简单的接口。

 

原型:type(类名,基类元组(可以为空,用于继承), 包含属性或函数的字典)

 以下二种写法都可以:

type(‘Class’,(object,),dict(hello=fun()))

type(‘Class’,(object,),{“hello”:fun()})

一、class 自定义的类名称

二、(object,)是继承类,的元组,倘若唯有三个就写这种时势(object,);八个(object,xxxx,)

三、dict(hello=fun()) 或 {“hello”:fun()}
第三个参数,是1个字典等号左是
自定义的点子名,左边是已写好的主意名,那一个要注意,有参数且从未默许值的意况下,要加括号;

 

def fun():
    print('hello world!')


if __name__=="__main__":

    h = type('Hello',(object,),dict(hello=fun()))
    tc = h()
    tc.hello

 

引用:

h 也等于收到Hello类;tc
= h()实例化类;tc.hello方法,调用的其实是大家定义的fun方法。

    Hello = type('Hello',(object,),dict(hello=fun()))
    tc = Hello()
    tc.hello

 type()动态创制类后,还足以增加愈来愈多的点子和性质:

def mysql():
    conn = pymysql.connect(host='127.0.0.1',port=3306 ,user='root' ,passwd='q123456' ,db='amsql' )
    cur = conn.cursor()
    sql = "SELECT * FROM amt_case_interface_table"
    ret = cur.execute(sql)
    print(cur.fetchmany(3))
    #conn.commit()

    cur.close()
    conn.close()

Hello.mysql = mysql()

调用:

tc.mysql

 

Linux and
python学习调换1,二群已满.

Linux and
python学习交换三群新开,欢迎出席,一起学习.qq 三群:5632278玖四

不前进,不倒退,截止的处境是绝非的.

bf88必发唯一官网,3只发展,与君共勉,

 

3.1     版本一

 

bf88必发唯一官网 5bf88必发唯一官网 6

 1 class NameProperty:
 2     def __init__(self, name=""):
 3         self.name = name
 4 
 5     def __get__(self, instance, owner):
 6         if instance is None:
 7             return self
 8         return instance.__dict__.get(self.name)
 9 
10     def __set__(self, instance, value):
11         if not isinstance(value, str):
12             raise TypeError("name must be string")
13         instance.__dict__[self.name] = value
14         
15 
16 class Student:
17     name = NameProperty('name')
18     age = None
19     heghth = None
20     weight = None
21 
22     def __init__(self, name):
23         self.name = name
24 
25     def __str__(self):
26         return self.name
27 
28     @property
29     def age(self):
30         return self.age
31 
32     @age.setter
33     def age(self, value):
34         if not isinstance(value, int):
35             raise ValueError("must be int")
36         self.age = value
37 
38 s = Student("Stitch")
39 print(s)
40 s.name = 'name'
41 print(s.name)

View Code

 

本条本子存在八个难点,便是name =
NameProperty(“sss”),必须设置三个称号,才可以使用。这么些与大家运用django的models时不太雷同,在应用models时,不写参数也能够的。

 

3.1     版本一

 

bf88必发唯一官网 7bf88必发唯一官网 8

 1 class NameProperty:
 2     def __init__(self, name=""):
 3         self.name = name
 4 
 5     def __get__(self, instance, owner):
 6         if instance is None:
 7             return self
 8         return instance.__dict__.get(self.name)
 9 
10     def __set__(self, instance, value):
11         if not isinstance(value, str):
12             raise TypeError("name must be string")
13         instance.__dict__[self.name] = value
14         
15 
16 class Student:
17     name = NameProperty('name')
18     age = None
19     heghth = None
20     weight = None
21 
22     def __init__(self, name):
23         self.name = name
24 
25     def __str__(self):
26         return self.name
27 
28     @property
29     def age(self):
30         return self.age
31 
32     @age.setter
33     def age(self, value):
34         if not isinstance(value, int):
35             raise ValueError("must be int")
36         self.age = value
37 
38 s = Student("Stitch")
39 print(s)
40 s.name = 'name'
41 print(s.name)

View Code

 

那个版本存在一个题材,正是name =
NameProperty(“sss”),必须安装二个名号,才得以应用。这几个与我们应用django的models时不太1样,在利用models时,不写参数也能够的。

 

3.2     版本二

不用输入变量名称。

bf88必发唯一官网 9bf88必发唯一官网 10

class NameProperty:
    index = 0

    def __init__(self):
        self.name = str(self.__class__.index)  # 使用类的变量
        self.__class__.index += 1

    def __get__(self, instance, owner):
        return getattr(instance, self.name)

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError("name must be string")
        instance.__dict__[self.name] = value


class Student:
    name = NameProperty()
    age = None

    def __str__(self):
        return self.name

s = Student()
s.name = "www"
print(s)

s2 = Student()
s2.name = "http"
print(s2)
print(s.name)

View Code

 

其壹本子还存在一个题材,假设叁个品类有七个字段使用了NameProperty时,错误提示时,无法代表出此变量的名称,只可以表示出三个index值。用户看到这几个时,不可能断定是可怜变量出了难题。

 

3.2     版本二

不用输入变量名称。

bf88必发唯一官网 11bf88必发唯一官网 12

class NameProperty:
    index = 0

    def __init__(self):
        self.name = str(self.__class__.index)  # 使用类的变量
        self.__class__.index += 1

    def __get__(self, instance, owner):
        return getattr(instance, self.name)

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError("name must be string")
        instance.__dict__[self.name] = value


class Student:
    name = NameProperty()
    age = None

    def __str__(self):
        return self.name

s = Student()
s.name = "www"
print(s)

s2 = Student()
s2.name = "http"
print(s2)
print(s.name)

View Code

 

这么些版本还设有一个题材,假诺一个项目有多个字段使用了NameProperty时,错误指示时,不或然表示出此变量的称谓,只可以表示出多个index值。用户观望那几个时,不能够判断是非凡变量出了难题。

 

4      使用元类

元类是python的中三个难处,在大部情景下都不会用到。但是在编辑框架方面却是必不可贫乏的利器。

4      使用元类

元类是python的中叁个困难,在超过三分之一气象下都不会用到。可是在编排框架方面却是不能缺少乏的利器。

4.1     版本三

利用元类来控制类的行为:

bf88必发唯一官网 13bf88必发唯一官网 14

class NameProperty:
    index = 0

    def __init__(self):
        self.storage_name = str(self.__class__.index)  # 使用类的变量
        self.__class__.index += 1

    def __get__(self, instance, owner):
        return getattr(instance, self.storage_name)

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError("%s must be string" % self.storage_name)
        instance.__dict__[self.storage_name] = value


class EntityMeta(type):
    def __init__(cls, name, bases, attr_dict):
        super().__init__(name, bases, attr_dict)
        for key, attr in attr_dict.items():
            if isinstance(attr, NameProperty):
                type_name = type(attr).__name__
                attr.storage_name = '{} property {}'.format(type_name, key)


class Student(metaclass=EntityMeta):
    name = NameProperty()
    age = None
    nicky_name = NameProperty()

    def __str__(self):
        return self.name

s = Student()
s.name = "www"
print(s)

s2 = Student()
s2.name = "test"
s2.nicky_name = 4444
print(s2)
print(s2.nicky_name)

View Code

 

执行输出为:

 

raise TypeError("%s must be string" % self.storage_name)

TypeError: NameProperty property nicky_name must be st

 

语法解释:

本子三相比较版本二,最大的变更在于Student类继承了自定义元类EntityMeta。

要是对于python面向对象编程有询问的话,python的富有类都继承自type,type是全数类的元类。。

在此间,大家自定义的元类EntityMeta,具备2个意义正是判断类属性是或不是为NameProperty类型,假若为这么些项目,则那些项目标实例属性storage_name值赋值为类名和属性名

 

4.1     版本三

使用元类来控制类的一坐一起:

bf88必发唯一官网 15bf88必发唯一官网 16

class NameProperty:
    index = 0

    def __init__(self):
        self.storage_name = str(self.__class__.index)  # 使用类的变量
        self.__class__.index += 1

    def __get__(self, instance, owner):
        return getattr(instance, self.storage_name)

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError("%s must be string" % self.storage_name)
        instance.__dict__[self.storage_name] = value


class EntityMeta(type):
    def __init__(cls, name, bases, attr_dict):
        super().__init__(name, bases, attr_dict)
        for key, attr in attr_dict.items():
            if isinstance(attr, NameProperty):
                type_name = type(attr).__name__
                attr.storage_name = '{} property {}'.format(type_name, key)


class Student(metaclass=EntityMeta):
    name = NameProperty()
    age = None
    nicky_name = NameProperty()

    def __str__(self):
        return self.name

s = Student()
s.name = "www"
print(s)

s2 = Student()
s2.name = "test"
s2.nicky_name = 4444
print(s2)
print(s2.nicky_name)

View Code

 

实施输出为:

 

raise TypeError("%s must be string" % self.storage_name)

TypeError: NameProperty property nicky_name must be st

 

语法解释:

本子叁比照版本二,最大的更动在于Student类继承了自定义元类EntityMeta。

假使对于python面向对象编制程序有打探的话,python的具有类都继承自type,type是全体类的元类。。

在这里,大家自定义的元类EntityMeta,具备二个意义正是判断类属性是还是不是为NameProperty类型,固然为那些连串,则那个类别的实例属性storage_name值赋值为类名和属性名

 

4.2     版本四—模仿django的models

模仿Django的models实现:

bf88必发唯一官网 17bf88必发唯一官网 18

import abc

class NameProperty:
    index = 0

    def __init__(self):
        self.storage_name = str(self.__class__.index)  # 使用类的变量
        self.__class__.index += 1

    def __get__(self, instance, owner):
        return getattr(instance, self.storage_name)

    def __set__(self, instance, value):
        # instance.__dict__[self.storage_name] = value
        setattr(instance, self.storage_name, value)


class Validated(abc.ABC, NameProperty):
    def __set__(self, instance, value):
        value = self.validate(instance, value)
        super().__set__(instance, value)

    @abc.abstractclassmethod
    def validate(self, instance, value):
        """return validated value or raise ValueError"""


class ChartField(Validated):
    def validate(self, instance, value):
        if not isinstance(value, str):
            raise TypeError("{} must be str".format(self.storage_name))
        return value


class IntegerField(Validated):
    def __init__(self, min_value=None):
        self.min_value = min_value

    def validate(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("{} must be int".format(self.storage_name))
        if self.min_value and value < self.min_value:
            raise ValueError("{} must larger min_value".format(self.storage_name))
        return value


class EntityMeta(type):
    def __init__(cls, name, bases, attr_dict):
        super().__init__(name, bases, attr_dict)
        for key, attr in attr_dict.items():
            if isinstance(attr, Validated):
                type_name = type(attr).__name__
                attr.storage_name = "{} property {}".format(type_name, key)


class Entity(metaclass=EntityMeta):
    pass


class Student(Entity):
    name = ChartField()
    age = IntegerField(min_value=0)
    nicky_name = ChartField()

    def __init__(self, name, age, nicky_name):
        self.name = name
        self.age = age
        self.nicky_name = nicky_name

    def __str__(self):
        return self.name

s2 = Student("test", 12, "toddy")
s2.age = -1
print(s2.nicky_name)
s2.nicky_name = 4444

View Code

 

施行结果:

 

raise ValueError("{} must larger min_value".format(self.storage_name))

ValueError: IntegerField property age must larger min_value 

 

 

诸如此类,完全效仿了models的概念。

类的初始化和两次三番属性赋值,都会自行调用__set__来设置并校验。

 

4.2     版本四—模仿django的models

模仿Django的models实现:

bf88必发唯一官网 19bf88必发唯一官网 20

import abc

class NameProperty:
    index = 0

    def __init__(self):
        self.storage_name = str(self.__class__.index)  # 使用类的变量
        self.__class__.index += 1

    def __get__(self, instance, owner):
        return getattr(instance, self.storage_name)

    def __set__(self, instance, value):
        # instance.__dict__[self.storage_name] = value
        setattr(instance, self.storage_name, value)


class Validated(abc.ABC, NameProperty):
    def __set__(self, instance, value):
        value = self.validate(instance, value)
        super().__set__(instance, value)

    @abc.abstractclassmethod
    def validate(self, instance, value):
        """return validated value or raise ValueError"""


class ChartField(Validated):
    def validate(self, instance, value):
        if not isinstance(value, str):
            raise TypeError("{} must be str".format(self.storage_name))
        return value


class IntegerField(Validated):
    def __init__(self, min_value=None):
        self.min_value = min_value

    def validate(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("{} must be int".format(self.storage_name))
        if self.min_value and value < self.min_value:
            raise ValueError("{} must larger min_value".format(self.storage_name))
        return value


class EntityMeta(type):
    def __init__(cls, name, bases, attr_dict):
        super().__init__(name, bases, attr_dict)
        for key, attr in attr_dict.items():
            if isinstance(attr, Validated):
                type_name = type(attr).__name__
                attr.storage_name = "{} property {}".format(type_name, key)


class Entity(metaclass=EntityMeta):
    pass


class Student(Entity):
    name = ChartField()
    age = IntegerField(min_value=0)
    nicky_name = ChartField()

    def __init__(self, name, age, nicky_name):
        self.name = name
        self.age = age
        self.nicky_name = nicky_name

    def __str__(self):
        return self.name

s2 = Student("test", 12, "toddy")
s2.age = -1
print(s2.nicky_name)
s2.nicky_name = 4444

View Code

 

实施结果:

 

raise ValueError("{} must larger min_value".format(self.storage_name))

ValueError: IntegerField property age must larger min_value 

 

 

这么,完全效仿了models的概念。

类的开头化和继承属性赋值,都会活动调用__set__来设置并校验。

 

伍      原明白释

五      原精晓释

5.一     属性读取顺序

经超过实际例读取属性时,常常再次回到的是实例中定义的习性。读取顺序如下:          

  1. 实例属性
  2. 类属性
  3. 父类属性
  4. __getattr__()方法

先记住那一个顺序,前面了然描述要求。属性描述符都以概念在类中的,而不是在对象中。

伍.一     属性读取顺序

由此实例读取属性时,平时重返的是实例中定义的性质。读取顺序如下:          

  1. 实例属性
  2. 类属性
  3. 父类属性
  4. __getattr__()方法

先记住这几个顺序,前边理解描述供给。属性描述符都以概念在类中的,而不是在对象中。

5.2     描述符

有些类,只即便里面定义了主意 __get__, __set__, __delete__
中的二个或四个(set,delete必须有3个),就能够称之为描述符。

主意的原型为:

  ① __get__(self, instance, owner)

  ② __set__(self, instance, value)

  ③ __del__(self, instance)

 

叙述符只绑定到类上,在实例上不奏效。

讲述的调用实质为:type(objectA).__dict__[“key”].__get__(None,
objectB),objectB为描述符,objectA为定义类。

5.2     描述符

有些类,只就算内部定义了措施 __get__, __set__, __delete__
中的二个或几个(set,delete必须有三个),就足以称为描述符。

办法的原型为:

  ① __get__(self, instance, owner)

  ② __set__(self, instance, value)

  ③ __del__(self, instance)

 

讲述符只绑定到类上,在实例上不见效。

讲述的调用实质为:type(objectA).__dict__[“key”].__get__(None,
objectB),objectB为描述符,objectA为定义类。

5.3     元类

元类,正是创设类的类。一般类都持续自object类,私下认可会创立①些方法。

元类决定了类出初叶化后有哪些特点和行为。假诺我们想自定义1个类,具备某种特殊的行为,则需求自定义元类。

  • 类也是指标,全数的类都以type的实例
  • 元类(Meta Classes)是类的类
  • __metaclass__ = Meta 是 Meta(name, bases, dict) 的语法糖
  • 能够经过重载元类的 __new__ 方法,修改定义的作为

 

5.3     元类

元类,便是创造类的类。一般类都一而再自object类,暗中同意会创设1些情势。

元类决定了类出早先化后有怎么着特色和表现。借使大家想自定义多少个类,具备某种特殊的一言一动,则需求自定义元类。

  • 类也是指标,全部的类都以type的实例
  • 元类(Meta Classes)是类的类
  • __metaclass__ = Meta 是 Meta(name, bases, dict) 的语法糖
  • 能够透过重载元类的 __new__ 方法,修改定义的一举一动

 

六  别的案例

Django的django-rest-framework框架的serializer 也是用的那几个语法完结的。

6  别的案例

Django的django-rest-framework框架的serializer 也是用的这一个语法实现的。

柒      参考资料

编号

标题

链接

1

元类

https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

2

描述符

解密 Python 的描述符(descriptor)

3

《流畅的python》

元类部分

柒      参考资料

编号

标题

链接

1

元类

https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

2

描述符

解密 Python 的描述符(descriptor)

3

《流畅的python》

元类部分

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图