Python基础知识(三)
面向对象
-
面向过程
核心:"过程"
过程的终极奥义就是将程序流程化,进而程序的设计变得简单化,扩展性差过程就是"流水线",用来分步骤解决问题
-
面向对象
核心:"对象"
对象的终极奥义就是将程序"整合",提升程序的解耦合程度,设计复杂对象就是"容器",用来盛放数据与功能的
类
"""类即是类型;每一个类都可以当做一个新的数据类型。一切皆对象!!!"""
# 定义
类是"容器",该容器用来存放同类对象公有的数据与功能;某种角度也可以称类为对象
# 语法
class Student():
# 初始化对象独有的数据;
def __init__(self,name,age):
self.name = name # stu.__dict__['name'] = 'alan'
self.age = age # 必须默认返回None
'''注意:return 在这个方法中不能有返回值'''
# 选课方法
def choose_course(stu_dict,course):
stu_dict['course'].append(course)
print(f"学生{stu_dict['name']}选课{course}")
# 定义类发生的事情
立即执行类内代码
产生一个类的名称空间,把类内执行的名字都丢到名称空间中(丢到大字典中)
把类的名称空间绑定给__ dict __,类名.__dict__查看名称空间
对象(实例化)
# 先定义类,再调用类产生对象
stu = Student('alan',18) # 类体代码在定义阶段就运行了,实例化过程不会运行文件
'''
1.调用类产生一个空对象
2.python自动调用了Student.__init__方法 <=> 传入(空对象,'alan',18)
向对象中添加独有的属性
stu.__dict__['gender'] = 'male' <=> stu.gender = 'male'
'''
# 查看类的名称空间
print(Student.__dict__)
# 查看对象的名称空间
print(stu.__dict__) # {'name': 'alan', 'age': 18}
# 取值
print(stu.__dict__['name'])
print(stu.name)
属性的查找顺序与绑定方法
# 属性的查找:
类属性:在类中的属性就称为类属性
对象属性:在对象自己的名称空间中属性就是对象属性
注意:
属性的查找:先从自己的对象中查找,然后在去产生对象的类中取找
如果使用dict从对象的名称空间获取值,找不到会报错
使用.获取值,在自己的名称空间找不到会取类的名称空间查找
# 绑定方法
绑定给谁就是在操作谁
"""
类的数据属性:是给所有对象用的,id都是一样的,一旦类发生改变,对象均跟着改变,反之对象其中之一改,其他均不变;
类的函数属性:是绑定给对象用的,类调用位函数名,加括号直接调用;绑定给对象的方法,调用方法,将自己绑定方法,各不相同
"""
对象的绑定
绑定给对象的方法,对象来调用,会把自己当成第一个参数传到函数里面self
类的绑定
@classmethod
def func(cls):
pass
MyClass.func # 类的调用,同样地,将类本身传进去,通常是需要再这方法中产生对象
# 静态方法
''' 统计调用类,产生了多少个对象'''
class Count_obj():
# 定义count
count = 0
def __init__(self):
Count_obj.count += 1
静态方法/非绑定方法:既不绑定给类,也不绑定给对象
@staticmethod
def count_info():
print(f'产生了{Count_obj.count}个对象')
"""没有绑定给任何人,类可以调,对象也能调,本质就是一个函数"""
property装饰器
# 引入BMI
BMI指数为名词,但是要想得到BMI指数需要计算,所以需要将BMI伪装成数据属性
# property函数
格式:
property(fget=None, fset=None, fdel=None, doc=None)
fget:获取属性值的方法
fset:设置属性值的方法
fdel:删除属性值的方法
doc:属性描述信息
示例:
class Student:
def __init__(self):
self._age = None
def get_age(self):
print('查看属性时执行的代码')
return self._age
def set_age(self, age):
print('设置属性时执行的代码')
self._age = age
def del_age(self):
print('删除属性时执行的代码')
del self._age
age = property(get_age, set_age, del_age, '学生年龄') # 将age伪装起来,看起来就像数据属性
student = Student()
# 查看属性的文档字符串
Student.age.__doc__
# 修改属性
student.age = 18
# 查看属性
print(student.age)
# 删除属性
del student.age
封装
# 封装
整合代码,将数据和功能整合到一起,起到规范代码的作用,更好的组织了项目的整体结构,减少了代码的冗余度,提升了可扩展性
# 隐藏属性
如何隐藏
在属性名前加__前缀,就实现对外隐藏的效果
查看属性__dict__
Foo._Foo__x
特点
该隐藏只是语法上的变形
对外隐藏,对内不隐藏
因为__开头的属性,会在类定义阶段时统一发生变形
类内部也会发生变形,故造成了内部一起变形,不对内变形
目的
隐藏数据属性:
将数据隐藏起来就限制了类外部对数据的直接操作,隐藏不是目的,目的是为了保护
隐藏方法属性:
目的是隔离复杂度,暴露给使用者简单的接口
继承
# 什么是继承
继承是一种创建新类的方式,新建的类可称为子类或派生类,父类可称为基类或超类
python支持多继承,新建的类可以支持一个或多个父类
# 基本格式
class Parent1:
pass
class Parent2:
pass
class Sub1(Parent1): #单继承
pass
print(Sub1.__bases__) # 查看自己的父类---->(<class '__main__.Parent1'>,)
class Sub2(Parent1,Parent2): # 多继承
pass
# 在py2中有经典类和新式类的区别
新式类:继承了object类的子类,以及该子类的子类,子子类
经典类:没有继承object类的子类,以及该子类的子类,子子类
"""注意:在py3中没有继承任何类,默认继承object类,所以python3中都是新式类"""
# 解决de问题
类解决对象与对象之间代码冗余的问题,子类可以遗传父类的属性
继承解决的是类与类之间代码冗余的问题
object类丰富了代码的功能
继承的实现
# 教师类
class Teacher(Human):
def __init__(self, name, age, gender, level):
Human.__init__(self, name, age, gender) # 调用函数self不能省略,部分功能重复父类
self.level = level
# 属性查找顺序
"""对象>子类>父类>父父类"""
单继承背景下属性查找
class Foo():
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1() # 此时的self为obj
class Bar(Foo):
def f1(self):
print('Bar.f1')
obj = Bar()
obj.f2()
# 结果
Foo.f2
Bar.f1
'''查找顺序:
1.obj先从obj名称空间找,再从Bar名称空间中找,没有f2去他爹(Foo)中找
2.执行Foo中得f2,遇到self.f1()此时self是obj,是Bar的对象
3.执行Bar中的f1
'''
# 区别下:父类不想让子类的方法覆盖,可以私有化
class Foo:
def __f1(self): # _Foo__f1()
print('Foo.f1')
def f2(self):
#
print('Foo.f2')
self.__f1() # _Foo__f1()
class Bar(Foo):
def __f1(self): # # _Bar__f1()
print('Bar.f1')
obj = Bar()
obj.f2()
# 结果
Foo.f2
Foo.f1
'''Foo中f1私有化,所以输出的是Foo中的f1'''
# 菱形继承,mro列表
例如
class A(): # A为非object
def out_text(self):
print('from A')
class B(A):
def out_text(self):
print('from B')
class C(A):
def out_text(self):
print('from C')
class D(B,C):
pass
obj = D()
obj.out_text() # 结果---->from B
print(D.mro())
# [<class '__main__.D'>,
# <class '__main__.B'>,
# <class '__main__.C'>,
# <class '__main__.A'>,
# <class 'object'>] == mro列表,类已经该类产生的对象,查找顺序都是根据mro列表的顺序进行查找,mro列表是通过一个C3线性算法来实现的
mro查找顺序
子类先查,再查父类
当继承多个父类的时候,按mro列表顺序被检查
如果继承多个类,被继承类内具有相同的方法,先输出mro列表左边类的方法
深度优先---"""python2中经典类,一条道走到黑"""
class E:
pass
class F:
pass
class B(E):
pass
class C(F):
pass
class D:
def test(self):
print('from D')
class A(B, C, D):
pass
print(A.mro())
'''
查找顺序如下:
[<class '__main__.A'>,
<class '__main__.B'>, # B分支
<class '__main__.E'>,
<class '__main__.C'>, # C分支
<class '__main__.F'>,
<class '__main__.D'>, # D分支
<class 'object'>]
'''
obj = A()
obj.test()
# 结果为:from D
广度优先---新式类
class G: # python3中默认继承object,python2中要将G继承object
def test(self):
print('from G')
class E(G):
def test(self):
print('from E')
class F(G):
def test(self):
print('from F')
class B(E):
def test(self):
print('from B')
class C(F):
def test(self):
print('from C')
class D(G):
def test(self):
print('from D')
class A(B, C, D):
pass
obj = A()
obj.test() # 查找顺序为:obj->A->B->E->C->F->D-->G->object
# 结果
from B
# 多继承的优缺点
优点
子类可以同时遗传多个父类的属性,最大限度的重用代码
缺点
违反人的思维习惯,一个人有两个爹,代码的可读性会变差,不建议使用多继承,如果不可避免多个父类的继承,应该使用Mixins机制
"""继承表达的是一种“是”什么关系"""
# Mixins机制
多继承的正确打开方式:mixins机制
mixins机制核心:就是在多继承背景下尽可能底提升多继承的可读性
让多继承满足人的思维习惯--->什么“是”什么
class Vehicle:
pass
class FlyableMixin: # 规范多继承,类似于常量;Mixin结尾、able结尾、ible结尾
def fly(self):
pass
# 民航飞机
class CiviAircraft(FlyableMixin,Vehicle)
pass
#直升机
class Helicopter(FlyableMixin,Vehicle)
pass
class Car(Vehicle)
pass
'''表达是的关系,放在继承的末尾,特们都是交通工具'''
'''eg:飞机有飞的功能,是交通工具'''
# 在子类中重用父类的功能
方式一
指名道姓调用某一类下的函数==》不依赖于继承关系
People.__init__(self, name, age)
方式二
super()调用父类提供给自己的方法==》严格依赖继承关系
super(当前类, self).__init__(name, age, sex) # python3可以简写,super().__init(name, age, sex),参照的是mro()
class A:
def test(self):
print('from A')
super().test() # 属性发起者的顺序查找,mro规定了A的父类为B
'''用于调用下一个父类的方法B.test'''
class B:
def test(self):
print('from B')
class C(A, B):
pass
c = C()
c.test()
print(C.mro())
# 查找顺序如下
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
# 结果
from A
from B
组合/类方法补充
# 定义
一个类中以另外一个类的对象作为数据属性,就是类的组合,组合通常表示“有”的关系
# 目的
类和类之间代码冗余的问题可以通过继承来解决,或者super()方法等,其实我们还可以通过组合解决类与类之间代码冗余的问题
# 类的方法补充:
sel.__class__查看对象所属类
类名/对象名.__dict__查看类/对象名称空间
类名/对象名.__bases__查看父类
起始类名.__mro__打印继承顺序,py3从左到右查找
locals()查看局部名称空间
globals()查看全局名称空间
dirs(str)查看字符串所搭配的内置方法有哪些,查看内容可换
多态
# 什么是多态
同一种事务有的多种形态
eg:冰是水,水蒸汽也是水
# 多态性
多态性是指可以不用考虑对象具体类型的情况下直接使用对象,多态性是同一个操作,作用到不同实例而表现出不同实现方式的特性
通过派生,去定制子类特有的功能
# 多态性的优点
增加的程序的扩展性,使得每次来一个实例化的对象,都是以同一种形式去调用的,多态性还增加了程序的可扩展性,通过继承父类,减少了代码的冗余
# 鸭子类型
python推荐使用“鸭子类型”,是一种不依赖于继承,也可以实现不考虑对象类型而使用对象。
# 父类限制子类
抽象类 (abc模块)
class Animal(metaclass=abc.ABCMeta): # 统一所有子类的方法
@abc.abstractmethod
def say(self):
pass
下面但凡有子类继承了Animal父类的,必须含有say方法
继承
类的内置方法
# 定义
定义在类内部,__开头,__结尾,在满足某种条件下会"自动触发",不用自动主动调
# 如何使用
__init__()
初始化方法
__str__()
打印对象的时候,自动触发的函数
__del__()
在对象被删除的时候自动触发,程序运行结束即为对象被删除;在对象被删之前,应该回收操作系统的资源
__call__()
对象加括号自动触发,里面规定了__new__,__init__的执行顺序,比__new__还早
__enter__()
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
__exit__()
with中代码块执行完毕时执行此方法
__new__()
早于init方法,造空对象
反射
# 什么是反射
python是动态语言,而反射机制被视为动态语言的关键
动态语言:在程序运行过程中,识别数据类型
反射机制就是指程序运行过程中,动态获取程序信息以及动态调用对象的功能;--- "车到山前必有路"
# 为何用反射
函数接受到一个参数之后,有可能会访问到这个参数内部的一些属性,但是又不确定该参数内部有无该属性,__dict__()虽然也可以获取,但是仍欠妥
dir() # 该函数是可以查看名下的属性、方法
# 如何实现反射
dir()可以查看对象下所有的属性、方法;格式为["",...] # 列表套字符串
obj."name" 显然不行
==》通过操作"字符串"进而操作属性
obj.__dict__[dir(obj)[-1]] # 并不是所有的数据都有__dict__属性
"""引入四大反射内置函数"""
hasattr(obj,pro):按pro判断是否有无obj.pro属性
getattr(obj,pro,None):按pro判断是否有无obj.pro属性,没有返回None
setattr(obj,pro,value):设置obj.pro的值相当于obj.pro = value
delattr(obj,pro):删除obj.pro
元类
# 一切皆对象
# 类也是对象;元类就是用来实例化产生类的类
# 查看
用class关键字定义的所有的类以及内置的类都是由元类type帮助我们实例化产生的
print(type(int))
# calss机制
类的三大特征
类名 class_name
类的基类 class_bases
类体(字符串) class_body
exec(class_body, {}, class_dict) # class_dict用来存放运行类体代码产生的名称空间
调用基类***** # 自定义的关键位置
type(class_name, class_bases, class_dic)
# 自定义元类进而控制类的产生
class People(A, B, metaclass=Mymeta):
"""
注释信息
"""
pass
class Mymeta(type): # 只有继承了type类的类才是元类
def __init__(self, class_name, class_bases, class_dic):
if not class_name.istitle():
raise NameError('类名首字母必须大写!')
def __new__(cls, *args):
return super().__new__(cls, *args) # 会自动继承object父类
def __call__(): # 若想让对象加括号调用,则可以在对象里加括号调用
return
People = Mymeta(class_name, class_bases, class_dic) # 先造一个空对象People,再调用元类的Mymeta的__init__,完成初始化操作,最后返回值赋值给People
造空对象__new__(cls, (class_name, class_bases, class_dic))