前言:
1、对象可以比作人【(会某些技能,具有某些属性(特征)】。
2、每个对象都有不同的属性(特征),需要用__init__去定义这些属性(特征)。
3、类可以比作一群人(他们有相似的技能或者相似的特征)。
4、先定义类,然后调用类(实例化)产生对象。
5、"类" 具有数据属性(所有对象共享)和函数属性(主要是给对象使用的,并且是绑定到对象的)。
创建类的2中方式:
# 方式一
class Foo(object):
CITY = "bj"
def func(self,x):
return x+1
# 方式二
Coo=type("Coo",(object,),{"CITY":"bj","func":lambda self,x:x+1})
一、前言:面向过程编程 ---针对扩展性要求不高的程序,可使用面向过程编程
1、什么是面向过程编程?
核心是过程二字,过程指的是解决问题的步骤,即先干什么,在干什么,在干什么。。。。
基于面向过程的思想,编写程序就好比在设计一条流水线,是一种机械式思维方式。
优点:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
缺点:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向过程编程小例子:把要做的事情拆分为一件件小的事情,然后完成。
def login():
uname=input('pls input uname>>:').strip()
pwd=input('pls input pwd>>:').strip()
return uname,pwd
def auth(uname,pwd):
if uname == 'szq' and pwd == '123':
return True,uname
else:
return False,uname
def index():
if res[0]:
print('欢迎%s登录' %res[1])
else:
print('%s登录失败' % res[1])
uname,pwd=login()
res=auth(uname,pwd)
index()
二、面向对象编程 ---用在扩展性强的程序上(与用户交互上)
1、什么是面向对象编程?
核心是 "对象" 二字,对象指的是:特征(指变量)与技能(指函数)的结合体。
基于面向对象的思想,编写程序就好比在创造一个世界,程序员就是这个世界的上帝,因此一切存在的事物皆对象,任何不存在的对象也都可以造出来。
优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:
1. 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
2. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
三、类
类:是一系列对象相似的特征与技能的结合体
在程序中:一定要先定义类,后调用类 (产生对象)
例子:
对象1:学生
特征
school='Oldboy'
name='李泰迪'
sex='male'
age=18
技能
选课
对象2:学生
特征
school='Oldboy'
name='张随便'
sex='male'
age=38
技能
选课
对象3:老师
特征
name='Egon'
sex='male'
age=18
level=10
技能
打分
点名
总结:
学生之间相似的特征:school='Oldboy'
学生之间相似的技能:选课
3.1、类的定义 -->类在定义阶段就会执行代码,会产生类的名称空间
class OldboyStudent: #类并不是一个真正存在的东西,它是一个抽象的概念(这里表示school='Oldboy')
# 公共的特征: 变量
school='Oldboy'
# 技能: 函数
def choose_course(self):
print('is choose_course')
print(OldboyStudent.__dict__) #打印类定义的名称空间
print(OldboyStudent.__dict__['school']) #取名称空间内的"school"对应的值
# >>:Oldboy
print(OldboyStudent.school) #本质就是取"OldboyStudent.__dict__['school']"的值(OldboyStudent名称空间内"school"的值)
# >>:Oldboy
OldboyStudent.choose_course(123) #调用类的容器"choose_course",由于choose_course本身是一个函数,那么就按照函数的写法来调用
# >>:is choose_course
3.2、类的用法:增,删,改,查
'''类的用法:增删改查'''
OldboyStudent.country='China' #增加一条数据
OldboyStudent.school='Oldgirl' #改数据,把'Oldboy'修改为'Oldgirl'
print(OldboyStudent.__dict__) #查看数据
del OldboyStudent.school #在类(一个容器)里面把school给删掉
print(OldboyStudent.__dict__)
总结类的用途:2种
1、通过调用类(实例化),可以产生对象。
2、类本身就是一个容器,里面有很多名称空间定义好的名称,可以直接取。
3.3、python为类内置的特殊属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
3.4、python获取class名字的方法(1 是在类里面获取,2 是通过类实例化的对象获取)
一、查看类的名字,直接调用 class.__name__ 方法
class Parent(object):
def __init__(self, name):
self.name = name
print(Parent.__name__)
>: Parent
二、通过对象查找”对象所对应类“的名字,使用 self.__class__.__name__ 方法
class Parent(object):
def __init__(self, name):
self.name = name
res= Parent("szq")
print(res.__class__.__name__)
>: Parent
四、对象
object=class()] -->对象独有的特征一定会放在对象自己的名称空间内。对象共有的特征可以放在类的名称空间中。
class OldboyStudent:
country='China' #这个相关于对象的公共特征(即每个对象都有这个特征)
def __init__(self,name,sex,age): #"__init__"是一个内置方法(在满足某种条件下自动触发),用来定义对象有哪些属性(其中self表示对象,对象会自动当做第一个值传入)
self.name=name #特征1
self.sex=sex #特征2
self.age=age #特征3
def learn(self): #定义对象的技能
print('学习')
def eat(self): #定义对象的技能
print('吃饭')
def sleep(self): #定义对象的技能
print('睡觉')
# 在实例化时,自动触发__init__(s1,'张三','male','18')[将对象以及对象要传入的值一起传给__init__]
s1=OldboyStudent('张三','male','18') # 调用类(实例化)产生对象
s2=OldboyStudent('李四','female','28') # 调用类(实例化)产生对象
s3=OldboyStudent('王二','male','38') # 调用类(实例化)产生对象
print(s1.__dict__) #打印对象s1的名称空间,拿到名称空间的值。
# {'name': '张三', 'sex': 'male', 'age': '18'}
print(s2.__dict__)
# {'name': '李四', 'sex': 'female', 'age': '28'}
print(s3.__dict__)
# {'name': '王二', 'sex': 'male', 'age': '38'}
总结对象的用途:
1、对象的属性查找是:先从对象本身的名称空间找,找不到则取类的名称空间中查找,两者都找不到则报错。
4.2、修改对象名称空间的值,与修改类名称空间的值
#一、先定义类
class OldboyStudent:
school='Oldboy'
def __init__(self, x, y, z):
self.name = x
self.sex = y
self.age = z
def choose_course(self):
print('is choose_course')
#二、后调用类
stu1=OldboyStudent('李泰迪','male',18)
stu2=OldboyStudent('张随便','famale',38)
# 修改对象下的名称空间的值只针对对象本身的名称空间生效,而不会影响到其他对象。
stu1.school='US'
print(stu1.school)
>>:US
print(stu2.school)
>>:Oldboy
# 修改类下的名称空间的值,那么引用该类的对象的名称空间的值都会被修改。
OldboyStudent.school='US'
print(stu1.school)
>>:US
print(stu2.school)
>>:US
4.3、对象绑定方法的使用
绑定方法的特殊点:
1、绑定给谁就由谁来调用
2、谁来调用,就会将谁当做第一个参数自动传入
class OldboyStudent:
school='Oldboy'
def __init__(self, x, y, z):
self.name = x
self.sex = y
self.age = z
def choose_course(self):
print('%s is choose_course' %self.name) # 新增self.name
def eat(self):
print('%s is eating' %self.name) # 新增self.name
def sleep(self):
print('%s is sleeping' %self.name) # 新增self.name
stu1=OldboyStudent('李泰迪','male',18)
stu2=OldboyStudent('张随便','famale',38)
OldboyStudent.choose_course(stu1)
OldboyStudent.choose_course(stu2)
# 李泰迪 is choose_course
# 张随便 is choose_course
# 强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
stu1.choose_course() # 等同于OldboyStudent.choose_course(stu1)
stu2.choose_course() # 等同于OldboyStudent.choose_course(stu2)
# 李泰迪 is choose_course
# 张随便 is choose_course
4.4、从代码级别看面向对象
#1、在没有学习类这个概念时,数据与功能是分离的
def exc1(host,port,db,charset):
conn=connect(host,port,db,charset)
conn.execute(sql)
return xxx
def exc2(host,port,db,charset,proc_name)
conn=connect(host,port,db,charset)
conn.call_proc(sql)
return xxx
#每次调用都需要重复传入一堆参数
exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;')
exc2('127.0.0.1',3306,'db1','utf8','存储过程的名字')
#2、我们能想到的解决方法是,把这些变量都定义成全局变量
HOST=‘127.0.0.1’
PORT=3306
DB=‘db1’
CHARSET=‘utf8’
def exc1(host,port,db,charset):
conn=connect(host,port,db,charset)
conn.execute(sql)
return xxx
def exc2(host,port,db,charset,proc_name)
conn=connect(host,port,db,charset)
conn.call_proc(sql)
return xxx
exc1(HOST,PORT,DB,CHARSET,'select * from tb1;')
exc2(HOST,PORT,DB,CHARSET,'存储过程的名字')
#3、但是2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量,
这些全局变量并没有做任何区分,即能够被所有功能使用,
然而事实上只有HOST,PORT,DB,CHARSET是给exc1和exc2这两个功能用的。
言外之意:我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了。
class MySQLHandler:
def __init__(self,host,port,db,charset='utf8'):
self.host=host
self.port=port
self.db=db
self.charset=charset
def exc1(self,sql):
conn=connect(self.host,self.port,self.db,self.charset)
res=conn.execute(sql)
return res
def exc2(self,sql):
conn=connect(self.host,self.port,self.db,self.charset)
res=conn.call_proc(sql)
return res
obj=MySQLHandler('127.0.0.1',3306,'db1')
obj.exc1('select * from tb1;')
obj.exc2('存储过程的名字')
#改进
class MySQLHandler:
def __init__(self,host,port,db,charset='utf8'):
self.host=host
self.port=port
self.db=db
self.charset=charset
self.conn=connect(self.host,self.port,self.db,self.charset)
def exc1(self,sql):
return self.conn.execute(sql)
def exc2(self,sql):
return self.conn.call_proc(sql)
obj=MySQLHandler('127.0.0.1',3306,'db1')
obj.exc1('select * from tb1;')
obj.exc2('存储过程的名字')
数据与专门操作该数据的功能组合到一起
4.5、总结对象的好处
1、可扩展性高的例子
1):定义类并产生三个对象:s1 ,s2 ,s3
class OldboyStudent:
def __init__(self,name,sex,age): #定义对象有哪些特征
self.name=name #特征1
self.sex=sex #特征2
self.age=age #特征3
s1=OldboyStudent('张三','male','18') # 调用类(实例化)产生对象
s2=OldboyStudent('李四','female','28') # 调用类(实例化)产生对象
s3=OldboyStudent('王二','male','38') # 调用类(实例化)产生对象
2):新增一个类属性(特征),那么实例化后所有对象都会收到这个属性。
class OldboyStudent:
country='China'
def choose_course(self):
print('%s 测试绑定方法' %self.name)
def __init__(self,name,sex,age): #定义对象有哪些属性
self.name=name #属性1
self.sex=sex #属性2
self.age=age #属性3
def learn(self): #定义对象的特征
print('学习')
def tell_info(self):
info="""
国籍:%s
姓名:%s
年龄:%s
性别:%s
"""%(self.country,self.name,self.age,self.sex)
print(info)
s1=OldboyStudent('张三','male','18') # 调用类(实例化)产生对象
s2=OldboyStudent('李四','female','28') # 调用类(实例化)产生对象
s3=OldboyStudent('王二','male','38') # 调用类(实例化)产生对象
print(s1.country)
# China
s1.choose_course()
# 张三 测试绑定方法
s1.tell_info()
# 国籍: China
# 姓名: 张三
# 年龄: 18
# 性别: male
五、面向对象的三大特性:继承,派生,多态,封装
5.1、第一大特性:继承与派生
1、什么是继承?
1) 继承是一种新建类的方式,新建的类称为子类或者派生类,被继承的类称为父类或者基类或者超类。
2) 子类会遗传父类的一系列属性,从而解决代码重用问题。
3) Python支持多继承
4) 在Python3中,如果没有显示继承任何类(使用print(Sub1.__bases__)查看继承了哪些类),那么默认继承object类。
5) 在Python2中,如果没有显示继承任何类(使用print(Sub1.__bases__)查看继承了哪些类),也不会继承object类。
2、继承与抽象
继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
2、为什么要用继承?
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时。
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。例子如下:
==========================第一部分
例如
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下:
#猫和狗有大量相同的内容
class 猫:
def 喵喵叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
class 狗:
def 汪汪叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
==========================第二部分
上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
动物:吃、喝、拉、撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
伪代码如下:
class 动物:
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 猫(动物):
def 喵喵叫(self):
print '喵喵叫'
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 狗(动物):
def 汪汪叫(self):
print '喵喵叫'
==========================第三部分
#继承的代码实现
class Animal:
def eat(self):
print("%s 吃 " %self.name)
def drink(self):
print ("%s 喝 " %self.name)
def shit(self):
print ("%s 拉 " %self.name)
def pee(self):
print ("%s 撒 " %self.name)
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '猫'
def cry(self):
print('喵喵叫')
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed='狗'
def cry(self):
print('汪汪叫')
# ######### 执行 #########
c1 = Cat('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
使用继承来重用代码比较好的例子
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大减少了编程的工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大。例子如下:
class Hero:
def __init__(self,nickname,aggressivity,life_value):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
def move_forward(self):
print('%s move forward' %self.nickname)
def move_backward(self):
print('%s move backward' %self.nickname)
def move_left(self):
print('%s move forward' %self.nickname)
def move_right(self):
print('%s move forward' %self.nickname)
def attack(self,enemy):
enemy.life_value-=self.aggressivity
class Garen(Hero):
pass
class Riven(Hero):
pass
g1=Garen('草丛伦',100,300)
r1=Riven('锐雯雯',57,200)
print(g1.life_value)
r1.attack(g1)
print(g1.life_value)
'''
运行结果
'''
注意:像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。
1) 减少代码冗余例子 --- 这里使用到了对象的继承与派生
使用类的继承来减少代码冗余。
#修改前:
import pickle
class OldboyStudent:
school='oldboy'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def choose_course(self,course):
self.course=course
print('%s is choose course:%s' %(self.name,self.course))
def save(self):
with open('%s.stu' %self.name,'wb') as f:
pickle.dump(self,f)
class OldboyTeacher:
school = 'oldboy'
def __init__(self,name,age,sex,level):
self.name = name
self.age = age
self.sex = sex
self.level = level
def score(self,stu):
print('%s is score %s' %(self.name,stu.name))
def save(self):
with open('%s.stu' %self.name,'wb') as f:
pickle.dump(self,f)
stu1=OldboyStudent('alex',20,'male')
stu1.save()
tea1=OldboyTeacher('egon',18,'male','10')
tea1.save()
#####################################分割线####################################
#修改后: 使用继承与派生的方式减少代码冗余。
import pickle
class OldboyPeople:
school='oldboy'
def save(self):
with open('%s.stu' %self.name,'wb') as f:
pickle.dump(self,f)
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
class OldboyStudent(OldboyPeople):
def choose_course(self,course):
self.course=course
print('%s is choose course:%s' %(self.name,self.course))
# 在子类中派生出新方法中重用父类的功能:
# 方式一:指名道姓的访问父类的函数
class OldboyTeacher(OldboyPeople):
def __init__(self,name,age,sex,level): # 在子类派生出的新方法中重用父类的功能。[定义了与父类名字相同的"__init__"]
OldboyPeople.__init__(self,name,age,sex) # 指名道姓的访问父类的函数"__init__",把子类[def __init__(self,name,age,sex,level)]里面的self,name,age,sex 通过[ OldboyPeople.__init__(self,name,age,sex)]传值给父类OldboyPeople。
self.level = level # 在子类中新增一个功能level
def score(self,stu):
print('%s is score %s' %(self.name,stu.name))
stu1=OldboyStudent('alex',20,'male')
print(stu1.name)
print(stu1.school)
# alex
# oldboy
tea1=OldboyTeacher('egon',18,'male','10')
print(tea1.name)
print(tea1.level)
# egon
# 10
3、如何用继承 继承的优点:减少类与类之间的代码冗余
子类与父类的继承关系例子:
class Parent1: # 父类
pass
class Parent2: # 父类
pass
class Sub1(Parent1): # 子类
pass
class Sub2(Parent1,Parent2): # 子类
pass
print(Sub1.__bases__) # 查看子类Sub1继承的父类有哪些
# (<class '__main__.Parent1'>,)
print(Sub2.__bases__) # 查看子类Sub2继承的父类有哪些
# (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
4、在Python中类分为两种
新式类:==>Python3
但凡继承object的类,以及该类的子类都是新式类。
在Python3中所有的类都是新式类(Python3中所有类的父类都是object)。
经典类:==>Python2
没有继承object类,以该类的子类都是经典类。
只有在Python2中才有存在经典类,因为在python2中没有显示继承任何类,也不会继承object类。
5、继承实现的原理 -- 子类重用父类共的两种方式
先查找自己的名称空间,如果没有则去类的名称空间查找
单继承下,属性的查找关系
class Foo:
def f1(self):
print('Foo f1')
def f2(self): # self = obj
print('Foo f2')
self.f1() # self.f1() == obj.f1() ,obj这个对象本身是没有f1()这个函数的,它需要去类中查找,即Bar(Foo)中函数f1()
class Bar(Foo):
def B1(self):
print('Bar B1')
obj=Bar()
obj.f2()
# Foo f2
# Foo f1
多继承下的继承顺序
Python中子类可以同时继承多个父类,如A(B,C,D)
如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先
5.1..3、继承原理
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
5.1.4、子类中调用父类的方法
强调:指名道姓或者super()二者使用哪一种都可以,但最好不要混合使用。
方法一:指名道姓,即父类名.父类方法(),例:Vehicle.run(self)
class Vehicle: # 定义交通工具类
Country = 'China'
def __init__(self, name, speed, load, power):
self.name = name
self.speed = speed
self.load = load
self.power = power
def run(self):
print('开动啦...')
class Subway(Vehicle): # 地铁
def __init__(self, name, speed, load, power, line):
Vehicle.__init__(self, name, speed, load, power) # 派生方式1--指名道姓的使用"Vehicle"类定义的属性
self.line = line
def run(self):
print('地铁%s号线欢迎您' % self.line)
Vehicle.run(self)
line13 = Subway('中国地铁', '180m/s', '1000人/箱', '电', 13)
line13.run()
:>> 地铁13号线欢迎您
:>> 开动啦...
方法二:super() super(自己的类名,self).父类中的方法名(),例: super().run() 或 super(Subway, self).run()
class Vehicle: # 定义交通工具类
Country = 'China'
def __init__(self, name, speed, load, power):
self.name = name
self.speed = speed
self.load = load
self.power = power
def run(self):
print('开动啦...')
class Subway(Vehicle): # 地铁
def __init__(self, name, speed, load, power, line):
super().__init__(name, speed, load, power) # 派生方式2-super(),可以把"super()"理解为父类,那么就可以通过“super”直接调用父类里面的功能了。
# 在python3中: super() == super(Subway,self)
# super写法二:super(Subway,self).__init__(name, speed, load, power)
self.line = line
def run(self):
print('地铁%s号线欢迎您' % self.line)
super().run()
# super写法二:super(Subway, self).run()
line13 = Subway('中国地铁', '180m/s', '1000人/箱', '电', 13)
line13.run()
:>> 地铁13号线欢迎您
:>> 开动啦...
MRO列表从当前类的父类中依次查找属性)
证明:调用super()时,是如何按照MRO列表依次查找属性的
class A:
def f1(self):
print("A")
super().f2() # super() 会基于当前所在的查找位置继续往后查找。
# 从下面的MRO列表中可以看出:super() 会基于<class '__main__.B'>开始查找。
class B:
def f2(self):
print("B")
class C(A,B):
def f2(self):
print("C")
obj=C()
print(C.mro())
# MRO列表: [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
obj.f1()
# A
# B
5.1.5、类之间的组合使用
1、什么是组合?
一个对象属性(变量)的值是来自于另外一个类的对象,这就叫做类的组合使用。
2、为什么要用组合?
组合是用来减少类与类之间的代码冗余(这一点和继承是相同的,只不过继承是:多个子类属于一个父类,属于一个从属关系,这种的话都可以使用继承的方式来减少代码冗余。但是对于没有从属关系的类,想要实现代码冗余:这个时候就需要用到组合。)
组合使用例子:OldboyStudent类下的"tell_course_info"对象(函数)里面的for循环取到的值来自于Course类下面的"tel_info"对象(函数)。
# 人类
class OldboyPeople:
school='Oldboy'
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
# 学生类
class OldboyStudent(OldboyPeople):
def __init__(self,name, sex, age):
OldboyPeople.__init__(self,name, sex, age)
self.course=[] # 课程为空
def choose_course(self,course):
print('%s is choose_course %s' %(self.name,course))
def tell_course_info(self): # 循环取出self.course里面的值,并打印信息
for course in self.course: # 在stu1对象里面,循环取出将python对象和linux对象
course.tel_info() # course拿到的就是[python对象和linux对象],然后调用python对象和linux对象下面的tel_info方法
# 课程类
class Course:
def __init__(self,name,price,period):
self.name=name
self.price=price
self.period=period
def tel_info(self):
print("""
名称: %s,
价格: %s,
周期: %s,
""" % (self.name,self.price,self.period))
python=Course('Python','8000','5mons') # 调用课程类拿到一个对象(python)
linux=Course('Linux','10000','3mons') # 调用课程类拿到一个对象(linux)
# stu1对象的course属性的值,来自于Course类下面的python对象和linux对象
stu1=OldboyStudent('李三',18,'male') # 调用学生类拿到一个对象(stu1)
stu1.course.append(python) # 将python[课程类实例化的对象]这个对象放入到stu1对象内。
stu1.course.append(linux) # linux[课程类实例化的对象]这个对象放入到stu1对象内。
stu1.tell_course_info() # 调用学生类里面的方法,把学生所选的课程信息全部打印出来。
名称: Python,
价格: 8000,
周期: 5mons,
名称: Linux,
价格: 10000,
周期: 3mons,
5.2、第二大特性:多态与多态性
1、什么是多态?
同一种事务的多种形态。
2、为何要用多态
多态的特性:可以在不用考虑对象具体类型的情况下,直接调用对象的一些方法。
3、如何用多态:
解释多态的例子:
import abc
# "Animal"类在使用了"metaclass=abc.ABCMeta"之后无法被实例化(抽象基类不能被实例化)
class Animal(metaclass=abc.ABCMeta): # 父类存在的意义就是用来定义规范
@abc.abstractmethod # 在使用"abc.abstractmethod"方法之后:
def talk(self): # 所有继承了"Animal"的子类,里面必须定义一个相同的talk函数,否则报错。
pass
@abc.abstractmethod
def eat(self):
print("eat")
class People(Animal):
def talk(self):
print("hello")
def eat(self):
print("eat")
class Dog(Animal):
def talk(self):
print("旺旺")
def eat(self):
print("eat")
class Pig(Animal):
def talk(self):
print("henhen")
def eat(self):
print("eat")
peo1=People()
dog1=Dog()
pig1=Pig()
peo1.talk()
dog1.talk()
pig1.talk()
peo1.eat()
dog1.eat()
pig1.eat()
peo1.eat()
dog1.eat()
pig1.eat()
3.1、Python推崇的是鸭子类型(走起路来像鸭子,叫声像鸭子,那么就是鸭子)
class Animal(metaclass=abc.ABCMeta):
def talk(self):
pass
# 这里不用继承Animal类了,自己在每一个类里面都定义一个叫talk的方法:
class People:
def talk(self):
print("hello")
class Dog:
def talk(self):
print("旺旺")
class Pig:
def talk(self):
print("henhen")
peo1=People()
dog1=Dog()
pig1=Pig()
peo1.talk()
dog1.talk()
pig1.talk()
5.3、第三大特性:封装
1、什么是封装?
"封"的意思就是藏起来,在内部可以看到,但是对外部是隐藏的。
"装"的意思是往一个容器中放入一系列属性
2、为何要用封装 --- 将数据属性隐藏起来,从而让类的使用者无法直接操作该数据属性,只能间接使用[ 通过调用类内部提供的接口的方式来使用, 而接口则由类的开发人员设计。]
2.1、封装数据属性:
将数据属性隐藏起来,从而让类的使用者无法直接操作该数据属性。
需要类的设计者在类的内部开辟接口,让类的使用者用接口来间接地操作数据。
类的设计者可以在接口上附加任意逻辑,从而严格控制类的使用者对类属性的操作。
将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class People():
def __init__(self,name,age):
self.__name=name
self.__age=age
def tell_info(self):
print("<%s:%s>" %(self.__name,self.__age))
peo1=People("sudaa",18)
# print(peo1.__name) #在外部,无法通过调用对象的方式来查看封装后的属性值
peo1.tell_info() # 只能通过类内部的方法[在类内部可以调用和查看__开头的属性],查看属性的值
<sudaa:18>
# 数据隐藏之后无法调用
print(peo1.name)
print(peo1.__name)
2.2:封装方法:目的是隔离复杂度
封装方法举例:
1. 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
2. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
3. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
# 取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
# 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
# 隔离了复杂度,同时也提升了安全性
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
a=ATM()
a.withdraw()
3、如何用封装
但凡是双下划线开头" __test "(不能是双下划线结尾)的属性,都会被隐藏起来,类内部可以直接使用而类外部无法直接使用,即封装是对外不对内的(外部非要访问的话,通过res._Foo__f1()的方式照样可以获取到值)。
这种隐藏的特点:
1、只是一种语法上的变形,会将__开头的属性变形为:_自己的类名__属性名
2、该变形只在类定义阶段发生一次,在类定义阶段之后新增的__开头的属性并不会发生变形
3、这种隐藏是对外不对内的 --见例子1
4、在继承中,父类如果不想让子类覆盖自己的同名方法,可以将方法(函数)定义为私有的(即给函数加上__开头)。--见例子3
例子1:封装的例子,封装之后,类里面定义的函数,在类的外面是无法直接调用的。
class Foo():
__n=1
def __init__(self,name):
self.name=name
def __f1(self):
print('f1')
res=Foo('szq')
# res.__f1() # 使用此方法无法访问对象下的__f1方法
# 问题?如何才能访问到对象下的__f1方法:
# 1.首先查看类的名称空间
# print(Foo.__dict__) # 通过查看Foo类的名称空间可以看出'_Foo__n'与_Foo__f1'
# {'__module__': '__main__', '_Foo__n': 1, '__init__': <function Foo.__init__ at 0x0000023ECC1EAA60>, '_Foo__f1': <function Foo.__f1 at 0x0000023ECC1EAAE8>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
# 2.根据名称空间来访问对象下的__f1方法
# res._Foo__f1() # 那么就需要使用这种方式来获取类内部的一些属性和值
# f1
例子2:封装之后,外部如何调用? --通过在类的内部新建接口的形式!
class Foo():
__n=1
def __init__(self,name):
self.name=name
def __f1(self):
print('f1')
def f2(self): # 类的内部新增一个接口函数f2,通过f2函数来调用"__f1"函数
self.__f1()
res=Foo('szq')
res.f2()
f1
例子3:在继承中,父类如果不想让子类覆盖自己的同名方法,如何实现? --可以将方法(函数)定义为私有的(即给函数加上__开头)
class Foo:
def __f1(self):
print("Foo.f1")
def f2(self):
print("Foo.f2")
self.__f1()
class Bar(Foo):
def __f1(self):
print("Bar.f1")
obj=Bar()
obj.f2()
# 没加__时
# Foo.f2
# Bar.f1
# 加__之后
# Foo.f2
# Foo.f1
4、封装之property ,类里面的装饰器如何使用?
通过使用property装饰器:可以将对象的"技能"(函数)伪装成一个"属性"(变量),并且可以对这个属性(变量)做"增,删,改"的操作。
例子1:将一个对象的技能(函数)伪装成一个属性(变量)
伪装之前:想要访问People类下面的name时,只能通过调用peo1.name()函数的方式
class People():
def __init__(self,name):
self.__name=name
def name(self):
return self.__name
peo1=People('sudada')
print(peo1.name())
sudada
伪装之后:想要访问People类下面的name时,就可以直接通过peo1.name的方式去访问,和访问属性(变量)的方式是一样的。
class People():
def __init__(self,name):
self.__name=name
@property
def name(self):
return self.__name
peo1=People('sudada')
print(peo1.name)
sudada
例子2:将一个对象的技能(函数)伪装成一个属性(变量),并且做 "增,删,改" 的操作
class People():
def __init__(self,name):
self.__name=name
@property # 将技能(函数)伪装为一个属性(变量),可以进行查看
def name(self):
return self.__name
@name.setter # 由于name已经被伪装为一个属性(变量),那么就得有修改这个属性(变量)值的方法。就是"@name.setter"
def name(self,val):
if type(val) is not str:
raise TypeError('名字的值必须为str类型')
self.__name=val
@name.deleter # 删除伪装后的属性(变量)的方法"name.deleter"
def name(self):
print('无法删除!')
peo1=People('sudada')
peo1.name='szq' # 在使用"@name.setter"装饰器之后,就可以通过peo1.name='szq'的方式修改name的值了。
print(peo1.name)
# szq
del peo1.name # 删除操作,触发"name.deleter",并拿到返回值
# 无法删除!
5、绑定方法与非绑定方法
1、绑定方法 -- classmethod
特点:绑定给谁,就应该有谁来调用,谁来调用就会将谁当做第一个参数传入。
1.绑定到对象的方法:
在类中定义的函数,在没有被任何装饰器装饰的情况下,默认都是绑定给对象的。
2.绑定到类的方法:
在类中定义的函数,在被装饰器classmethod装饰的情况下,该方法是绑定类的,由类来调用。
绑定到类的例子:最常用的应用场景--提供额外的实例化方式
import settings # 导入配置文件
'''
settings配置文件内容如下:
HOST='127.0.0.1'
PORT=3306
'''
class MySQL():
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod # 提供一种额外的实例化方式,从配置文件中读取配置的方式来完成传参的一种方式
def from_conf(cls): # cls是自动补全的,和self一样。这里的cls补全的是类(MySQL)。[哪个类去调用就传谁入哪个了类]
return cls(settings.HOST,settings.PORT)
# cls(settings.HOST,settings.PORT) == MySQL(settings.HOST,settings.PORT)就是一次实例化,拿到的是一个对象。
# 方式一:使用类的绑定方法拿到的对象,MySQL.from_conf()获取到的是一个对象,这个对象是通过"cls(settings.HOST,settings.PORT)"(实例化)得到的。
conn1=MySQL.from_conf()
print(conn1.host,conn1.port)
# 127.0.0.1 3306
# 方式二:实例化类,获取到对象的方式
conn=MySQL('1.1.1.1',3306)
print(conn.host,conn.port)
# 1.1.1.1 3306
staticmethod
特点:即不与类绑定也不与对象绑定,没有任何自动传值的效果,因为函数体根本也不需要。
import hashlib
import time
class Mysql:
def __init__(self,host,port):
self.host=host
self.port=port
@staticmethod
def create_id(): # 这里不需要传入self,也就是不需要绑定任何类或者对象。
m=hashlib.md5()
m.update(str(time.clock()).encode("utf-8"))
return m.hexdigest()
obj=Mysql("1.1.1.1",3306)
print(obj.create_id()) # 对象可以调用create_id函数
>: f9eb7796eb26e95a1f1a4e341a5236fc
print(Mysql.create_id()) # 类也可以调用create_id函数
>: a94a88d7e37a9344ac076bd9805a6f5f
六、内置方法之反射
1、什么是反射?
指的是通过字符串来操作类或者对象的属性。
例子:反射涉及到的四个内置函数
# 先定义类
class People():
country='China'
def __init__(self,name):
self.name=name
def foo(self):
print("from foo")
peo=People("sudada")
涉及到的四个内置函数
一、hasattr # 查看一个类是否存在某个属性
# 方法一:通过查看"People"类的名称空间的方式查看"country"这个属性是否存在
print("country" in People.__dict__)
# 方法二:
print(hasattr(People,'country')) # 查看People类下是否有"country"这个属性
print(hasattr(peo,'country')) # 查看peo对象下是否有"country"这个属性
True
二、getattr # 获取一个类某个属性的值
# 方法一:通过查看"People"类的名称空间的方式获取"country"这个属性的值
print(People.__dict__['country'])
# 方法二:
print(getattr(peo,'country',None)) # 获取peo对象下"country"变量的返回值,如果变量country不存在则默认返回None
print(getattr(peo,"foo")) # 获取peo对象下"foo"变量的返回值,如果变量foo是函数的话,那么就返回函数foo的内存地址,加括号即可运行函数。
三、setattr # 设置一个类某个属性的值
setattr(peo,'x',111)
print(peo.x)
# 111
四、delattr # 删除一个类某个属性
delattr(peo,'country')
print(peo.__dict__)
2、反射的应用场景: -- 通过输入命令的方式获取类里面某个属性的值
class FTP():
def get(self):
print('get...')
def put(self):
print('put...')
def run(self):
while True:
cmd=input('>>: ').strip() # 用户输入命令
if hasattr(self,cmd): # 判断输入的"命令"是否存在于FTP这个类的属性里面
res=getattr(self,cmd) # 如果输入的"命令"存在的话,那么就通过getattr的方法拿到FTP这个类对应的属性,并拿到返回结果
res() # 把拿到的结果加括号运行,从而得到属性的值
else:
print('命令不存在')
obj=FTP()
obj.run()
七、内置方法
__str__ 当对象被打印时 " print(对象名) ",显示__str__函数的返回值。
class People():
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def __str__(self): # 在实例化对象后,只要使用"print(对象)",就会触发这个函数,并且会执行这个函数。
return '["%s",%s,"%s"]' %(self.name,self.age,self.sex)
peo1=People('sudaa',18,'male')
print(peo1)
# ["sudaa",18,"male"]
内置方法二: __del__ 当对象被删除之前(当前程序代码结束运行之前),会自动触发
哪些地方会用到__del__:当一个Python变量关联了系统变量时,涉及到系统回收资源的情况下,会用到__del__。
class Foo():
def __init__(self,x):
self.x=x
def __del__(self): # 当一个对象的代码执行完毕之后,就会触发这个规则(执行这个函数)
print('del...')
obj=Foo(111)
print('====>') # 先执行这个代码,然后执行__del__代码
# ====>
# del...
# 伪代码:当MySQL服务器调用完毕后,关闭系统资源
class MySQL():
def __init__(self,host,port):
self.host=host
self.port=port
self.conn=connect(host,port)
def __del__(self):
self.conn.close
obj=MySQL('1.1.1.',3306)
八、内置函数补充 -- 详见第8节15讲
十、元类
10.1、元类的简单理解
元类就是类的类: 默认的元类是"type";
也就是调用元类,产生类;
调用类,产生对象;
即:元类 --> 类 --> 对象
10.2、查看一个类是属于哪个元类: type(类名)
class People:
country="China"
def __init__(self,name,age):
self.name=name
self.age=age
p1=People("szq",18)
print(p1.__dict__)
# {'name': 'szq', 'age': 18}
# 查看对象属于什么类型:对象是一个类的实例化,也就是说对象的类型是一个"类"(People)
# print(type(p1))
# <class '__main__.People'>
# 查看类属于什么类型:可以把类理解为"类是一个元类的实例化",那么打印类的类型,就能拿到类对应的元类。
# print(type(People))
# <class 'type'>
10.3、类的定义过程分析
1)、首先了解定义一个类的三大组成部分
1、类名
2、类的基类
3、类的名称空间
2)、class 关键字底层执行的操作(也就是类的定义过程分析)
1、拿到类名:class_name="People"
2、拿到基类们:class_bases=(object,)
3、执行类体代码,产生类的名称空间,将类体代码执行过程中产生的名字(名称空间)都放进去,class_dict={.....}
4、调用元类产生People类:People=type(class_name,class_bases,class_dict)
3)、根据上述分析,不使用class关键字,定义一个类出来
1、拿到类名:class_name="People"
2、拿到基类们:class_bases=(object,)
3、执行类体代码,产生类的名称空间,将类体代码执行过程中产生的名字(名称空间)都放进去,class_dict={.....}
4、调用元类产生People类:People=type(class_name,class_bases,class_dict)
class_name="People"
class_bases=(object,)
# 这一步因为需要那名称空间的内存地址,所以写了一个同名的__init__函数做替代使用
def __init__(self, name, age):
self.name = name
self.age = age
class_dict={"country":"China","__init__":__init__} # 这里的__init__就是函数的内存地址
# 通过元类产生类
People=type(class_name,class_bases,class_dict)
# 查看类的类型
# print(type(People))
# <class '__main__.People'>
# 查看类的值
p1=People("szq",18)
print(p1.__dict__)
# {'name': 'szq', 'age': 18}
10.4、自定义元类 (必须继承type类,才能称之为元类)
# 自定义元类,必须继承type类,才能称之为元类
class Mymeta(type):
# 继承了type类,所以__init__方法要传3个值(也就是上面说的"类名,基类们,类体代码",分别对应class_name,class_bases,class_dict)
def __init__(self,class_name,class_bases,class_dict):
super(Mymeta,self).__init__(class_name,class_bases,class_dict)
# 首字母必须大写
if not class_name.istitle():
raise TypeError("类名:%s 的首字母必须大写" %(class_name))
# 类里面必须有注释的字段,且注释字典的值不为空
if "__doc__" not in class_dict or len(class_dict["__doc__"].strip(" \n")) == 0:
# __doc__ 这个字段是通过打印类的__dict__方法拿到的:print(People.__dict__)
# 如果注释字段的值为空时,class_dict["__doc__"] 拿到的就是一个" \n",所以strip(" \n")。
raise TypeError("必须有注释")
# 普通类,继承元类
class People(object,metaclass=Mymeta):
"""
注释字段
"""
country="China"
def __init__(self,name,age):
self.name=name
self.age=age
print(People.__dict__)
10.5、自定义元类,控制类的调用过程 (使用 __call__ 方法)
1)、元类一般使用的2个方法
__init__ 是控制类的产生过程
__call__是控制类的调用过程(也就是类产生对象的过程)
2)、对象(类的实例化)默认是不可以被调用的,如果一个对象可以被调用,一定是对象的类定义了一个 __call__ 方法,那么:调用对象(类的实例化),实际上就是在执行类的 __call__ 方法(调用对象的返回值就是__call__方法的返回值)
class People(object):
county = 'china'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 实例化一个对象,然后调用这个对象,就会去执行这个对象所在类的 __call__ 方法。
# 也就是说对象可以被调用,一定是对象的类定义了一个 __call__ 方法。
def __call__(self, *args, **kwargs):
print("====>")
print(self)
print(args)
print(kwargs)
# 调用对象People,一定是对象People的类 Mymeta 中有一个__call__方法
p1 = People('egon', 18, 'male')
p1(1,2,3,x=4,y=5) # 默认情况下,"对象()"执行是会报错的,
# ====>
# <__main__.People object at 0x102c2d780>
# (1, 2, 3)
# {'x': 4, 'y': 5}
3)、类也可以被看作是一个对象,在上面2的基础上,那么:调用类,实际上就是在执行元类的 __call__ 方法
在元类中定义__call__方法来控制类的调用过程(也就是类产生对象的过程),通过3个步骤:
1、先造一个People类的空对象obj
2、调用People类中__init__函数,完成初始化空对象obj的操作
3、返回对象obj
class Mymeta(type):
def __call__(self, *args, **kwargs):
# 1、先造一个People类的空对象obj
obj = self.__new__(self) # 这里的self就是People类
# 2、调用People类中__init__函数,完成对空对象obj的初始化操作
self.__init__(obj, *args, **kwargs)
# 把字段给隐藏,也就是加"_"字段
obj.__dict__ = {'_%s__%s' % (self.__name__, k): v for k, v in obj.__dict__.items()}
# 3、返回对象obj
print(111)
return obj
class People(object,metaclass=Mymeta):
county = 'china'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 直接调用类People,其实就是调用Mymeta中的__call__方法
print(People('egon', 18, 'male').__dict__)
# {'_People__name': 'egon', '_People__age': 18, '_People__sex': 'male'}
10.6、元类的__new__方法
"__new__"方法是一种自定义类的方式。
也就是 "元类" 会把 "类的创建过程" 给拦截,去走元类的"__new__"方法,来创建一个类A。在创建类A的过程中,可以给这个类A添加多个类属性(通过A.__dict__查看)。那么实例化这个类A的对象a,就会包含这些类A属性。
下面的这个例子就是:在创建类A的过程中,给类A添加 ("table_name","primary_key","mappings") 三个类属性,那么实例化这个类的对象a,自然可以通过 a.table_name,a.primary_key,a.mappings拿到这3个类属性的值了。
class Fileld:
def __init__(self, name, column_type, primary_key, default):
# 数据表名
self.name = name
# 数据表字段的类型
self.column_type = column_type
# 数据表字段的主键
self.primary_key = primary_key
# 数据表字段的默认值
self.default = default
# 字符串类型
class StringFile(Fileld):
def __init__(self, name=None, column_type="varchar(200)", primary_key=False, default=None):
super(StringFile, self).__init__(name, column_type, primary_key, default)
# 整数类型
class IntegerFile(Fileld):
def __init__(self, name=None, column_type="int", primary_key=False, default=0):
super(IntegerFile, self).__init__(name, column_type, primary_key, default)
# 元类
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
# 这里Name == User 类
# 这里bases == Model 也就是User的基类
# 这里attrs == User 类下所有的属性,以key:value的形式
if name == "Model": # Model类走到这里的时候直接返回,下面的逻辑都都给继承Model类的类使用(也就是User类)。
return type.__new__(cls, name, bases, attrs)
table_name = attrs.get("table_name", None)
if not table_name:
table_name = name
primary_key = None
mappings = dict()
for k, v in attrs.items():
if isinstance(v, Fileld): # 判断v是不是Field的对象
mappings[k] = v
if v.primary_key:
if primary_key:
raise TypeError("主键重复:%s" %(k))
primary_key = k
for k in mappings.keys():
attrs.pop(k)
if not primary_key:
raise TypeError("没有主键")
# 这里是把想要的值(也就是3个类属性),传给User类。(后面只要是继承Model类的类,都可以拿到这3个类属性)
attrs["table_name"] = table_name
attrs["primary_key"] = primary_key
attrs["mappings"] = mappings
return type.__new__(cls, name, bases, attrs)
class Model(dict, metaclass=ModelMetaclass): # 元类会把类的创建过程给拦截,去走元类的__new__方法,来创建一个类。
def __init__(self, **kwargs):
super(Model, self).__init__(**kwargs)
# __setattr__ 以user.name为例,当给user.name赋值时,对象下没有.name属性的时候,就会去执行__setattr__方法。
# 存值: 实例一个对象"user=Model()",对象做赋值操作"user.name='Szq'"时,触发当前操作。
def __setattr__(self, key, value):
self[key] = value
# __getattr__ 以user.name为例,当user对象下没有.name属性的时候,就会去执行__getattr__方法。
# 取值: 实例一个对象"user=Model()",对象做取值操作"print(user.name)"时,触发当前操作。
def __getattr__(self, item):
try:
# print("1__getattr__",self)
# print("2__getattr__",item)
return self[item]
except TypeError:
raise ("没有该属性")
# 这里创建类的时候,会把类的所有属性全部都传到元类的__new__方法
class User(Model):
table_name = "user"
id = IntegerFile("id", primary_key=True, default=0)
password = StringFile("password")
# 到这里User类就已经创建好了,此时的User类的类属性里面就有了("table_name","primary_key","mappings")这3个类属性了。
# 可以通过print(User.__dict__)查看
# 实例化类,拿到的对象也继承了User类的类属性
user=User() # User类的基类是dict,所以这里实例化类的时候,没有传值,那么user对象的值也就是个空字典"{}"。
user.name="name" # 手动给user对象传值,触发基类Model里面的__setattr__方法。
user.password="123" # 手动给user对象传值,触发基类Model里面的__setattr__方法。
print(user) # 这里拿到user对象的值,也就是经过2次传值后的。即: user = {'name': 'name', 'password': '123'}。
print(user.name) # 这里取值,触发基类Model里面的__getattr__方法。
print(user.mappings) # 这里取值,拿到的是User类的类属性值。