3 面向对象补充
内容概要:
- 函数式编程和面向对象的对比
- 面向对象如何编程
- 面向对象三大特性: 3.1 封装 3.2 继承 3.3 多态
- 成员
- 组合(建模、耦合等)
3.1 函数式编程和面向对象的对比
开发一个消息提醒的功能(发邮件 短信 微信的提醒) 函数式编程的优点:调用简单、定义简单 面向对象的缺点:调用复杂、定义复杂 优点:可归类,将一堆类似的功能放到一起
class F1:
def __init__(self): # 这个函数:只要 类名() 就会自动调用这个方法 -- 构造方法
pass
通过构造方法,可以将数据打包,以后需要使用的时候直接去拿就可以了。
3.2 面向对象的应用
如果遇到很多函数都要用到同一个参数的时候,可以使用面向对象的方式编程
- 将数据封装到对象中,以供自己在方法中调用
- 将数据封装到对象中,以供其它函数调用
class FileHandler:
def read_first(self, file_path):
pass
def read_second(self, file_path):
pass
def read_last(self, file_path):
pass
上述程序,我们可以看出来,实现这些方法都需要一个文件路径来打开文件再进行操作,所以可以这样写:
class FileHandler:
def __init__(self, file_path):
self.file_path = file_path
def read_first(self):
with open(self.file_path, "r", encoding="utf-8") as fp:
...
def read_second(self):
with open(self.file_path, "r", encoding="utf-8") as fp:
...
def read_last(self):
with open(self.file_path, "r", encoding="utf-8") as fp:
...
还可以进行优化:我们发现上述代码,如果我们要先读第一行read_first
,再读第二行read_second
最后读最后一行read_last
,那么我们需要像下面这样写:
F = FileHandler("./db.txt")
F.read_first()
F.read_second()
F.read_last()
但是如果这样写,就会导致一个文件打开三次 -- 于是可以再进行优化,优化如下:
class FileHandler:
def __init__(self, file_path):
self.file_path = file_path
self.f = open(file_path, "r")
def read_first(self):
self.f.read()
pass
def read_second(self):
self.f.read()
pass
def read_last(self):
self.f.read()
pass
F = FileHandler("./db.txt")
F.read_first()
F.read_second()
F.read_last()
F.obj.close() # 打开文件记得关闭
# 下面这种打包的思想
def func(*args, **kwargs):
print(args)
print(kwargs["k1"])
func(1, 2, 3, k1=1, k2=3)
# 也可以使用面向对象的思想来实现
def new_func(arg):
arg.k1
arg.k2
arg.k6
class Foo:
def __init__(self, k1, k2, k6):
self.k1 = k1
self.k2 = k2
self.k6 = k6
obj = Foo(111, 222, 333)
new_func(obj)
练习:信息管理系统
- 用户登录
- 显示当前用户信息
- 查看当前用户所有的账单
- 购买了姑娘形状的抱枕
class Account:
def __init__(self):
self.name = None
def info(self):
"""
打印用户信息
Returns:
"""
print("用户名:", self.name)
def account(self):
"""
打印用户账单
Returns:
"""
pass
def buy_pillow(self):
"""
购买抱枕
Returns:
"""
pass
def login(self):
user = input("请输入用户名:")
password = input("请输入密码:")
if password == "123456":
self.name = user
while True:
print("1 查看用户信息 2 查看用户啊账单 3 购买抱枕")
num_choose = int(input("""请输入选项:"""))
if num_choose == 1:
self.info()
elif num_choose == 2:
self.account()
elif num_choose == 3:
self.buy_pillow()
else:
print("输入错误,请重新输入!")
break
print("登录成功!")
else:
print("登录失败!")
obj = Account()
obj.login()
3.3 如何编写面向对象程序
3.3.1 什么时候写、如何写
-
方式一:归类 + 提取公共值 -- 反推
def file_read(file_path): pass def file_update(file_path): pass def file_delete(file_path): pass def file_add(file_path): pass def excel_read(file_path): pass def excel_update(file_path): pass def excel_delete(file_path): pass def excel_add(file_path): pass
--> 归类:仁者见仁 智者见智
class File: def file_read(file_path): pass def file_update(file_path): pass def file_delete(file_path): pass def file_add(file_path): pass class ExcelFile: def excel_read(file_path): pass def excel_update(file_path): pass def excel_delete(file_path): pass def excel_add(file_path): pass
提取公共值:
class File: def __init__(self, file_path): self.file_path = file_path def file_read(file_path): pass def file_update(file_path): pass def file_delete(file_path): pass def file_add(file_path): pass class ExcelFile: def __init__(self, file_path): self.file_path = file_path def excel_read(file_path): pass def excel_update(file_path): pass def excel_delete(file_path): pass def excel_add(file_path): pass
-
方式二:正向编写
[!IMPORTANT]
在指定类中编写和当前类相关的所有代码
class Message: def email(self): pass def wechat(self): pass class Person: def __init__(self, gender, age, name): self.gender = gender self.age = age self.name = name def sing(self): pass def dancing(self): pass def rap(self): pass def basketball(self): pass obj = Person("男", 18, "computer") ...
3.4 三大特性
3.4.1 封装
将相关功能打包(封装)到一个类中
将数据打包(封装)到一个对象中
3.4.2 继承
自己(子类/派生类)有就用自己的,没有就用父亲(基类)的。
子类和父类是相对而言的。
为什么要有继承 -- 为了复用,提高代码重用性。
3.4.3 多态
鸭子模型:只要可以嘎嘎叫,那就是鸭子。
python
原生支持多态- 崇尚鸭子模型
- 传参时无需指定类型
3.5 成员
-
类的成员
class Foo: def __init__(self, name): self.name = name # 实例变量/对象变量/字段 -- 作用是对对象初始化 def func(self): pass # obj = Foo("haha") # Foo类的实例/对象
-
成员共分为三类:
-
变量/字段
-
实例变量(字段)
-
公有实例变量
class Foo: def __init__(self, name): self.name = name # 公有实例变量 self.age = 123 # 公有实例变量 def func(self): # 公有变量 在内部调用 print(self.name) obj = Foo("computer") # 公有变量在外部调用 print(obj.name) print(obj.age)
-
私有实例变量
class Foo: def __init__(self, name): self.__name = name # 私有实例变量 self.age = 123 # 公有实例变量 def func(self): print(self.__name) # 类内部调用私有实例变量 -- 间接访问 obj = Foo("computer") # 公有变量在外部调用 print(obj.age) # 类外部调用私有变量 print(__name) # 报错,显示“没有__name这个属性” obj.func() # 可以访问
-
-
类变量(静态字段) -- 可以直接通过类进行访问
class Foo: country = "中国" # 类变量(静态字段) -- 在内存中,这个变量是放在类那个空间里面 -- 如果每个对象里面都需要设置一个相同的值,并且要改都改,要删都删,那就可以将其提取为类变量。 def __init__(self, name): self.name = name # 实例变量/对象变量/字段 -- 作用是对对象初始化 def func(self): pass obj = Foo("computer") obj1 = Foo("science") print(obj.country) # 中国 print(obj1.country) # 中国
obj.country = "美国" # 只改的是自己对象所在内存区域的country变量,和Foo类里面的country没有关系 print(obj.country) # 美国 print(obj1.country) # 中国
Foo.country = "美国" print(obj.country) # 美国 print(obj1.country) # 美国
-
公有类变量
class Foo: country = "中国" def __init__(self): pass def func(self): # 内部调用 print(self.country) print(Foo.country) # 外部调用 print(Foo.country) obj = Foo() print(obj.country) print(Foo.country)
-
私有类限量
class Foo: __country = "中国" def __init__(self): pass def func(self): # 内部调用 print(self.__country) print(Foo.__country) # 外部直接调用 print(Foo.__country) # 无法调用 obj = Foo() print(obj.__country) # 无法调用 print(Foo.__country) # 无法调用 obj.func() # 外部间接调用,可以调用
-
如果非要访问,也不是不行
class Foo: def __init__(self, name): self.__name = name obj = Foo("hahaha") obj.__name # 无法访问 print(obj._Foo__name) # 可以访问但不推荐
-
-
准则:以后
- 对于实例变量/字段,访问时使用对象访问,
obj.xxx
- 对于类变量/静态字段,访问时,使用类来访问,如果实在不方便时,使用对象访问。
- 对于实例变量/字段,访问时使用对象访问,
思考题:私有变量,不仅外部无法访问,其子类也无法访问。那问题来了,如何验证?
class Base(object): #python 3+ 所有、任何类都会继承object类,不管写不写object __private = "私有" class Foo(Base): def func(self): print(self.__private) print(Foo.__private) Foo().func() # 无法调用
class Base(object): #python 3+ 所有、任何类都会继承object类,不管写不写object __private = "私有" def tell(self): print(Base.__private) class Foo(Base): def func(self): print(self.__private) print(Foo.__private) Foo().tell() # 可以调用
-
-
方法
-
方法(实例方法) -- 当实例方法里面如果需要调用到构造方法里面的值的时候才需要用到实例方法
class Foo(object): def __init__(self, name): self.name = name def func(self): # 实例方法 print("实例方法") print(self.name) obj = Foo("computer") obj.func()
-
静态方法 -- 如果方法中无需使用构造方法中封装的数据、对象中封装的方法时,那就写静态方法
class Foo(object): def __init__(self, name): self.name = name def func(self): # 实例方法 print(self, "实例方法") print(self.name) @staticmethod def display(): # 静态方法 print(123) @classmethod def show(cls, a, b): """类方法""" print(999) obj = Foo("computer") obj.func() obj.show(1, 2) # cls传递的是当前类
-
类方法 -- 可以帮我们自动识别当前类 -- 如果在方法中会使用到当前类那么就可以使用类方法。
class Foo(object): def __init__(self, name): self.name = name def func(self): # 实例方法 print(self, "实例方法") print(self.name) @staticmethod def display(): # 静态方法 print(123) @classmethod def show(cls, a, b): """类方法""" print(999) obj = Foo("computer") obj.func() obj.show(1, 2) # cls传递的是当前类
-
公有方法
-
私有方法
class Foo(object): def __init__(self): # 这是特殊方法 self.name = "computer" def __display(self, arg): # 私有实例方法 print("私有方法") print(arg) def func(self): self.__display(233) obj = Foo() obj.__display("hello") # 无法访问 obj.func() # 可以访问
class Foo(object): def __init__(self): # 这是特殊方法 self.name = "computer" @staticmethod def __display(arg): # 私有静态方法 print("私有静态方法") print(arg) def func(self): self.__display(233) obj = Foo() obj.__display("hello") # 无法访问 obj.func() # 可以访问 Foo.func() # 可以访问
# 私有类方法 一样的
-
-
属性(特性) -- 通过方法改造出来的 -- 当一个方法不需要传递参数(只有一个
self
),并且return
一个东西时 -- 属性也有公有和私有之分 改造之前:class Foo(object): def __init__(self): pass def start(self): # 私有方法方法 return 1 def end(self): return 10 obj = Foo() obj.start() obj.end()
改造之后:
class Foo(object): def __init__(self): pass @property def start(self): # 私有方法方法 return 1 @property def end(self): return 10 obj = Foo() print(obj.start) print(obj.end)
[!Caution]
当将方法变成属性时,不能传参数。
-
3.5.1 面试题
[!Important]
面试题:静态方法/实例方法/类方法的区别:
- 如果在方法中不使用关于对象的方法,那么就使用静态方法;
- 如果我们需要使用对象中封装的数据,那么就使用实例方法;
- 如果我们需要在方法中使用当前类,那么就使用类方法
3.5.2 练习题:分页
- 面向过程:
data_list = []
for i in range(1, 301):
data_list.append("alex-%s" % i)
page_size = 10
print("请输入要查看的页码,输入q/Q退出。")
while True:
page_num_str = input(">>> ")
if page_num_str.upper() == "Q":
break
page_num = int(page_num_str)
page_start_index = (page_num - 1) * page_size + 1
page_end_index = page_num * page_size
for item in range(page_start_index - 1, page_end_index):
print(data_list[item], end=" ")
print()
- 面向对象
# 我的:
class Pagination(object):
"""
处理分页类
"""
def __init__(self, current_page_num):
"""
初始化分页类
Args:
total_page_num (): 总页数
"""
self.current_page_num = current_page_num
self.page_size = 2
def get_start_index(self):
"""
获取开始索引
Returns:
"""
page_start_index = (self.current_page_num - 1) * self.page_size + 1
return page_start_index
def get_end_index(self):
"""
获取结束索引
Returns:
"""
page_end_index = self.current_page_num * self.page_size
return page_end_index
# 老师的:
class Pagination(object):
"""
处理分页类
"""
def __init__(self, current_page_num, page_size=10, ):
"""
初始化分页类
Args:
current_page_num (int): 当前页数
page_size (int): 一页有多少条数据
"""
self.current_page_num = current_page_num
self.page_size = page_size
def start(self):
"""
获取开始索引
Returns:
"""
return (self.current_page_num - 1) * self.page_size + 1
def end(self):
"""
获取结束索引
Returns:
"""
return self.current_page_num * self.page_size
def main() -> int:
data_list = []
for i in range(1, 301):
data_list.append("alex-%s" % i)
print("请输入要查看的页码,输入q/Q退出。")
while True:
page_num_str = input(">>> ")
if page_num_str.upper() == "Q":
break
page_num = int(page_num_str)
pagination_obj = Pagination(page_num, 20)
page_start_index = pagination_obj.start()
page_end_index = pagination_obj.end()
for item in range(page_start_index - 1, page_end_index):
print(data_list[item], end=" ")
print()
return 0
if __name__ == '__main__':
main()
优化一下:
# 老师的:
class Pagination(object):
"""
处理分页类
"""
def __init__(self, current_page_num, page_size=10, ):
"""
初始化分页类
Args:
current_page_num (int): 当前页数
page_size (int): 一页有多少条数据
"""
self.current_page_num = current_page_num
self.page_size = page_size
@property
def start(self):
"""
获取开始索引
Returns:
"""
return (self.current_page_num - 1) * self.page_size + 1
@property
def end(self):
"""
获取结束索引
Returns:
"""
return self.current_page_num * self.page_size
def main() -> int:
data_list = []
for i in range(1, 301):
data_list.append("alex-%s" % i)
print("请输入要查看的页码,输入q/Q退出。")
while True:
page_num_str = input(">>> ")
if page_num_str.upper() == "Q":
break
page_num = int(page_num_str)
pagination_obj = Pagination(page_num, 20)
page_start_index = pagination_obj.start
page_end_index = pagination_obj.end
for item in range(page_start_index - 1, page_end_index):
print(data_list[item], end=" ")
print()
return 0
if __name__ == '__main__':
main()
另外一种分类方式:
class Pagination(object):
"""
处理分页类
"""
def __init__(self, current_page_num, page_size=10, ):
"""
初始化分页类
Args:
current_page_num (int): 当前页数
page_size (int): 一页有多少条数据
"""
self.current_page_num = current_page_num
self.page_size = page_size
@property
def start(self):
"""
获取开始索引
Returns:
"""
return (self.current_page_num - 1) * self.page_size
@property
def end(self):
"""
获取结束索引
Returns:
"""
return self.current_page_num * self.page_size
def show(self, data_list):
"""
打印分页的那一页的数据
Returns:
"""
page_start_index = self.start
page_end_index = self.end
print(data_list[page_start_index:page_end_index])
def main() -> int:
data_list = []
for i in range(1, 301):
data_list.append("alex-%s" % i)
print("请输入要查看的页码,输入q/Q退出。")
while True:
page_num_str = input(">>> ")
if page_num_str.upper() == "Q":
break
page_num = int(page_num_str)
pagination_obj = Pagination(page_num, page_size=10)
pagination_obj.show(data_list)
return 0
if __name__ == '__main__':
main()
写在最后
由于这些笔记都是从typora里面粘贴过来的,导致图片会加载失败,如果想要带图片的笔记的话,我已经上传至github,网址(https://github.com/wephiles/python-foundation-note)如果github上不去的话也可以去我的gitee下载,网址(https://gitee.com/wephiles/python-django-notes)。欢迎大家来下载白嫖哦,最后,如果可爱又善良的你能够给我github点个star,那你将会是这个世界上运气最好的人喔。