面向对象基础
编程范式
所谓的面向对象编程,指的就是一种编程范式,那么什么是编程范式呢?就是按照某种语法风格加上数据结构加上算法来编写程序。
- 数据结构:列表、字典、集合
- 算法:编写程序的逻辑或者解决问题的流程
一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓跳跳大陆通罗马,实现一个任务的方式有很多种不同的方式,对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。
不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路,大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。两种最重要的编程范式分别是面向过程编程和面向对象编程。
面向过程编程
面向过程编程,核心就是过程二字,过程指的是解决问题的步骤,就是先干什么,然后干什么,最后干什么?实际上就相当于设计了一条流水线,就是机械式的思维方式,把一个大问题拆分成若干个小问题,然后一个个小问题再细化,然后再把这些小问题组合起来,就解决了一个大问题。
下面来看下面向过程的编程,让用户输入注册信息:
import json
import re
def interactive(): # 接收用户输入
name = input('>>>').strip()
pwd = input('>>').strip()
return {
'name':name,
'pwd':pwd
}
def check(user_info): # 检测
is_valid = True
if len(user_info['name']) == 0:
print('用户名不能为空!')
is_valid = False
if len(user_info['pwd']) < 6:
print('密码不能少于6位!')
is_valid = False
return {
'is_valid':is_valid,
'user_info':user_info
}
def register(check_info): # 写入数据库中
if check_info['is_valid']: # 如果为真
with open('db.json','w',encoding='utf-8') as f:
json.dump(check_info['user_info'],f)
def main(): # 主函数
user_info = interactive()
check_info = check(user_info)
register(check_info)
if __name__ == '__main__':
main()
那么这段程序的运行结果就是提示输入用户名和密码,然后对用户名和密码进行检测,如果符合规则,那么就将写入数据库中。
那么此时,如果我想让用户再注册信息的时候,再加上邮箱,代码是怎么写的呢?
import json
import re
def interactive(): # 接收用户输入
name = input('>>>').strip()
pwd = input('>>').strip()
email = input('>>').strip()
return {
'name':name,
'pwd':pwd,
'email':email
}
def check(user_info): # 检测
is_valid = True
if len(user_info['name']) == 0:
print('用户名不能为空!')
is_valid = False
if len(user_info['pwd']) < 6:
print('密码不能少于6位!')
is_valid = False
if not re.search(r'@.*?.com$',user_info['email']):
print('邮箱格式不合法!')
is_valid = False
return {
'is_valid':is_valid,
'user_info':user_info
}
def register(check_info): # 写入数据库中
if check_info['is_valid']: # 如果为真
with open('db.json','w',encoding='utf-8') as f:
json.dump(check_info['user_info'],f)
def main(): # 主函数
user_info = interactive()
check_info = check(user_info)
register(check_info)
if __name__ == '__main__':
main()
如果我们还要添加来自国家、性别、年龄这些信息,这整个代码流程不都要继续修改吗?那么我们现在就能看出面向过程的优缺点了:
优点:把复杂的问题流程化,进而简单化
缺点:可扩展性差
应用场景:自动部署、系统监控脚本
面向对象编程
面向对象编程,核心就是对象二字,就是站在上帝的视角:所有存在的事物都是i对象
举个例子说明:在西游记中,如来就是上帝,他想要解决的问题就是如何把经书传入大唐,如果是面向过程来说,如来就要想这条路要怎么走,碰到事情的时候应该怎么处理,就是要把取经的过程全部都要考虑到,然而换成面向对象呢?他找来了唐僧、孙悟空等人,妖魔鬼怪,一大堆神仙开始了取经之路...
我和小明的特征都是对象,我们的特征都是有鼻子有眼睛、两只手两条腿,技能就是吃饭、学习、睡觉,所以我们可以被称为对象。
为什么你不会觉得你面前的笔记本不是孙悟空呢?因为笔记本有显示器、键盘、鼠标,而孙悟空有毛脸雷公嘴、会七十二变、有火眼金晶,所以你不会这样觉得。
所谓的对象:就是特征和技能的结合体
面向过程:设计流程化
面向过程:站在上帝的视角来模拟世界
优点:可扩展性高
缺点:编程复杂度高
应用场景:用户需求经常变更:互联网应用,游戏,企业内部应用
面向对象程序设计
在软件质量属性上,成本、性能、安全性、可扩展性都是必须要有的,但是可扩展性占的比例还是比较高的。
类
类即类别、种类,是面向对象设计最重要的概念。刚刚我们知道对象是特征和技能的结合体,那么类就是一系列对象相似的特征与技能的结合体。
那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有人类这个概念,这个问题需要分两种情况去看
- 在现实世界中,肯定是先有的对象,然后才有的类
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,比如人类、动物类、植物类等概念。也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在,比如你无法告诉我人类具体指的是哪一个人。
- 在程序中,务必先保证先定义类,后产生对象
与函数的使用是类似的:先定义函数,后调用函数,类也是一样的:在程序中需要先定义类,后调用类。不一样的是:调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
定义类
按照上面步骤,我们来先定义一个类(站在学校的角度,我们都是学生)
- 在现实世界中,先有对象,然后才有的对象
对象1:李坦克
特征:
学校:zhcpt
姓名:李坦克
性别:男
年龄:18
技能:
学习
吃饭
睡觉
对象2:王大炮
特征:
学校:zhcpt
姓名:王大炮
性别:男
年龄:23
技能:
学习
吃饭
睡觉
对象3:张小丽
特征:
学校:zhcpt
姓名:张小丽
性别:女
年龄:16
技能:
学习
吃饭
睡觉
现实中的学生类:
相似的特征:
学校 = zhcpt
相似的技能:
学习
吃饭
睡觉
- 在程序中,务必保证:先定义类,然后使用类(产生对象)
# 在python中程序中的类用class关键字定义。而在程序中特征用变量标识,技能用函数表示,因而类中最常见的无非是:变量和函数的定义
# 先定义类
class zhcptStundet:
school = 'zhcpt' # 相同的特征
def learn(self): # 相同的属性
print('is learning')
def eat(self):
print('is eatting')
def sleep(self):
print('is sleeping')
# 后产生对象
zhcptStudent()
产生一个对象,不是去执行类体,而是执行这个类体后会得到一个返回值。这个返回值就是对象,在这个过程中也被称为实例化。所以应该是这样的:
stu1 = zhcptStudent() # 类的实例化
print(stu1) # <__main__.zhcptStundet object at 0x0000013557F00438>
print(stu1)则是打印zhcptStudent这个类产生的对象,是这个对象的内存地址
注意:
- 类中可以有任意的python代码,这些代码在类定义阶段便会执行,因而会产生新的名称空间,用来存放类的变量名和函数,可以通过zhcptStundet.__dict__方法去查看
- 类中定义的名字,都是类的属性,点是访问属性的语法
- 对于经典类来说,我们可以通过该字典去操作类名称空间的名字,但新式类有限制
# 定义类
class zhcptStudent:
school = 'zhcpt' # 类的数据属性,定义新的名字,新的命名空间
def learn(self): # 类的函数属性,定义了函数,产生了函数局部名称空间,把函数内部定义的名字放进去
print('is learning')
def eat(self):
print('is eatting')
def sleep(self):
print('is sleeping')
print('----------run-----------')
# 查看类的名称空间
print(zhcptStudent.__init__)
我们刚刚说类在定义阶段时候便会执行,因而会产生新的名称空间,用来存放类的变量名和函数,可以通过方法去查看,运行结果为:
----------run-----------
{'__module__': '__main__', 'school': 'zhcpt', 'learn': <function zhcptStundet.learn at 0x0000022B584A0C80>, 'eat': <function zhcptStundet.eat at 0x0000022B584A0D08>, 'sleep': <function zhcptStundet.sleep at 0x0000022B584C90D0>, '__dict__': <attribute '__dict__' of 'zhcptStundet' objects>, '__weakref__': <attribute '__weakref__' of 'zhcptStundet' objects>, '__doc__': None}
所以类和函数就有了区别:函数在定义完成之后不调用的话,那么这段函数就不会执行,而类在定义阶段就会执行
针对属性,python提供了专门的属性访问语法,现在我们来查看访问下变量属性的值、函数属性的值
print(zhcptStudent.school)
print(zhcptStudent.learn)
运行结果为:
zhcpt
<function zhcptStudent.learn at 0x000001D94F020C80> # 打印的是这个类下的learn函数内存地址
刚刚我们说,可以通过字典的方式去操作类名称空间的名字,让我们来看一下:
print(zhcptStudent.__dict__['school'])
print(zhcptStudent.__dict__['learn'])
运行结果和上面是一样的:
zhcpt
<function zhcptStudent.learn at 0x0000023D2EAB0C80>
其实,我们很早之前就接触过类了,还记得我们之前学过了list、dict吗?记得time吗?
In [2]: print(list)
<class 'list'>
In [3]: print(dict)
<class 'dict'>
# time本身也是一个类
import time
time.sleep # 其中time是类,sleep是类里定义的函数属性
类的操作
class zhcptStudent:
school = 'zhcpt'
def learn(self):
print('is learning')
def eat(self):
print('is eatting')
def sleep(self):
print('is sleeping')
print('----------run-----------')
增加
zhcptStudent.country = 'CN'
print(zhcptStudent.__dict__)
print(zhcptStudent.country)
# 运行结果
----------run-----------
{'__module__': '__main__', 'school': 'zhcpt', 'learn': <function zhcptStudent.learn at 0x00000233B8D10C80>, 'eat': <function zhcptStudent.eat at 0x00000233B8D10D08>, 'sleep': <function zhcptStudent.sleep at 0x00000233B8D390D0>, '__dict__': <attribute '__dict__' of 'zhcptStudent' objects>, '__weakref__': <attribute '__weakref__' of 'zhcptStudent' objects>, '__doc__': None, 'country': 'CN'}
CN
删除
del zhcptStudent.country
print(zhcptStudent.__dict__)
# 运行结果,已经找不到country这个k了
{'__module__': '__main__', 'school': 'zhcpt', 'learn': <function zhcptStudent.learn at 0x000001EEF1380C80>, 'eat': <function zhcptStudent.eat at 0x000001EEF1380D08>, 'sleep': <function zhcptStudent.sleep at 0x000001EEF13A90D0>, '__dict__': <attribute '__dict__' of 'zhcptStudent' objects>, '__weakref__': <attribute '__weakref__' of 'zhcptStudent' objects>, '__doc__': None}
修改
zhcptStudent.school = '珠海城市职业技术学院'
print(zhcptStudent.school)
运行结果:
珠海城市职业技术学院
初始化方法
首先先定义一个zhcptStudent类
class zhcptStudent:
school = 'zhcpt' # 相同的特征
def learn(self): # 相同的属性
print('is learning')
def eat(self):
print('is eatting')
def sleep(self):
print('is sleeping')
print('----------run-----------')
stu1 = zhcptStudent()
stu2 = zhcptStudent()
stu1.learn()
stu2.eat()
# 运行结果是
----------run-----------
is learning
is eatting
上面说过,类是具有一系列对象相似的技能和属性的结合体,对象特征与技能的结合体。
那么,实例化的对象,总有不同的地方,比如李坦克是男的,而张小丽是女的,所以我们需要为对象定制对象自己独有的特征:
class Student:
school = 'zhcpt'
def __init__(self,name,age,sex):
self.Name = name
self.Age = age
self.Sex = sex
def learn(self):
print('is learning')
def eat(self):
print('is eatting')
stu1 = Student() # TypeError: __init__() missing 3 required positional arguments: 'name', 'age', and 'sex'
这个时候就会报错,提示缺少三个参数,但是我们却没有调用__init__方法,为什么呢?因为python会自动调用,什么时候会调用呢?创建对象、调用类、实例化的时候就会调用。
stu1 = Student('李坦克',18,'女')
那么这个时候,就相当于把 ('李坦克','女',18) 三个参数传递进去了,即
self.Name = '李坦克'
self.Age = 18
self.Sex = '女'
加上__init__方法后实例化的步骤:
1.先产生一个空对象
2.触发Student.init(stu1,'李坦克',18,'女')
为什么触发就会把参数传递进去呢?
print(Student.__init__)
结果为:
<function Student.__init__ at 0x000001CFC9AE0C80> # 原来__init__方法是一个函数,那么函数就必须要传入参数
类的操作
class Student:
school = 'zhcpt'
def __init__(self,name,age,sex):
self.Name = name
self.Age = age
self.Sex = sex
def learn(self):
print('is learning')
def eat(self):
print('is eatting')
查找
stu1 = Student('张铁牛',22,'男')
print(stu1.Name)
print(stu1.Age)
print(stu1.Sex)
# 运行结果为:
张铁牛
22
男
改
stu1 = Student('张铁牛',22,'男')
stu1.Name = '李铁蛋'
print(stu1.Name)
# 运行结果为:
李铁蛋
删除
stu1 = Student('张铁牛',22,'男')
del stu1.Name
print(stu1.__dict__) # 查看对象的名称空间
# 运行结果如下:
{'Age': 22, 'Sex': '男'}
增加
stu1 = Student('张铁牛',22,'男')
stu1.class_name = 'python开发'
print(stu1.__dict__)
# 运行结果如下:
{'Name': '张铁牛', 'Age': 22, 'Sex': '男', 'class_name': 'python开发'}
补充说明:
- 站的角度不同,定义出的类是截然不同的
- 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类、业务类
- 有时为了编程需求,程序中也可能会定义出现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的事
属性查找与绑定方法
类有两种属性:数据属性和函数属性
1、类的数据属性是所有对象共享的
class Student:
school = 'zhcpt'
def __init__(self,name,age,sex):
self.Name = name # 就相当于往类的名称空间添加一个名字,是stu1的名称空间
self.Age = age
self.Sex = sex
def learn(self):
print('is learning')
def eat(self):
print('is eatting')
stu1 = Student('张全蛋',18,'男') # 都有着相同的数据属性,school = 'zhcpt'
stu2 = Student('王小丽',16,'女')
stu3 = Student('李黑',23,'男')
他们三个对象都定制了私有的特征:
print(stu1.__dict__)
print(stu2.__dict__)
print(stu3.__dict__)
# 运行结果如下:
{'Name': '张全蛋', 'Age': 18, 'Sex': '男'}
{'Name': '王小丽', 'Age': 16, 'Sex': '女'}
{'Name': '李黑', 'Age': 23, 'Sex': '男'}
访问类中的数据属性:是所有对象共有的
print(Student.school,id(Student.school))
print(stu1.school,id(stu1.school))
print(stu2.school,id(stu2.school))
print(stu3.school,id(stu3.school))
# 运行结果如下:
zhcpt 2347297903872
zhcpt 2347297903872
zhcpt 2347297903872
zhcpt 2347297903872
2、类的函数属性是绑定给对象使用的,称为对象的绑定方法
class Student:
school = 'zhcpt'
def __init__(self,name,age,sex):
self.Name = name # 就相当于往类的名称空间添加一个名字,是stu1的名称空间
self.Age = age
self.Sex = sex
def learn(self):
print('%s is learning',self.Name)
def eat(self):
print('%s is eatting',self.Name)
stu1 = Student('张全蛋',18,'男') # 都有着相同的数据属性,school = 'zhcpt'
stu2 = Student('王小丽',16,'女')
stu3 = Student('李黑',23,'男')
访问类中的函数属性:是绑定给对象使用的,绑定到不同的对象是不同的绑定方法,对象调用绑定方法,就会把对象本身当作第一个参数传递给self
# 绑定方法,类的函数的内存地址,和对象的函数内存地址不一样
print(stu1.learn) # <bound method Student.learn of <__main__.Student object at 0x0000027BFF0680B8>>
# 对象访问类中的函数属性,则将本身作为第一个参数传递给self
print(stu1.learn())
# 运行结果
张全蛋 is learning
绑定方法的意思就是调用的都是同一种功能,但是大家执行的都是各自不同的方法
stu1.learn()
stu2.learn()
stu3.learn()
# 运行结果如下:
张全蛋 is learning
王小丽 is learning
李黑 is learning
类中定义的函数属性,没有任何处理的话,不是给类用的,实际上是绑定给对象使用的,谁来调用就是谁在使用这个功能
类即类型
python中一起都是对象,且python3中类与类型是一个概念,类型就是类
# 类型就是类
In [1]: print(list)
<class 'list'>
# 实例化3个对象
In [2]: l1 = list()
In [3]: l2 = list()
In [4]: l3 = list()
# 每个对象的绑定方法,都具有着相同的功能,但是内存地址不同
In [5]: print(l1.append)
<built-in method append of list object at 0x7ff344053e88>
In [6]: print(l2.append)
<built-in method append of list object at 0x7ff344035fc8>
In [7]: print(l3.append)
<built-in method append of list object at 0x7ff34431c5c8>
# 操作绑定方法l1.append(3),就是往l1里添加3,绝不会把3添加到l2或l3中
In [8]: l1.append(3)
In [9]: l1
Out[9]: [3]
In [10]: l2
Out[10]: []
In [11]: l3
Out[11]: []
# 调用类list.append(l3,111),实际上相当于:l3.append(111)
In [14]: list.append(l3,111)
In [15]: l3
Out[15]: [111]
从代码级别看面向对象
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
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('存储过程的名字')
总结使用类可以:将数据和专门操作该数据的功能整合到一起
可扩展性高
定义类并产生3个对象
class Chinese:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
p1=Chinese('egon',18,'male')
p2=Chinese('alex',38,'female')
p3=Chinese('wpq',48,'female')
如果我们新增一个类属性,将会立马反应给所有对象,而对象却无需修改
class Chinese:
country='China'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def tell_info(self):
info='''
国籍:%s
姓名:%s
年龄:%s
性别:%s
''' %(self.country,self.name,self.age,self.sex)
print(info)
p1=Chinese('egon',18,'male')
p2=Chinese('alex',38,'female')
p3=Chinese('wpq',48,'female')
print(p1.country)
p1.tell_info()
小节练习
编写一个学生类,产生一堆学生对象
要求:有一个计数器,统计总共实例化了多少对象
class Student:
def __init__(self,name,sex,age):
self.Name = name
self.Sex = sex
self.Age = age
Student.count += 1
stu1 = Student('小明','男',14)
stu2 = Student('老王','男',38)
stu3 = Student('小丽','女',18)
这时候我们要考虑一个问题,对象的属性是对象自己的, 所以....类的属性是大家共有的
class Student:
count = 0
def __init__(self,name,sex,age):
self.Name = name
self.Sex = sex
self.Age = age
Student.count += 1
stu1 = Student('小明','男',14)
stu2 = Student('老王','男',38)
stu3 = Student('小丽','女',18)
print(Student.count)