微信小程序> 微信小程序meta标签-【python进阶笔记】元类Metacasses、实现ORM-小程序标签

微信小程序meta标签-【python进阶笔记】元类Metacasses、实现ORM-小程序标签

浏览量:1807 时间: 来源:在下能猫

1.【python进阶笔记】元类Metaclasses、实现ORM

目录

1.元类

1.1.类也是对象

1.2.动态的创建类

1.3.使用type创建类

1.4.使用type创建复杂的类

1.4.1.type创建带有继承的类

1.4.2.添加实例方法

1.4.3.添加静态方法

1.4.4.添加类方法

1.4.5.较为完整的使用type创建类

1.5.元类定义

1.6.__metaclass__属性

1.7.元类应用

1.8.更新:元类中的__call__方法

注:关于元类中的new、init、call

1.9.元类的使用场景

2.元类实现ORM(ObjectRelationalMapping)

2.1.ORM是什么

2.2.通过元类简单实现ORM中的insert功能

1.元类

2.元类就是用来创建类的“东西”,元类创建类,类创建实例对象。

1.1.类也是对象在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:

3.以下在ipython3中,通过类生成了一个对象并输出:

classObjectCreator(object):…pass…my_object=ObjectCreator()print(my_object)__main__.ObjectCreatorobjectat0x8974f2c

但是,Python中的类还远不止如此。类同样也是一种对象。只要使用关键字class,Python解释器在执行的时候就会自动创建一个对象。

执行以下代码段:

classObjectCreator(object):pass

4.python将在内存中自动创建一个对象,名字就是ObjectCreator。

5.这个对象(类对象ObjectCreator)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,可以对它做针对对象的操作:

赋值给一个变量拷贝它为它增加属性作为函数参数进行传递

6.注:

globals()函数会以字典类型返回当前位置的全部全局变量,包括所有全局对象的引用。即返回的字典会包含了python定义好的变量,也包含了自己定义的变量,可以直接使用。想查看一个类或模块里面有什么,可以使用类.__dict__或模块.__dict__。globals()的返回有一个内建模块__builtins__,里面包含了常用的print等对象(__builtins__.__dict__可见)。用一个变量名或函数名时,先直接到globals()返回的字典里面找,找不到再到字典的内建模块__builtins__去找。1.2.动态的创建类

7.因为类也是对象,可以在运行时动态的创建它们,就像其他任何对象一样。可以在函数中创建类,使用class关键字。

defchoose_class(name):ifname=='foo':classFoo(object):passreturnFoo#返回的是类,不是类的实例else:classBar(object):passreturnBarMyClass=choose_class('foo')print(MyClass)#函数返回的是类,不是类的实例print(MyClass())#你可以通过这个类创建类实例,也就是对象#输出:#class'__main__.choose_class.locals.Foo'#__main__.choose_class.locals.Fooobjectat0x7fa8b5ac2f98

8.根据传递的参数不一样,返回的类的引用不一样,然后导致创建的实例对象不一样。但这还不够动态,因为仍然需要自己编写整个类的代码。可以使用type动态的创建类。(注:type函数功能之一能够返回一个对象的类型,如print(type(1))会打印type'int')

1.3.使用type创建类type还有一种完全不同的功能,即动态的创建类。函数type实际上是一个元类。

type可以接受一个类的描述作为参数,然后返回一个类对象。

9.type(类名,由元组组成的父类名称(针对继承的情况,可以为空),包含属性的字典(名称和值))

10.以下分别用两种方法创建了类Test1和类Test2:

In[13]:classTest:....:num=100....:num2=200....:In[14]:Test2=type("Test2",(),{"num":100,"num2":200})

11.使用help来测试这2个类:

In[16]:help(Test)#用help查看Test类HelponclassTestinmodule__main__:classTest(builtins.object)|Datadescriptorsdefinedhere:||__dict__|dictionaryforinstancevariables(ifdefined)||__weakref__|listofweakreferencestotheobject(ifdefined)||----------------------------------------------------------------------|Dataandotherattributesdefinedhere:||num=100||num2=200In[18]:help(Test2)HelponclassTest2inmodule__main__:classTest2(builtins.object)|Datadescriptorsdefinedhere:||__dict__|dictionaryforinstancevariables(ifdefined)||__weakref__|listofweakreferencestotheobject(ifdefined)||----------------------------------------------------------------------|Dataandotherattributesdefinedhere:||num=100||num2=200

12.可见,二者是一样的。

1.4.使用type创建复杂的类1.4.1.type创建带有继承的类

13.Test11继承于Test,Test22继承于Test2.

In[1]:classTest:num=100num2=200...:In[2]:Test2=type("Test2",(),{"num":100,"num2":200})In[3]:classTest11(Test):...:pass...:In[4]:Test22=type("Test22",(Test2,),{})

14.再使用help查看,两个类是一样的。

1.4.2.添加实例方法In[15]:classFoo(object):....:bar=True....:In[16]:defecho_bar(self):#定义了一个普通的函数....:print(self.bar)....:In[17]:FooChild=type('FooChild',(Foo,),{'echo_bar':echo_bar})#让FooChild类中的echo_bar属性,指向了上面定义的函数In[18]:hasattr(Foo,'echo_bar')#判断Foo类中是否有echo_bar这个属性Out[18]:FalseIn[19]:hasattr(FooChild,'echo_bar')#判断FooChild类中是否有echo_bar这个属性Out[19]:TrueIn[20]:my_foo=FooChild()In[21]:my_foo.echo_bar()True1.4.3.添加静态方法

15.在上面代码的基础上

In[36]:@staticmethod...:deftest_static():...:print("staticmethod....")...:In[37]:Foochild=type('Foochild',(Foo,),{"echo_bar":echo_bar,"test_static":test_static})In[38]:fooclid=Foochild()In[39]:fooclid.test_staticOut[39]:function__main__.test_staticIn[40]:fooclid.test_static()staticmethod....In[41]:fooclid.echo_bar()True1.4.4.添加类方法

16.在上面代码的基础上

In[42]:@classmethod...:deftest_class(cls):...:print(cls.bar)...:In[43]:Foochild=type('Foochild',(Foo,),{"echo_bar":echo_bar,"test_static":test_static,"test_class":test_class})In[44]:fooclid=Foochild()In[45]:fooclid.test_class()True1.4.5.较为完整的使用type创建类classA(object):num=100defprint_b(self):print(self.num)@staticmethoddefprint_static():print("----haha-----")@classmethoddefprint_class(cls):print(cls.num)#B继承于A,B=type("B",(A,),{"print_b":print_b,"print_static":print_static,"print_class":print_class})b=B()b.print_b()b.print_static()b.print_class()#结果#100#----haha-----#1001.5.元类定义元类就是用来创建类的“东西”。创建类是为了创建类的实例对象。但是Python中的类也是对象,元类就是用来创建这些类(对象)的,元类就是类的类。即元类创建类,类创建实例对象。MyClass=MetaClass()#使用元类创建出一个对象,这个对象称为“类”my_object=MyClass()#使用“类”来创建出实例对象

17.前面用到的函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。

type就是创建类对象的类。可以通过检查__class__属性来验证这一点。(注:实例调用__class__属性时会指向该实例对应的类(即那个类创建了该实例),还可以再去调用其它类属性)

Python中所有的东西,都是对象。包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。In[24]:classT(object):....:pass....:In[25]:t=T()In[26]:t.__class__Out[26]:__main__.T#以上可见t由当前模块的T创建In[27]:t.__class__.__class__Out[27]:type#以上可见T由type创建(注:type还是type创建的)1.6.__metaclass__属性

18.定义一个类时,可以为其添加__metaclass__属性。定义了__metaclass__就定义了这个类的元类。

classFoo(object):#python2__metaclass__=something…...省略...classFoo(metaclass=something):#python3__metaclass__=something…

19.这样,Python就会用元类来创建类Foo。执行到语句classFoo(object):时,类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。

20.如,以下代码:

classFoo(Bar):pass

21.Python做了如下的操作:

Foo中有__metaclass__这个属性吗?如果是,Python会通过__metaclass__创建一个名字为Foo的类(对象)如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

22.问:在__metaclass__中放置些什么代码呢?

23.答:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东东都可以。

1.7.元类应用

24.元类的主要目的就是为了当创建类时能够自动地改变类。

25.创建类时,metaclass用来标记元类是谁,不写metaclass会默认调用type来创建类。

26.元类所做的事情概括起来就是:

拦截类的创建修改类返回修改之后的类

27.demo1:

假设,你决定在你的模块里所有的类的属性都应该是大写形式。方法之一就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。

例子如下,以下也相当于自定义了一个元类。

#-*-coding:utf-8-*-defupper_attr(class_name,class_parents,class_attr):#遍历属性字典,把不是__开头的属性名字变为大写new_attr={}forname,valueinclass_attr.items():ifnotname.startswith("__"):#不是下划线开头时new_attr[name.upper()]=value#调用type来创建一个类returntype(class_name,class_parents,new_attr)classFoo(object,metaclass=upper_attr):bar='bip'print(hasattr(Foo,'bar'))print(hasattr(Foo,'BAR'))f=Foo()print(f.BAR)

28.以上,类名Foo传给了class_name,父类object传给了class_parents,把类内部的内容以字典的方式传给了class_attr。把传过来的字典里的key改为大写再传给type并返回。

上面传的upper_attr是一个函数,而不是一个类,并不十分符合元类这个名称。下面Demo2用一个真正的class来当做元类.

29.Demo2:

#coding=utf-8classUpperAttrMetaClass(type):#继承了type类的子类,相当于是个元类#__new__是在__init__之前被调用的特殊方法#__new__是用来创建对象并返回之的方法#而__init__只是用来将传入的参数初始化给对象#你很少用到__new__,除非你希望能够控制对象的创建#这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__#如果你希望的话,你也可以在__init__中做些事情#还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用def__new__(cls,class_name,class_parents,class_attr):#遍历属性字典,把不是__开头的属性名字变为大写new_attr={}forname,valueinclass_attr.items():ifnotname.startswith("__"):new_attr[name.upper()]=value#方法1:通过'type'来做类对象的创建returntype(class_name,class_parents,new_attr)#方法2:复用type.__new__方法#这就是基本的OOP编程,没什么魔法#returntype.__new__(cls,class_name,class_parents,new_attr)#python3的用法classFoo(object,metaclass=UpperAttrMetaClass):bar='bip'#python2的用法#classFoo(object):#__metaclass__=UpperAttrMetaClass#bar='bip'print(hasattr(Foo,'bar'))#输出:Falseprint(hasattr(Foo,'BAR'))#输出:Truef=Foo()print(f.BAR)#输出:'bip'

30.思考:实现定义类时,无论是否写上object,都会继承object,该如何实现?

1.8.更新:元类中的__call__方法注:关于元类中的new、init、call

31.在元类中:

32.创建的对象是类,如果希望能够自定义它,一般改写__new__;如果有需要的话,也可以在__init__中做些事情;一些高级的用法会涉及到改写__call__特殊方法。

1.9.元类的使用场景若定义好了函数,需要给函数添加功能时,可以使用装饰器;若定义好了类,需要对类的功能进行修改,可以使用元类;

33.Python界的领袖TimPeters说过,“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”

2.元类实现ORM(ObjectRelationalMapping)2.1.ORM是什么ORM(ObjectRelationalMapping),即对象-关系-映射,是python编程语言后端web框架Django的核心思想,用其操作数据库时可以不需要再用SQL语句。

一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应MySQL语句。

34.说明:

所谓的ORM就是让开发者在操作数据库的时候,能够像操作对象时通过xxxx.属性=yyyy一样简单,这是开发ORM的初衷只不过ORM的实现较为复杂,Django中已经实现了很复杂的操作。本节知识主要通过完成一个insert相类似的ORM,理解其中的道理就就可以了classUser(父类省略):uid=('uid',"intunsigned")name=('username',"varchar(30)")email=('email',"varchar(30)")password=('password',"varchar(30)")...省略...u=User(uid=12345,name='Michael',email='test@orm.org',password='my-pwd')u.save()#对应如下sql语句#insertintoUser(username,email,password,uid)#values('Michael','test@orm.org','my-pwd',12345)以上Demo的思想,定义一个类,类名对应于表名,类属性相关的信息对应着每一个字段。

通过创建实例对象,然后创建实例对象的save方法,来对表进行操作。通过操作实例对象,自动转换成操纵SQL语句,而不用写SQL语句。

2.2.通过元类简单实现ORM中的insert功能

35.Demo:

定义了一个元类ModelMetaclass(继承自type)。定义了一个User类,有四个类属性、__init__方法和save方法。User类使用了metaclass指定了ModelMetaclass,所以不会用默认的type去创建类,而是使用指明ModelMetaclass。会调用ModelMetaclass的__new__方法(类名User传给name、空元组传给bases、类属性作为字典传给attrs),最后返回type.__new__。u=User(uid=12345,name='Michael',email='test@orm.org',password='my-pwd')执行时,User的__init__方法会被调用;最后调用save()方法:self指向实例对象...classModelMetaclass(type):def__new__(cls,name,bases,attrs):mappings=dict()#判断是否需要保存forkey,valuesinattrs.items():#注:字典(Dictionary)items()函数以列表返回可遍历的(键,值)元组数组#判断是否是指定的StringField或者IntegerField的实例对象ifisinstance(values,tuple):#判断values是否是元组print('Foundmapping:%s==%s'%(key,values))mappings[key]=values#循环结束时传进来的类属性保存到了字典mappings#删除这些已经在字典中存储的属性forkeyinmappings.keys():attrs.pop(key)#将之前的uid/name/email/password以及对应的对象引用、类名字attrs['__mappings__']=mappings#保存属性和列的映射关系attrs['__table__']=name#假设表名和类名一致returntype.__new__(cls,name,bases,attrs)classUser(metaclass=ModelMetaclass):uid=('uid',"intunsigned")#!!注意:在Django中等号右边是对象,而非元祖name=('username',"varchar(30)")email=('email',"varchar(30)")password=('password',"varchar(30)")#当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储#以上User类中有(即最终User类的类属性对应为下面的样子)#__mappings__={#"uid":('uid',"intunsigned")#"name":('username',"varchar(30)")#"email":('email',"varchar(30)")#"password":('password',"varchar(30)")#}#__table__="User"def__init__(self,**keywargs):forname,valueinkeywargs.items():setattr(self,name,value)#setattr往self指向的对象添加一个属性。defsave(self):fields=[]args=[]forkey,valuesinself.__mappings__.items():#注:实例对象没有__mappings__,但是类对象里面有fields.append(values[0])#取元组values第一个值添加(如:第一次循环取元组('uid',"intunsigned")的uid)args.append(getattr(self,key,None))#getattr取对象的key的值,添加到列表argssql='insertinto%s(%s)values(%s)'%(self.__table__,','.join(fields),','.join([str(i)foriinargs]))print()print('SQL:%s'%sql)u=User(uid=12345,name='Michael',email='test@orm.org',password='my-pwd')#print(u.__dict__)u.save()

36.输出:

Foundmapping:email==('email','varchar(30)')Foundmapping:password==('password','varchar(30)')Foundmapping:uid==('uid','intunsigned')Foundmapping:name==('username','varchar(30)')SQL:insertintoUser(email,username,uid,password)values(test@orm.org,Michael,12345,my-pwd)

37.注意看,生成的SQL语句是有问题的:...values(test@orm.org,Michael,12345,my-pwd),括号内有些值应该加上引号。

38.改进版本:

39.将语句

sql='insertinto%s(%s)values(%s)'%(self.__table__,','.join(fields),','.join([str(i)foriinargs]))

40.改成

args_temp=list()fortempinargs:#判断入如果是数字类型ifisinstance(temp,int):args_temp.append(str(temp))elifisinstance(temp,str):args_temp.append("""'%s'"""%temp)sql='insertinto%s(%s)values(%s)'%(self.__table__,','.join(fields),','.join(args_temp))#例如,经过以上["12345","""'xxxx'""","""'xxx@xxx.com'""","""'567'"""]就会变成12345,'xxxx','xxx@xxx.com','567'

41.最后一行输出变为:

SQL:insertintoUser(email,username,password,uid)values('test@orm.org','Michael','my-pwd',12345)

42.扩展:

字典(Dictionary)的items()函数以列表返回可遍历的(键,值)元组数组。setattr(object,name,value)函数对应函数getattr(),用于设置属性值,该属性不一定是存在的。往self指向的对象添加一个属性。object--对象。name--字符串,对象属性。value--属性值。getattr()函数用于返回一个对象属性值。getattr(object,name[,default])object--对象。name--字符串,对象属性。default--默认返回值,如果不提供该参数,在没有对应属性时,将触发AttributeErrorjoin()方法用于将序列中的元素以指定的字符连接生成一个新的字符串。返回通过指定字符连接序列中元素后生成的新字符串。str.join(sequence)str--指定的字符sequence--要连接的元素序列。

43.最后一句,ORM做到了操作对象,就相当于操作数据表。

44.-----end-----

版权声明

即速应用倡导尊重与保护知识产权。如发现本站文章存在版权问题,烦请提供版权疑问、身份证明、版权证明、联系方式等发邮件至197452366@qq.com ,我们将及时处理。本站文章仅作分享交流用途,作者观点不等同于即速应用观点。用户与作者的任何交易与本站无关,请知悉。

产品经理

手机 : 13312967497

擅长 : 小程序流量变现

扫码领取礼包

最新资讯

热门模板

  • 头条
  • 搜狐
  • 微博
  • 百家
  • 一点资讯
  • 知乎