P1 面向对象的概念 面向过程编程,在编写函数时,会出现你调用我,我调用你的情况,函数的参数也会出现不同和变化,在这种复杂情况下,使用面向过程编程,比较繁琐 而面对对象编程,首先要明确不同对象和它们的职责,很少出现互相调用函数的情况,明确了各自的功能,再编写函数,调用自己的方法,简化了复杂项目的开发工作。
P2、类和对象
类就是制造飞机的图纸,不能直接使用,但是有属性和方法
对象是这个图纸造出来的飞机,可以直接使用的,同时拥有了这类飞机的属性和方法
一个类可以有很多个对象,不同对象之间的属性可能不同
但是同属一个类的对象,它们之间的属性和方法不能多也不能少
P3 如何设计类?(很重要)
步骤: 需求分析>创建类名、属性、方法>大驼峰命名法
P4 dir 查看内置函数和方法
在Python中对象是无处不在的,之前学习的变量、数据、函数都是对象
P5 定义一个只包含方法的类
面向对象就是更大的封装
1、方法的参数中至少要有一个self参数
2、类的命名要符合大驼峰命名法
如何创建对象?
P6
将方法封装在类中,在调用时不关心实现的细节
类名加上小括号就可以创建一个对象,当对象创建完成,就可以用 “点”的方式调用类中封装的方法
注意:创建对象之后,变量中仍然记录的是对象在内存中的地址
P8 创建多个猫对象
类只有一个,使用相同的类可以创建多个不同的对象
P9 在类的外部给对象增加属性(但是不推荐使用)
在类的外部如何添加?对象名 . 属性名 = 属性内容
对象的属性应该封装在类的内部
P10 利用self参数在类封装的方法输出对象的属性
哪一个对象调用的方法,self就是那个对象的引用。
tom调用时,self就指向tom; lazy_tom调用时,self就指向lazy_tom
在方法内部想要访问属性,就可以使用“self . 一个属性”即可
可以用调试中单步执行的方法验证,当进入类中的方法时,self指向的是调用对象的地址
在调用方法的时候不用传递self参数,但是在创建方法的时候第一个参数必须是self参数
P11 类的外部给对象增加属性带来的问题——可能找不到属性
P12 初始化方法
只是去创建了一个新的对象,我们没有调用__init__方法,但是它仍会执行。
__init__是所有对象的内置方法,在使用类名()创建新的对象时,就会自动执行,所以我们可以使用这个初始化方法去为对象的属性设置初始值
P13 在初始化方法内部定义属性
保证以后创建新的对象时,都会有这些属性
P14 利用形参设置属性初始值
当不希望把方法中的变量写死时,就要用到形参
P15 del方法 和 对象的生命周期
当一个对象创建时,分配完内存空间后,会自动调用__init__方法
当一个对象被从内存销毁前,会自动调用__del__方法
所以可以改造这两个方法,找到这两个方法的应用场景
P16 __str__方法
改造__str__方法时,必须返回一个字符串
__str__方法的使用场景:改变 对象变量 默认打印出来的情况
P17 面对对象封装案例
在对象的方法内部,是可以直接访问对象的属性的
P19 多个对象之间的属性互不影响
P20 摆放家居
遇到问题,需求分析很重要
多个类中,被使用的类先开发
P24 一个对象的属性 可以由 另一个类创建的对象 构建
一个对象(士兵)的属性(枪)可以是另外一个类(枪)的对象(ak47),通过赋值语句赋值即可
这个对象的 属性变量 会指向 那个对象的内存空间
P25
在开发时,想要定义一个类的属性,但是又不知道给这个属性赋什么值时,可以设为None,
P28 判断是否None应该使用 is 身份运算符(根据PEP8的编码规范要求的,使用==也可以,但是不规范)
is 是用于判断两个变量的内存地址是否相等
==是用于判断内存地址中的值是否相等
P29 私有属性和私有方法(在属性或者方法前加两个下划线定义)
它的应用场景:对象的属性和方法只希望在对象的内部被使用,不希望在对象外部被访问到
P30 伪私有属性和方法
在Python中是没有真正意义上的私有,改写后,私有属性和方法都可以被访问
知道了Python对于私有属性和方法的处理方法,按照处理方法,就可以访问这些私有属性和方法了
使用"_类名__私有属性"或者"_类名__私有方法"就可以访问这些私有属性和方法了
继承(这里只讨论逻辑上的关系,物理上的实现方式不深究)
P31 问题的提出:单纯的封装可能出现重复的代码
继承实现了代码的重用,相同的代码不需要重复的编写
实际应用中:狗类具有动物类的特性,在编写狗类的时候,不使用继承,那我们就要复制动物类特性的代码,放在狗类代码中,我们不想使用这样的方法,同样的,当动物类的代码改变时,我们也要重现复制粘贴,再一次改变狗类的代码
>>“继承”的概念应运而生
P32 继承的语法和特点(在“子类名” 后加上 “父类名”就可以继承了)
继承:子类拥有父类的所有方法和属性
子类可以直接享受父类已经封装哈的方法和属性,不需要再次开发
两种术语的表达:
P34 继承的传递性
子类拥有父类的方法和属性,同时也拥有父类的 父类的(爷爷类)的方法和属性
P35 继承传递性的注意事项:只能向上继承父类的属性和方法
P36 方法的重写
使用场景:父类方法的实现,不能满足子类继承下来的这个方法的需求,就需要重写方法
如果子类中,重写了父类的方法,在使用子类对象调用方法时,会调用子类中重写的方法,而不会调用父类中的方法
具体的实现方法:在子类中定义一个和父类同名的方法并且实现
P37 方法重写的拓展
在父类方法的实现 满足不了子类实现的需求时,就需要重写父类的方法
>>>但是出现了我们同时想在重写的方法中调用父类的某些方法,就可以使用“super().方法名”调用父类的任意一个方法(这只是一种情况)
>>>>>>任何子类的方法中想要调用父类的方法,都可以使用 super().方法名 调用父类的方法
P39 子类对象中不能直接访问父类的私有属性和方法
1、子类对象不能在外部直接访问父类的私有属性和私有方法
2、并且子类对象不能在自己的方法内部,直接访问父类的私有属性和私有方法
>>子类对象不能直接访问父类的私有属性和私有方法
!!!这里虽然说的是不可访问父类的私有属性和方法,其实本质是子类自己没有继承下来这些私有属性和方法的意思。
关键:子类继承了父类的所有公有的属性和方法,但是私有的不继承,同时父类的那些私有的属性和方法,子类也不能访问和调用(这里只涉及到一个 子类的对象)(讨论的是如何访问父类的隐私的问题)
子类对象可以借助于父类的公有方法,访问父类的私有属性和私有方法
同时或者在子类的方法内部借助父类的公有方法
子类对象访问父类的公有方法天经地义、父类的公有方法访问自己的私有数据和私有方法也是天经地义>>>通过这两步,子类对象可以 借助于父类的公有方法,访问父类的私有属性和私有方法
P41 多继承
多继承可以让子类对象,同时拥有多个父类的属性和方法
P42 多继承的注意事项
例如:class C(A, B) C类创建的对象调用方法,当A,B有方法重名时,优先调用括号左边A类中的方法(先入为主)
P43 类的内置属性:__mro__属性(方法搜索顺序)
使用场景:在多继承中,要查看这个类中某个方法的搜索顺序的
可以使用print(类名.__mro__),会告诉你这个类中所有方法,是按照什么样的 类 的顺序执行的
即执行方法时,左边的类找不到这个方法时,会依次向右查找,第一次找到了就执行这个类中的方法
P44 新式类和旧式类
object类是Python3.x中所有类的基类,所以新建的类会默认有一些内置的方法和属性(都是从object继承下来的)
可以使用 dir(对象名) 查看
P45 多态:以继承和重写父类为前提
封装>>>继承>>>多态,三者是循序渐进的,递进的关系
继承以封装为基础,多态以继承为基础
P46 多态案例
P47 创建对象的过程、实例的概念
使用类名创建对象的过程:1、在内存中为对象分配内存空间 2、调用初始化方法__init__
重点:
1、不同对象 :各自的属性在各自的内存空间
2、对象的方法在内存中是如何保存的呢?
所有对象的方法在内存中只有一份。当一个对象调用这个对象的方法(代码其实是在类中)时,会将类中方法中的self参数指向这个对象实例
P48 类是一个特殊的对象
程序运行时,类同样会被加载到内存中
类对象在内存中只有一份,使用一个类,可以创建出很多的对象(每个对象的内存空间中保存着自己的属性,但是对象的实例方法是保存在类的内存空间中的)
上图区分:类属性、类方法、实例属性、实例方法
当对象调用方法时,是将对象实例的引用 传递给类中的self
对象的实例方法是保存在类的内存空间的
类对象也有自己的属性和方法
类属性、类方法是针对类而言的。实例属性、实例方法是针对对象而言的。它们有不同的内存地址
P49 类属性的定义和使用
类属性 类似于 全局变量,是属于这个类的,通过“类名 . 类属性名”访问
使用场景:使用类属性用来记录与这个类相关的特征,专门记录与类这个模板相关的特征,不用来记录与具体对象有关的特征
P50 类属性的查找机制
使用 “对象 . 类属性” 访问 类属性 时,先在对象属性内部找这个属性有没有,没找到再找类属性。
访问类属性:不建议使用“对象 . 类属性”,读取时没有问题,但是一旦使用了赋值语句,会给这个对象多添加一个属性
P51 使用“对象名+类属性”的赋值语句会创建对象实例的属性
第19行:使用“对象名+类属性”的赋值语句会创建对象实例的属性
核心:类属性的内存空间、各个对象实例的内存空间都是不同的
P52 类方法:针对类定义的方法
语法有两点注意:1、上面的修饰符 2、第一个参数是cls
P53 类方法的案例演练
P54 静态方法:既不访问类属性,也不访问实例属性的方法
静态方法的应用场景和定义方式:
调用“静态方法”和“类方法”都是通过 “类名.” 的形式调用
P55 综合案例分析
1、首先进行需求分析>>>设计类>>>分清类属性(和某个具体的对象无关的属性)和实例属性
2、需要访问“类属性”的方法设为“类方法”,需要访问“实例属性”的方法设为“实例方法(就是对象方法)”,既不访问“类属性”也不访问“实例属性”的方法设为“静态方法”
P58 单例设计模式
单例设计模式解决的问题就是:不论我们执行多少次的 “类名()” 创建对象,它们的内存地址都是相同的,即在系统中只有一个唯一的实例 (创建出来的对象在内存中只有唯一的一个实例)
P59 __new__方法
1、先了解下__new__方法
__new__方法是由object基类提供的内置静态方法,使用 “类名()” 创建对象时有两个主要作用:一,在内存为对象分配内存空间;二,返回这片内存空间的引用 给Python解释器,然后Python解释器会将这个对象的引用作为__init__初始化方法的参数传递给self ,self形参就指向了对象实例,由初始化方法对传递进来的对象进行初始化操作
2、为什么学习__new__方法?
就是因为我们要对分配空间的方法进行改造,改造的目的就是当我们使用“类名()”创建对象的时候,无论执行多少次,在内存中只会创建出一个对象的实例,这样就可以达到单例设计的目的
__new__方法只负责分配内存空间,不负责返回内存空间的引用给解释器,必须要return super().__new__(cls)
P60 单例设计模式的实现
P63 初始化动作只执行一次
解决办法:
P64 异常的概念
P65 捕获异常
在程序开发中,对于某些代码,不能确定是否按照正确的流程执行,例如让用户输入这部分,用户可能不会按照规范的方式去输入,则这部分就不能确定是否能正确的执行>>>可以增加try来捕获异常
注意:捕获异常中,不论是正确的执行,还是执行出现错误的处理,都会继续向下执行,不会因此停止下来
P66 根据错误类型捕获不同的异常
格式:“except 错误类型:” ---多写几个
P67 捕获未知错误---格式基本固定的
使用场景:无法预判错误的发生,且不想让程序因为Python解释器抛出异常而被终止时
result 是一个接收异常的变量名,可以更改
P68 异常捕获的完整语法
注意:else和finally中的代码什么时候会执行(下方有两种执行流程)
P69 异常的传递性---只需要在主函数中增加异常捕获,被调用的函数中都不用增加
在程序执行时,如果某个函数出现了异常,会把这个异常向上传递,传递给函数的调用一方,一直传递到主程序之后,如果没有发现异常处理,程序才会被终止。
P70 抛出raise异常---根据应用程序特有的业务需求,主动抛出异常
P71 主动抛出异常 演练(重点)
Exception是一个异常类,我们要主动抛出一个异常,就要先创建一个异常对象,然后再用raise关键字抛出这个异常对象
可以主动创建不同的异常对象,来捕获这些不同的(我们自定义的)异常
P72 模块
1、每一个以“.py”拓展名结尾的文件就是一个模块
2、模块名也是标识符,也要遵守标识符的命名规则
3、模块中的全局变量、函数、类都可以作为外界使用的工具,而模块就是一个工具包
推荐使用第二种导入方式(符合PEP8的标准)
使用“模块名 .”的方式使用模块提供的工具
P73 给模块起别名
1、别名要符合大驼峰命名法(单词首字母大写、单词与单词之间不需要下划线)
2、别名 只是在当前文件下使用,模块的实际名称还是原有的
P74 from...import导入
注意:
1、import导入是一次性导入模块中的所有工具,并且通过“模块 .”的方式使用函数、类或者访问全局变量的
2、from...import导入只导入部分工具,并且不需要通过“模块 .”的方式使用函数、类或者访问全局变量的
P75 from...import导入同名工具的注意事项 --- 导入同名函数,后导入的会将前面导入的覆盖掉
如果就是想调用不同模块中两个同名的工具,可以采用给前面一个函数起别名的方式
P76 from...import导入全部(不推荐使用)
优点:调入的工具包,在使用时,不用加前面的“模块名 . ”
P77 模块的搜索顺序
Python解释器导入模块时,先搜索当前目录,看是否有指定的模块(.py文件),如果有,就直接导入,如果没有,再搜索系统目录
1、一旦在当前目录下找到random模块,就不会去系统目录下去找了
2、使用 print(模块名.__file__) 可以查看这个模块的绝对路径
>>>模块名不要取和系统模块一样的名字
P78 开发原则---每个模块都可以导入>>>简单的导入会带来了一些问题
在一个模块中向外界提供的工具包括:全局变量、函数、类
能够直接执行的代码(例如print语句)不是向外界提供的工具
我们想要每一个文件都是可以被导入的,但是在导入文件时,文件中所有没有任何缩进的代码都会被执行(即导入时,有些能够直接输出的代码,我们不需要它这时候执行)
P79 __name__属性兼顾测试和导入两种模式
在hm_09___name__模块中顶格写“print(__name__)”会直接输出__main__
但是在导入“hm_09___name__模块”的“hm_09___name__测试导入”模块中,会输出“hm_09___name__模块”这个模块名
总结(原理):
1>>>__name__是Python的内置属性,不属于任何一个模块
2>>>执行当前A模块时,__name__等于__main__,但是在执行B模块中,(因为B模块导入了A模块,所以输出__name__的语句会被自动执行,但是它不等于__main__了),而是那个A模块中的__name__等于A模块名
开发时,常用的套路,这样主程序用于测试时会被执行,文件被当做模块导入时,又不会执行主程序中的代码
P80 包的概念
新建包的两种方式:(包的名字必须以字母下划线开头)
1、右键>New>Directory>命名包的名字>再右键>新建一个以“__init__”命名的Python文件>完成
2、右键>New>Python Package>命名包的名字
P81 如何使用 包 中模块
1、先在__init__.py文件中指定对外界提供的模块
2、再导入包名
3、最后使用包中模块中的函数的格式:包名。模块名。工具名
P82-P85 制作发布压缩包 的 三步
P86
P87 文件的类型
文本文件是可以用文本编辑软件查看的文件(但本质上还是二进制文件)
P88 文件的基本操作
操作文件的三个步骤(不论读写都是固定的)
open只是打开文件,并且返回文件的操作对象(即只是将文件的内存地址,放在内存中的文件目录中),文件中的具体内容并没有到内存中
P89 读取文件
文件名是区分大小写的
P90
1、第一次open打开文件时,文件指针会指向文件的开始位置
2、当执行了read方法后,文件指针会移动到文件内容的末尾(默认情况下)
P91 文件的打开方式
open方法默认是以只读的方式打开的(文件指针(目录)调入内存),这时调用write方法是不可写的
需要添加控制信息打开文件
重点掌握 只读、只写 的方式
“w”以只写的方式打开,如果调用write方法写内容进入文件,则会将原本已经存在的文件内容覆盖掉
“a”(append)以追加方式打开,如果调用write方法写内容进入文件,则会将内容追加在文件末尾
P92 readline方法,分行读取内容
P93 复制文件
P94 Python中如何执行文件/目录的管理?
在Python解释器中导入os模块,也可以执行文件/目录的管理操作(和在Linux终端中操作文件/目录是一样的)
P96 文本文件的编码方式
1个字节描述一个字符>>>1-6个字节描述一个字符(涵盖的字符更多了)
P97
在Python2.x的解释器中,执行带中文字符的文件时,使用utf-8的编码格式,是能够输出中文的
但是如果是要遍历某个带中文的字符串,是会输出???的,一个中文对应三个问号(一个汉字占三个字节,且ASCII编码是按一个字节编码的),因为解释器在遍历字符串时,仍然以字节为单位遍历字符串,如果要避免这种情况,遍历字符串时,在字符串前面要加 u
P99 eval函数
滥用eval会导致系统不安全!