面向对象入门
本周内容:这周就四天,主要介绍面向对象入门。
day1: 面向对象编程基础
1. 面向对象编程介绍: 对象/类
- 面向过程
- 主要是将程序流程化,流水线,分步骤解决问题。
- 面向对象
- 对象就是“容器”,整合体,数据+功能。
- 学生容器 = 学生数据 + 学生功能
- 课程容器 = 课程容器 + 课程功能
- 这样扩展功能,直接找到容器即可。
- 对象就是“容器”,整合体,数据+功能。
- 类
- 也是容器,用来存放 数据 + 功能。
2. 面向对象编程
-
类的定义与实例化:
- 类的定义,可以放任何函数。
- 类体的代码在定义的时候就会执行。
- 只有函数体代码在调用阶段才会运行。
-
属性访问
-
Student.__dict__
-
一个字典,里面是Student类的所有成员和方法。(****)
-
.
点号访问类的属性
python的成员、方法都叫属性。都是用点来访问。函数就要记得加括号。 -
下面两句等价(*****)
Student.(stu_1, xxx) stu_1(xxx)
-
-
类属性与对象属性
- 类的
__dict__
是共有的属性 - 对象的
__dict__
是自己独有的属性。 - 都可以用 dict 字典的方式赋值,但是不推荐。
- 类的
-
__init__
方法:- 绝对不能有返回值。只能默认返回None。
- 想要在调用在一开始就执行的代码,都可以放入init。因为只有在调用的时候会执行。
- 应该存放独有的属性,但是可以存放任何种类代码。
-
属性查找顺序与绑定方法
-
属性查找顺序:先从对象找,再去类找。
-
类可以访问,一种是变量值,一种是函数地址。
-
(****)如果类的属性改了,所有对象的属性也会改的。
-
类的属性一变,所有对象都能感知到,比如什么count。
-
如果这个属性是通过对象改的,其他对象不变。
-
类中的函数属性:(obj,······)
- 如果调用对象的函数属性,默认会把自己当作参数传入。
- 默认写 obj 和 self
-
在 Python3 中,类就是一种类型。
-
思考题:如果 Student 本身没有 xxx
-
Student.xxx = 1
stu_1.xxx = 33333
print(stu_2.xxx)
如果想改一个地方,所有都改,直接改类。
需求:产生一个随机序列号,这样方便储存。 —> uuid
import uuid
print(uuid.uuid4())
day2: 封装与继承:
封装相关
隐藏属性 | 开放接口 | 隐藏数据 | 函数接口 | property
-
隐藏属性
-
什么是隐藏属性?
- 属性名之前添加
__
双下前缀 - 即私有成员,将封装的属性进行隐藏。
- 属性名之前添加
-
隐藏属性的特点
- 是变形操作。在属性名之前添加
__
双下前缀,可以产生隐藏效果。 - 其实也不等于java的private成员。非要访问还是可以访问的。
- __x 变形完以后如果要调用
- _Foo_x
- self.__x 也是一个道理。
- 这种隐藏对外不对内。其实和java对private成员的get方法一样。
- 定义以后再添加 __ 前缀的变量,不会变形。在定义的时候直接变形。
- 是变形操作。在属性名之前添加
-
-
开放接口
- 什么是开放接口?
- 就是set、get方法,主要可以添加严格的限制逻辑。
- 什么是开放接口?
-
隐藏数据
- 为什么要隐藏属性?
- 对外,可以加严格的控制逻辑,限制外部对数据的操作。
- 为什么要隐藏属性?
-
函数接口
继承相关(全是重点)
- 大纲1
- 继承介绍
- 抽象
- 属性查找
- 继承的实现原理:
- 大纲2
- 菱形问题
- 继承原理
- 深度优先与广度优先
- Mixins机制
- 派生与方法:
-
@property
- 是一个装饰器,改变类中定义的函数。(不修改原函数,添加一点新的功能。)
- 我们只需要知道怎么用,暂时不用了解底层原理。
- property 是干什么的?
-
案例1:people类,计算bmi。
- bmi本身是一个功能,函数,因为每次都得进行一次运算。不加()不能执行。但是听起来就像一个数据。
- 解决方法
- 函数前面加一个 @property 可以直接调用 bmi
-
案例2:
类中:name = property(get_name, set_name, del_name)
-
案例3:
类中定义了一大堆 name 方法,加装饰器
@property ==》对应 obj.name 操作
@name.setter ==》对应 obj.name = 'xxx’操作
@name.deleter ==》对应 del obj.name操作
-
- 是一个装饰器,改变类中定义的函数。(不修改原函数,添加一点新的功能。)
-
继承介绍
python 支持
- 继承类(多继承)
- 子类/派生类
- 父类/超类
# 单继承:
Class Sub1(Parent1):
pass
# 多继承:
Class Sub1(Parent1, Parent2):
pass
继承的优点:
- 解决类与类之间的代码冗余问题。
- 多继承,最大限度地重用代码。
继承的缺点:
- 违背人的思维逻辑。
- 代码可读性会变差。
- 可能引发可恶的菱形问题。
对于继承的建议
- 尽量避免多继承。
- 如果无法避免,就尽量使用 Mixins 机制,享受多继承的好处,又解决多继承可读性的问题。
新式类与经典类
-
新式类
- 祖宗是Object类 (Python3 中默认都是新式类。)
-
经典类
- 祖宗不是Object类
-
一个类是经典类还是新式类?
- 类名.bases
- 可以看到所有的父类
-
如何找出继承关系?抽象
-
属性查找顺序
类.y
obj.x -
继承的实现原理
······继承实现代码重用······
Class OldBoy:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
Class Student(OldBoy):
# 与父类构造函数相同,也init可以不写的
pass
class Teacher(OldBoy):
# 与父类构造函数大部分相同,又有需要增加的地方:
def __init__(self, name, age, gender, level ,salary):
OldBoy.__init__(self, name, age, gender)
self.level = level
self.salary = salary
单继承下的属性查找探讨
··········
Class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1
def f3(self):
print('Foo.f3')
Foo.f1(self) # 自己调用自己,可行。
Class Bar:
def f1():
print(''Bar.f1')
# 现场翻车 # ===》因为会找到自己的f1
b = Bar()
b.f2() # Foo.f2 Bar.f1,
# 如果改成:__f1(self),那就不会和产生命名产生冲突。
多继承下的属性查找探讨(菱形问题与钻石问题)
- 菱形问题
- 一个子类,继承了多个爹,最后汇聚到一个
非Object类
上。
- 一个子类,继承了多个爹,最后汇聚到一个
- 钻石问题
- 汇聚到
Object类
上,叫死亡钻石,不叫菱形继承,不是靠形状判定的。
- 汇聚到
- MRO(方法解析列表)
- C3算法,做一个列表。
- 只要定义一个类,就会有一个MRO列表。
- 类和类的列表都是参照MRO列表。
class D(B, C)::::#查找顺序是 D B C
print(D.mro())::: # 查找顺序
注意
- MRO是固定的,但是经典类和新式类不同,
- Python2 和Python3 的结果是不一样的。
新式类中
- Python2:第一个分支直接搜索到Object类。
- Python3:最后一个分支搜索搜到Object类。
总结
- 多继承是需要的,但是要使用 Mixins 机制。
day3
1. Mixins机制:
核心
- 提升多继承机制的可读性。
继承中,代码大量重用的问题:
- 交通工具类(可以飞),民航客机类/直升机类/汽车类,都继承了交通工具类。
- “飞”的功能大量重用,所以放在了交通工具类中。
- 所以,按照规则,汽车也能飞。
解决思路:
- 专门写一个 FlyableMixin,参杂了fly功能,让客机、直升机多继承继承这个类
Helicopter(FlyableMixin, Vahicle)
- 暗示原则:FlyableMixin,本质还是多继承,但是暗示我们功能单一。例如PI = 3.14,虽然本质还是变量,但是通过常量大写原则,暗示我们是常量,
Mixin 类设计原则
- 为形容词,Mixin、able、ible结尾,
- 功能必须单一,
- 一个类,表明身份的爹,只能有一个,Mixin 类可以多个。
- 比如我们可以查考socketsever库。
2. 子类重用父类的功能
子类如何重用爹的功能?
- 调用某个类下的函数
- super()
- 调用父类提供的方法
- 注意有括号,没有self
举例
- super().init(name, age, sex)
- 会得到一个特殊的对象,参照当前类的MRO,去父类中找属性。
- 注意是从父类中找属性,直接跳过自己。
··········
# ! python3.8
# -*- coding:utf-8 -*-
class OldBoy:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(OldBoy):
def __init__(self, name, age, sex, level, salary):
# 就是这句,重用父类的构造函数。
# OldBoy.__init__(self, name, age, sex)
# 这句是super()。注意别忘记加括号,没有self
super().__init__(name, age, sex)
# 然后记录新的参数
self.level = level
self.salary = salary
teacher1 = Teacher('egon', 13, 'male', 'high', 10000)
print(teacher1.name) # egon
print(OldBoy) # <class '__main__.OldBoy'>
print(Teacher) # <class '__main__.Teacher'>
print(teacher1) # <__main__.Teacher object at 0x7f9b1898b2b0>
print(OldBoy.__dict__) # {'__module__': '__main__', '__init__': <function OldBoy.__init__ at 0x7f9b189b7670>, '__dict__': <attribute '__dict__' of 'OldBoy' objects>, '__weakref__': <attribute '__weakref__' of 'OldBoy' objects>, '__doc__': None}
print(Teacher.__dict__) # {'__module__': '__main__', '__init__': <function Teacher.__init__ at 0x7f9b189b7790>, '__doc__': None}
print(teacher1.__dict__) # {'name': 'egon', 'age': 13, 'sex': 'male', 'level': 'high', 'salary': 10000}
print(OldBoy.__str__) # <slot wrapper '__str__' of 'object' objects>
print(teacher1.__str__) # <method-wrapper '__str__' of Teacher object at 0x7ff903a812b0>
总结
Class.__dict__
:所有的方法obj.__dict__
:所有的数据Class.__str__
:slot wrapperobj.__str__
:method-wrapper
3. 多态 / 鸭子类型 / import abc(子类都得重写这个方法)
什么是多态?
- 同一种事物有多种形态。
- 就是一个Animal类,派生了Dog类,Pig类,People类。
为什么要用多态?
怎么用多态?
- Dog类,Pig类,People类继承了Animal类,都有say()方法
- 可以传入各种对象。只要长得像,都有say就可以。
def animal_say(animal):
animal.say()
什么是鸭子类型?
- 长得像、走路晃、还会游泳,你就是鸭子。
- 通过一种规范,把所有类型做得像对象一样。
- 所以,Python下一切皆对象
抽象基类 abc
要么子类别用say方法,要用就得重写say方法。
import abc # 一个模块,抽象基类
# 作用:
# 统一所有的子类方法。
# 抽象基类不是为了自己实例化,而是为了统一标准。
def Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod # ##
def say(self):
pass
4. 绑定方法:调用者本身作为第一参数传入。
- 绑定给类的方法:调用者是类,自动传入的是类。
- 绑定给对象的方法:调用者对象,自动传入的是对象
- 非绑定方法:没有绑定给任何人,调用者可以是类/对象,没有自动传参的效果,就是个普通函数
5. @classmethod 方法
用装饰器,提供一种新的构造对象的方式
举例:加载配置,生成对象
import settings # 导入文件,下面用到
class Mysql:
def __init__(self, ip, port):
self.ip = ip
self.port = port
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.IP, settings.PORT)
obj = Mysql.from_conf() # 用这种方式替换构造函数
6. staticmethod:静态方法
类体的代码,不需要类,也不需要对象,就可以运行。
比如每造一个对象,自动生成一个id
7. 内置函数(补充)
enumerate:::循环一个容器,变成index和元素的2元元组
obj_1.__dir__():::一个对象,有什么属性?
divmod(10, 3):::求商和余数,返回元组
eval('1 + 2'):::执行字符串中的表达式:
abs():::绝对值:::
all():::容器中,所有元素都真,结果为真;空容器为真:
any():::容器中,只要一个为真,就是真;空容器为假:
???:bool
callable():::是不是函数、方法、类?:
chr():::ascii数字->字符:::范围:0x110000
ord:::字符转为ascii数字,
frozenset:::不可变集合:
isinstance(a, A):::判断对象a是不是类A的实例:
以前用 type(l) is list 判断类型,现在用什么?
pow(10, 2, 3):::10的2次方,对3取余数:
__repr__():::在交互式环境下,输入一个值,默认调用的方法
slice(1, 4, 2):::切片
zip():::两个同等长度的容器,结合成元组迭代器(留短):
time = __import__('time'):::文件里面存了一堆要导入的模块,读取的时候,怎么导入?