Python面向对象
01 面向对象(OOP)基本概念
1.1 面向对象基本概念
- 我们之前学习的编程方式就是面向过程 的
- 面向过程和面向对象,是两种不同的编程方式
- 对比面向过程的特点,可以更好地了解什么是面向对象
1.1.1 过程和函数
- 过程是早期的一个编程概念
- 过程类似于函数,只能执行,但是没有返回值
- 函数不仅能执行,还可以返回结果
1.1.2 面向过程和面向对象基本概念
1) 面相过程 —— 怎么做?
- 把完成某一个需求的所有步骤从头到尾逐步实现
- 根据开发需求,将某些功能独立的代码封装成一个又一个函数
- 最后完成的代码,就是顺序地调用不同的函数
特点
- 注重步骤与过程,不注重职责分工
- 如果需求复杂,代码会变得很复杂
- 开发复杂项目,没有固定的套路,开发难度很大!
2) 面向对象 —— 谁来做? - 相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法
- 1.在完成某一个需求前,首先确定职责 —— 要做的事情(方法)
- 2.根据职责确定不同的对象,在 对象内部封装不同的方法(多个)
- 3.最后完成的代码,就是顺序地让不同的对象调用不同的方法
特点
- 注重对象和职责,不同的对象承担不同的职责
- 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
- 需要在面向过程基础上,再学习一些面向对象的语法
02 类和对象
2.1 类和对象的概念
2.1.1 类
- 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
- 特征被称为属性
- 行为被称为方法
- 类是一个模板,是负责创建对象的
2.1.2 对象
- 对象是由类创建出来的一个具体存在,可以直接使用
- 由哪一个类创建出来的对象,就拥有在哪一个类中定义的:
- 属性
- 方法
- 在程序开发中,应该先有类,再有对象
2.2 类和对象的关系
- 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象
- 类只有一个,而对象可以有很多个
- 不同的对象之间属性可能会各不相同
- 类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少
2.3 类的设计
- 在使用面相对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类!
- 在程序开发中,要设计一个类,通常需要满足一下三个要素:
- 类名这类事物的名字,满足大驼峰命名法
- 属性这类事物具有什么样的特征
- 方法这类事物具有什么样的行为
- 大驼峰命名法
- 每一个单词的首字母大写
- 单词与单词之间没有下划线
2.3.1 类名的确定
- 名词提炼法分析整个业务流程,出现的名词,通常就是找到的类
2.3.2 属性和方法的确定
- 对对象的特征描述,通常可以定义成属性
- 对象具有的行为(动词),通常可以定义成方法
- 提示:需求中没有涉及的属性或者方法在设计类时,不需要考虑
03 面向对象基础语法
3.1 定义简单的类
- 面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!
3.1.1 定义只包含方法的类
- 在
Python
中要定义一个只包含方法的类,语法格式如下:
class 类名:
def 方法1(self, 参数列表):
pass
def 方法2(self, 参数列表):
pass
- 方法的定义格式和之前学习过的函数 几乎一样
- 区别在于第一个参数必须是
self
,具体看后面介绍
注意:类名的命名规则要符合大驼峰命名法
3.1.2 创建对象
- 当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:
对象变量 = 类名()
- 在面向对象开发中,引用的概念是同样适用的,在
Python
中使用类创建对象之后,tom
变量中 仍然记录的是对象在内存中的地址 - 也就是
tom
变量引用了新建的猫对象 - 使用
print
输出对象变量,默认情况下,是能够输出这个变量引用的对象是由哪一个类创建的对象,以及
在内存中的地址(十六进制表示) - 提示:在计算机中,通常使用十六进制表示内存地址
- 十进制和十六进制都是用来表达数字的,只是表示的方式不一样
- 十进制和十六进制的数字之间可以来回转换
%d
可以以10 进制输出数字%x
可以以16 进制输出数字
3.2 方法中的self参数
3.2.1 给对象增加属性
在 Python
中,要给对象设置属性,只需要在类的外部的代码中直接通过 .
设置一个属性即可。但是不推荐使用,因为对象属性的封装应该封装在类的内部
tom.name = "Tom"
...
lazy_cat.name = "大懒猫"
提示
- 在日常开发中,不推荐在类的外部给对象增加属性
- 如果在运行时,没有找到属性,程序会报错
- 对象应该包含有哪些属性,应该封装在类的内部
3.2.2 self的意义和用法
- 由哪一个对象调用的方法,方法内的
self
就是哪一个对象的引用 - 在类封装的方法内部,
self
就表示当前调用方法的对象自己 - 调用方法时,程序员不需要传递
self
参数 - 在方法内部
- 可以通过
self.
访问对象的属性 - 也可以通过
self.
调用其他的对象方法
- 可以通过
- 在类的外部,通过
变量名.
访问对象的属性和方法 - 在类封装的方法中,通过
self.
访问对象的属性和方法
3.3 dir内置函数
- 在
Python
中对象几乎是无所不在的,我们之前学习的变量、数据、函数都是对象 - 在
Python
中可以使用以下两个方法验证:- 在标识符 / 数据后输入一个
.
,然后按下TAB
键,iPython
会提示该对象能够调用的方法列表 - 使用内置函数
dir
传入标识符 / 数据,可以查看对象内的所有属性及方法
- 在标识符 / 数据后输入一个
- 提示
__方法名__
格式的方法是Python
提供的内置方法 / 属性,稍后会给大家介绍一些常用的内置方法 / 属性
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | __new__ | 方法 | 创建对象时,会被自动调用 |
02 | __init__ | 方法 | 对象被初始化时,会被自动调用 |
03 | __del__ | 方法 | 对象被从内存中销毁前,会被自动调用 |
04 | __str__ | 方法 | 返回对象的描述信息,print 函数输出使用 |
- 提示"利用好
dir()
函数,在学习时很多内容就不需要死记硬背了
3.3.1 __init__方法
-
当使用
类名()
创建对象时,会自动执行以下操作:- 为对象在内存中分配空间 —— 创建对象
- 为对象的属性设置初始值 —— 初始化方法(init)
-
这个初始化方法就是
__init__
方法,__init__
(两个下划线)是对象的内置方法 -
__init__
方法是专门用来定义一个类具有哪些属性的方法! -
验证该方法在创建对象时会被自动调用
在初始化方法内部定义属性
- 在
__init__
方法内部使用self.属性名 = 属性的初始值
就可以定义属性 - 定义属性之后,再使用
Cat
类创建的对象,都会拥有该属性
初始化的同时设置初始值
- 在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对
__init__
方法进行改造 - 把希望设置的属性值,定义成
__init__
方法的参数 - 在方法内部使用
self.属性 = 形参
接收外部传递的参数 - 在创建对象时,使用
类名(属性1, 属性2...)
调用
3.3.2 __del__方法
- 在
Python
中- 当使用
类名()
创建对象时,为对象分配完空间后,自动调用__init__
方法 - 当一个对象被从内存中销毁前,会自动调用
__del__
方法
- 当使用
- 应用场景
__init__
改造初始化方法,可以让创建对象更加灵活__del__
如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__
方法
- 生命周期
- 一个对象从调用
类名()
创建,生命周期开始 - 一个对象的
__del__
方法一旦被调用,生命周期结束 - 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
- 一个对象从调用
3.3.3 __str__方法
- 在
Python
中,使用print
输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示) - 如果在开发中,希望使用
print
输出对象变量时,能够打印自定义的内容,就可以利用__str__
这个内置方法了 - 注意:
__str__
方法必须返回一个字符串
04 面向对象的封装
- 封装是面向对象编程的一大特点
- 面向对象编程
- 将属性和方法封装到一个抽象的类中
- 外界使用类创建对象,然后让对象调用方法
- 对象方法的细节都被封装在类的内部
提示:
- 在对象的方法内部,是可以直接访问对象的属性的
- 同一个类创建的多个对象之间,属性互不干扰!
- 被使用的类,通常应该先开发
- 一个对象的属性可以是另外一个类创建的对象
4.1 定义没有初始值的属性None
在定义属性时,如果不知道设置什么初始值,可以设置为None
None
关键字表示什么都没有- 表示一个空对象,没有方法和属性,是一个特殊的常量
- 可以将
None
赋值给任何一个变量
4.2 身份运算符is
身份运算符用于比较两个对象的内存地址是否一致 —— 是否是对同一个对象的引用
- 在 Python 中针对
None
比较时,建议使用is
判断
运算符 | 描述 | 实例 |
---|---|---|
is | is 是判断两个标识符是不是引用同一个对象 | x is y ,类似 id(x) == id(y) |
is not | is not 是判断两个标识符是不是引用不同对象 | x is not y ,类似 id(a) != id(b) |
is
与 ==
区别:
is
用于判断两个变量引用对象是否为同一个==
用于判断引用变量的值是否相等
4.3 私有属性和私有方法
在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到
- 私有属性就是对象不希望公开的属性
- 私有方法就是对象不希望公开的方法
4.3.1 私有属性和方法的定义方式
在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法
注:Python中其实是伪私有属性和私有方法,没有真正意义上的私有
- 在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到
- 处理方式:在名称前面加上
_类名 => _类名__名称
05 继承
5.1 单继承
5.1.1 继承的概念、语法和特点
① 继承的概念:子类拥有父类的所有方法和属性
② 继承的语法
class 类名(父类名):
pass
- 子类继承自父类,可以直接使用父类中已经封装好的方法,不需要再次开发
- 子类中应该根据功能,封装子类特有的属性和方法
③专业术语
Dog
类是Animal
类的子类,Animal
类是Dog
类的父类,Dog
类从Animal
类继承Dog
类是Animal
类的派生类,Animal
类是Dog
类的基类,Dog
类从Animal
类派生
④继承的传递性C类
从B类
继承,B类
又从A类
继承那么C类
就具有B类
和A类
的所有属性和方法- 子类拥有父类以及父类的父类中封装的所有属性和方法
方法的重写
当父类的方法实现不能满足子类需求时,可以对方法进行重写(override)
重写父类方法有两种情况:
- 覆盖父类的方法
- 对父类方法进行扩展
①覆盖父类的方法
- 如果在开发中,父类的方法实现和子类的方法实现,完全不同就可以使用覆盖的方式,在子类中重新编写父类的方法实现
- 具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现
- 重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
② 对父类方法进行扩展
- 如果在开发中,子类的方法实现中包含父类的方法实现父类原本封装的方法实现 是 子类方法的一部分就可以使用扩展的方式在子类中重写父类的方法
- 在需要的位置使用
super().父类方法
来调用父类方法的执行 - 代码其他的位置针对子类的需求,编写子类特有的代码实现
注:关于 super()
- 在
Python
中super
是一个特殊的类 super()
就是使用super
类创建出来的对象- 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
提示 - 在开发时,
父类名.方法(self)
和super().方法
两种方式不要混用 - 如果使用当前子类名调用方法,会形成递归调用,出现死循环
5.1.2 父类的私有属性和私有方法
- 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
- 子类对象可以通过父类的公有方法 间接访问到私有属性或私有方法
- 私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问
- 私有属性、方法通常用于做一些内部的事情
B
的对象不能直接访问__num2
属性B
的对象不能在demo
方法内访问__num2
属性B
的对象可以在demo
方法内,调用父类的test
方法- 父类的 test 方法内部,能够访问
__num2
属性和__test
方法
5.2 多继承
5.2.1 概念
- 子类可以拥有多个父类,并且具有所有父类的属性和方法
- 语法
class 子类名(父类名1, 父类名2...)
pass
5.2.2 多继承的使用注意事项
- 开发时,应该尽量避免不同的父类中存在同名的方法这种容易产生混淆的情况! —— 如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承
- Python 中的 MRO —— 方法搜索顺序(了解)
- Python 中针对类提供了一个内置属性
__mro__
可以查看方法搜索顺序 - MRO是
method resolution order
,主要用于在多继承时判断方法、属性的调用路径
- 在搜索方法时,是按照
__mro__
的输出结果从左至右的顺序查找的 - 如果在当前类中找到方法,就直接执行,不再搜索
- 如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索
- 如果找到最后一个类,还没有找到方法,程序报错
- Python 中针对类提供了一个内置属性
5.2.3 新式类与旧式(经典)类
object
是 Python
为所有对象提供的基类,提供有一些内置的属性和方法,可以使用 dir
函数查看
- 新式类:以
object
为基类的类,推荐使用 - 经典类:不以
object
为基类的类,不推荐使用 - 在
Python 3.x
中定义类时,如果没有指定父类,会默认使用object
作为该类的基类 ——Python 3.x
中定义的类都是新式类 - 在
Python 2.x
中定义类时,如果没有指定父类,则不会以object
作为基类 - 新式类和经典类在多继承时 —— 会影响到方法的搜索顺序
- 为了保证编写的代码能够同时在
Python 2.x
和Python 3.x
运行,今后在定义类时,如果没有父类,建议统一继承自object
class 类名(object):
pass
06 多态
面向对象三大特性
- 封装根据职责将属性和方法封装到一个抽象的类中
- 定义类的准则
- 继承实现代码的重用,相同的代码不需要重复的编写
- 设计类的技巧
- 子类针对自己特有的需求,编写特定的代码
- 多态不同的子类对象调用相同的父类方法,产生不同的执行结果
- 多态可以增加代码的灵活度
- 以继承和重写父类方法为前提
- 是调用方法的技巧,不会影响到类的内部设计
07 类属性和类方法
7.1 类的结构
7.1.1 术语 —— 实例
1.使用面向对象开发,第 1 步是设计类
2.使用类名() 创建对象,创建对象的动作有两步:
- ①在内存中为对象分配空间
- ②调用初始化方法
__init__
为 对象初始化
3.对象创建后,内存中就有了一个对象的实实在在的存在 —— 实例
因此,通常也会把:
- 创建出来的对象叫做类的实例
- 创建对象的动作叫做实例化
- 对象的属性叫做实例属性
- 对象调用的方法叫做实例方法
在程序执行时:
- 对象各自拥有自己的实例属性
- 调用对象方法,可以通过
self.
-访问自己的属性
-调用自己的方法
结论
- 每一个对象都有自己独立的内存空间,保存各自不同的属性
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部
7.1.2 类是一个特殊的对象
-
Python
中一切皆对象:class AAA
: 定义的类属于类对象obj1 = AAA()
属于实例对象
-
在程序运行时,类同样会被加载到内存
-
在
Python
中,类是一个特殊的对象 —— 类对象 -
在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例
-
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法
- 类属性
- 类方法
-
通过
类名.
的方式可以访问类的属性或者调用类的方法 -
对象的实例方法保存在对象的内存空间中,当一个对象调用实例方法时,对象会将引用当作参数传递给方法
7.2 类属性和实例属性
7.2.1 概念和使用
- 类属性就是给类对象中定义的属性
- 通常用来记录与这个类相关的特征
- 类属性不会用于记录具体对象的特征
7.2.2 属性的获取机制
-
在
python
中属性的获取存在一个向上查找机制,即若在对象中没找到该属性,会去其查找创建该对象的类中有无这个属性。
-
因此,要访问类属性有两种方式:
类名.类属性
对象.类属性
(不推荐,阅读代码可能混淆)
注意
- 如果使用
对象.类属性 = 值
赋值语句,只会给对象添加一个属性,而不会影响到类属性的值
7.3 类方法和静态方法
7.3.1 类方法
- 类属性就是针对类对象定义的属性
- 使用赋值语句在
class
关键字下方可以定义类属性 - 类属性用于记录与这个类相关的特征
- 使用赋值语句在
- 类方法就是针对类对象定义的方法
- 在类方法内部可以直接访问类属性或者调用其他的类方法
语法如下
@classmethod
def 类方法名(cls):
pass
- 类方法需要用修饰器
@classmethod
来标识,告诉解释器这是一个类方法 - 类方法的第一个参数应该是
cls
- 由哪一个类调用的方法,方法内的
cls
就是哪一个类的引用 - 这个参数和实例方法的第一个参数是
self
类似 - 提示使用其他名称也可以,不过习惯使用
cls
- 由哪一个类调用的方法,方法内的
- 通过
类名.
调用类方法时,不需要传递cls
参数 - 在方法内部
- 可以通过
cls.
访问类的属性 - 也可以通过
cls.
调用其他的类方法
- 可以通过
7.3.2 静态方法
- 在开发时,如果需要在类中封装一个方法,这个方法:
- 既不需要访问实例属性或者调用实例方法
- 也不需要访问类属性或者调用类方法
- 这时,可以把这个方法封装成一个静态方法
语法如下
@staticmethod
def 静态方法名():
pass
- 静态方法需要用修饰器
@staticmethod
来标识,告诉解释器这是一个静态方法 - 通过
类名.
调用静态方法
案例小结
- 实例方法 —— 方法内部需要访问实例属性
- 实例方法内部可以使用
类名.
访问类属性
- 实例方法内部可以使用
- 类方法 —— 方法内部只需要访问类属性
- 静态方法 —— 方法内部,不需要访问实例属性和类属性
- 如果方法内部,既需要访问实例属性,又需要访问类属性,应定义成实例方法
08 单例
8.1 单例设计模式
- 设计模式
- 设计模式是前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案
- 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性
- 单例设计模式
- 目的—— 让类创建的对象,在系统中只有唯一的一个实例
- 每一次执行
类名()
返回的对象,内存地址是相同的
8.1.1 __new__方法
- 使用类名()创建对象时,
Python
的解释器首先会调用__new__
方法为对象分配空间 __new__
是一个由object
基类提供的内置的静态方法
,主要作用有两个:- ①在内存中为对象分配空间
- ②返回对象的引用
Python
的解释器获得对象的引用后,将引用作为第一个参数,传递给__init__
方法- 重写
__new__
方法一定要return super().__new__(cls)
- 否则Python的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法
- 注意:
__new__
是一个静态方法,在调用时需要主动传递cls
参数
8.2 Python中的单例
- 单例 —— 让类创建的对象,在系统中只有唯一的一个实例
- 定义一个类属性,初始值是
None
,用于记录单例对象的引用 - 重写
__new__
方法 - 如果类属性
is None
,调用父类方法分配空间,并在类属性中记录结果 - 返回类属性中记录的对象引用
- 定义一个类属性,初始值是
- 对
__new__
方法改造,使得每次都会得到第一次被创建对象的引用
- 对初始化方法
__init__
进行改造,使得只执行一次初始化操作 - 解决办法
- 定义一个类属性
init_flag
标记是否执行过初始化动作,初始值为False
- 在
__init__
方法中,判断init_flag
,如果为False
就执行初始化动作 - 然后将
init_flag
设置为True
- 这样,再次自动调用
__init__
方法时,初始化动作就不会被再次执行了
- 定义一个类属性
09 异常
9.1 异常的概念
- 程序在运行时,如果
Python解释器
遇到到一个错误,会停止程序的执行,并且提示一些错误信息,这就是异常 - 程序停止执行并且提示错误信息这个动作,我们通常称之为:抛出(raise)异常
程序开发时,很难将 所有的特殊情况 都处理的面面俱到,通过 异常捕获 可以针对突发事件做集中的处理,从而保证程序的 稳定性和健壮性
9.2 捕获异常
9.2.1 简单的捕获异常语法
- 在程序开发中,如果对某些代码的执行不能确定是否正确,可以增加
try(尝试)
来捕获异常 - 捕获异常最简单的语法格式:
try:
尝试执行的代码
except:
出现错误的处理
try
尝试,下方编写要尝试代码,不确定是否能够正常执行的代码except
如果不是,下方编写尝试失败的代码
9.3 错误类型捕获
- 在程序执行时,可能会遇到不同类型的异常,并且需要针对不同类型的异常,做出不同的响应,这个时候,就需要捕获错误类型了
- 语法如下:
try:
# 尝试执行的代码
pass
except 错误类型1:
# 针对错误类型1,对应的代码处理
pass
except (错误类型2, 错误类型3):
# 针对错误类型2 和 3,对应的代码处理
pass
except Exception as result:
print("未知错误 %s" % result)
- 当
Python
解释器抛出异常时,最后一行错误信息的第一个单词,就是错误类型
捕获未知错误 - 在开发时,要预判到所有可能出现的错误,还是有一定难度的
- 如果希望程序无论出现任何错误,都不会因为
Python
解释器抛出异常而被终止,可以再增加一个except
语法如下:
except Exception as result:
print("未知错误 %s" % result)
9.3.1 异常捕获完整语法
- 在实际开发中,为了能够处理复杂的异常情况,完整的异常语法如下:
try:
# 尝试执行的代码
pass
except 错误类型1:
# 针对错误类型1,对应的代码处理
pass
except 错误类型2:
# 针对错误类型2,对应的代码处理
pass
except (错误类型3, 错误类型4):
# 针对错误类型3 和 4,对应的代码处理
pass
except Exception as result:
# 打印错误信息
print(result)
else:
# 没有异常才会执行的代码
pass
finally:
# 无论是否有异常,都会执行的代码
print("无论是否有异常,都会执行的代码")
else
只有在没有异常时才会执行的代码finally
无论是否有异常,都会执行的代码
9.4 异常的传递
- 异常的传递——当函数/方法执行出现异常,会将异常传递给函数/方法的调用一方
- 如果传递到主程序,仍然没有异常处理,程序才会被终止
提示 - 在开发中,可以在主函数中增加异常捕获
- 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的异常捕获中
- 这样就不需要在代码中,增加大量的异常捕获,能够保证代码的整洁
9.5 抛出raise异常
9.5.1 应用场景
- 在开发中,除了代码执行出错
Python
解释器会抛出异常之外 - 还可以根据应用程序特有的业务需求主动抛出异常
- 示例:提示用户输入密码,如果长度少于 8,抛出异常
- 当前函数只负责提示用户输入密码,如果密码长度不正确,需要其他的函数进行额外处理
- 因此可以抛出异常,由其他需要处理的函数捕获异常
9.5.2 抛出异常
Python
中提供了一个Exception
异常类- 在开发时,如果满足特定业务需求时,希望抛出异常,可以:
- 创建 一个
Exception
的对象 - 使用
raise
关键字抛出异常对象
10 模块和包
10.1 模块
10.1.1 模块的概念
- 模块是
Python
程序架构的一个核心概念 - 每一个以扩展名
py
结尾的Python
源代码文件都是一个模块 - 模块名同样也是一个标识符,需要符合标识符的命名规则
- 在模块中定义的全局变量 、函数、类都是提供给外界直接使用的工具
- 模块就好比是工具包,要想使用这个工具包中的工具,就需要先导入这个模块
10.1.2 模块导入的方式
10.1.2.1 import导入
- 提示:在导入模块时,每个导入应该独占一行
import 模块名1
import 模块名2
- 导入之后
- 通过
模块名.
使用模块提供的工具 —— 全局变量、函数、类 - 使用
as
指定模块的别名,如果模块的名字太长,可以使用as
指定模块的名称,以方便在代码中的使用
- 通过
import 模块名1 as 模块别名
注意:模块别名应该符合大驼峰命名法
10.1.2.2 from…import导入
- 如果希望从某一个模块中,导入部分工具,就可以使用
from ... import
的方式 import 模块名
是一次性把模块中所有工具全部导入,并且通过模块名/别名访问
# 从模块导入某一个工具
from 模块名1 import 工具名
- 导入之后
- 不需要通过
模块名.
- 可以直接使用模块提供的工具 —— 全局变量、函数、类
- 不需要通过
注意;如果两个模块,存在同名的函数,那么后导入模块的函数,会覆盖掉先导入的函数
- 开发时
import
代码应该统一写在代码的顶部,更容易及时发现冲突 - 一旦发现冲突,可以使用
as
关键字给其中一个工具起一个别名 - 导入模块中所有工具
from...import *
,在使用时不需要使用模块名.
的方式
# 从模块导入所有工具
from 模块名1 import *
10.1.3 模块的搜索顺序
Python
的解释器在导入模块时,会:- 搜索当前目录指定模块名的文件,如果有就直接导入
- 如果没有,再搜索系统目录
- 在开发时,给文件起名,不要和系统的模块文件重名
- Python 中每一个模块都有一个内置属性
__file__
可以查看模块的完整路径
注意:如果当前目录下,存在一个random.py
的文件,程序就无法正常执行了!这时,Python
的解释器会加载当前目录下的random.py
而不会加载系统的random
模块
10.1.4 原则 – 每一个文件都应该是可以被导入的
- 一个独立的
Python
文件就是一个模块 - 在导入文件时,文件中所有没有任何缩进的代码都会被执行一遍
10.1.4.1 __name__方法
__name__
属性可以做到,测试模块的代码只在测试情况下被运行,而在被导入时不会被执行__name__
是Python
的一个内置属性,记录着一个字符串- 如果是被其他文件导入的,
__name__
就是模块名 - 如果是当前执行的程序
__name__
是__main__
- 如果是被其他文件导入的,
在很多Python
文件中都会看到以下格式的代码:
# 导入模块
# 定义全局变量
# 定义类
# 定义函数
# 在代码的最下方
def main():
# ...
pass
# 根据 __name__ 判断是否执行下方代码
if __name__ == "__main__":
main()
10.2 包(Package)
10.2.1 概念
- 包是一个包含多个模块的特殊目录
- 目录下有一个特殊的文件
__init__.py
- 包名的命名方式和变量名一致,小写字母 +
_
,不能以数字开头 - 使用
import 包名
可以一次性导入包中所有的模块
10.2.2 建立包的两种方式
- 右键点击项目文件—New—Directory—新建目录—右键点击目录—New—Python File—新建名为
__init__
的py文件 - 右键点击项目文件—New—Python Package—新建包
- 要在外界使用包中的模块,需要在
__init__.py
中指定对外界提供的模块列表
# 从 当前目录导入模块列表
from . import send_message
from . import receive_message
10.3 发布模块
10.3.1 制作发布压缩包步骤
① 创建setuo.py
setup.py
的文件,其保存在包的PKG-INFO
文件中
from distutils.core import setup
setup(name="hm_message", # 包名
version="1.0", # 版本
description="itheima's 发送和接收消息模块", # 描述信息
long_description="完整的发送和接收消息模块", # 完整描述信息
author="itheima", # 作者
author_email="itheima@itheima.com", # 作者邮箱
url="www.itheima.com", # 主页
py_modules=["hm_message.send_message",
"hm_message.receive_message"])
- 字典参数的详细信息
② 构建模块 - 添加参数
build
$ python3 setup.py build
③ 生成发布压缩包
- 添加参数
sdist
$ python3 setup.py sdist
注意:要制作哪个版本的模块,就使用哪个版本的解释器执行!
10.3.2 安装模块
$ tar -zxvf message-1.0.tar.gz
$ sudo python3 setup.py install
10.3.3 卸载模块
直接从安装目录下,把安装模块的目录删除就可以
$ cd /usr/local/lib/python3.7/dist-packages/
$ sudo rm -r message*
10.3.4 pip3安装第三方模块
- 第三方模块通常是指由知名的第三方团队开发的并且被程序员广泛使用的
Python
包 / 模块 - 例如
pygame
就是一套非常成熟的游戏开发模块
pip3
是一个现代的,通用的Python
包管理工具- 提供了对
Python
包的查找、下载、安装、卸载等功能
安装和卸载命令如下:
# 将模块安装到 Python 2.x 环境
$ sudo pip install pygame
$ sudo pip uninstall pygame
# 将模块安装到 Python 3.x 环境
$ sudo pip3 install pygame
$ sudo pip3 uninstall pygame
- 在
Mac
下安装iPython
$ sudo pip install ipython
- 在
Linux
下安装iPython
$ sudo apt install ipython3
10.3.5 __file__属性
- 可以查看模块或包源文件的完整路径
11 文件
11.1 文件的概念
11.1.1 文件的概念和作用
- 计算机的文件,就是存储在某种长期储存设备上的一段数据
- 长期存储设备包括:硬盘、U 盘、移动硬盘、光盘…
文件的作用:将数据长期保存下来,在需要的时候使用
11.1.2 文件的存储方式
- 在计算机中,文件是以二进制的方式保存在磁盘上的
文本文件和二进制文件
- 文本文件
- 可以使用文本编辑软件查看
- 本质上还是二进制文件
- 例如:python 的源程序
- 二进制文件
- 保存的内容不是给人直接阅读的,而是提供给其他软件使用的
- 例如:图片文件、音频文件、视频文件等等
- 二进制文件不能使用文本编辑软件查看
11.2 文件的基本操作
11.2.1 操作文件的套路
在计算机中要操作文件的套路非常固定,一共包含三个步骤:
- ① 打开文件
- ② 读、写文件
- 读将文件内容读入内存
- 写将内存内容写入文件
- ③ 关闭文件
11.2.2 操作文件的函数/方法
- 在
Python
中要操作文件需要记住 1 个函数和 3 个方法
序号 | 函数/方法 | 说明 |
---|---|---|
01 | open | 打开文件,并且返回文件操作对象 |
02 | read | 将文件内容读取到内存 |
03 | write | 将指定内容写入文件 |
04 | close | 关闭文件 |
open
函数负责打开文件,并且返回文件对象read/write/close
三个方法都需要通过文件对象来调用
11.2.3 read方法——读取文件
open
函数的第一个参数是要打开的文件名(文件名区分大小写)- 如果文件存在,返回文件操作对象
- 如果文件不存在,会抛出异常
read
方法可以一次性读入并返回文件的所有内容close
方法负责关闭文件- 如果忘记关闭文件,会造成系统资源消耗,而且会影响到后续对文件的访问
- 注意:
read
方法执行后,会把文件指针移动到文件的末尾 - 提示:在开发中,通常会先编写打开和关闭的代码,再编写中间针对文件的读/写操作!
11.2.4 文件指针
- 文件指针标记从哪个位置开始读取数据
- 第一次打开文件时,通常文件指针会指向文件的开始位置
- 当执行了
read
方法后,文件指针会移动到读取内容的末尾- 默认情况下会移动到文件末尾
- 如果执行了一次
read
方法,读取了所有内容,文件指针移动到了文件末尾,再次调用read
方法不会读取到任何的内容
11.2.5 打开文件的方式
open
函数默认以只读方式打开文件,并且返回文件对象
语法如下:
f = open("文件名", "访问方式")
访问方式 | 说明 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头,这是默认模式。如果文件不存在,抛出异常 |
w | 以只写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件 |
a | 以追加方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入 |
r+ | 以读写方式打开文件。文件的指针将会放在文件的开头。如果文件不存在,抛出异常 |
w+ | 以读写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件 |
a+ | 以读写方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入 |
- 提示:频繁的移动文件指针,会影响文件的读写效率,开发中更多的时候会以只读、只写的方式来操作文件
11.3 按行读取文件内容
read
方法默认会把文件的所有内容一次性读取到内存- 如果文件太大,对内存的占用会非常严重
readline
方法
readline
方法可以一次读取一行内容- 方法执行后,会把文件指针移动到下一行,准备再次读取
复制小文件
复制大文件
11.4 文件/目录的常用管理操作
- 在终端 / 文件浏览器中可以执行常规的文件 / 目录管理操作,例如:创建、重命名、删除、改变路径、查看目录内容
- 在
Python
中,如果希望通过程序实现上述功能,需要导入os
模块
11.4.1 文件操作
序号 | 方法名 | 说明 | 示例 |
---|---|---|---|
01 | rename | 重命名文件 | os.rename(源文件名, 目标文件名) |
02 | remove | 删除文件 | os.remove(文件名) |
11.4.2 目录操作
序号 | 方法名 | 说明 | 示例 |
---|---|---|---|
01 | listdir | 目录列表 | os.listdir(目录名) |
02 | mkdir | 创建目录 | os.mkdir(目录名) |
03 | rmdir | 删除目录 | os.rmdir(目录名) |
04 | getcwd | 获取当前目录 | os.getcwd() |
05 | chdir | 修改工作目录 | os.chdir(目标目录) |
06 | path.isdir | 判断是否是文件 | os.path.isdir(文件路径) |
提示:文件或者目录操作都支持相对路径和绝对路径
11.5 文本文件的编码格式
- 文本文件存储的内容是基于字符编码的文件,常见的编码有
ASCII
编码,UNICODE
编码等 Python 2.x
默认使用ASCII
编码格式Python 3.x
默认使用UTF-8
编码格式
11.5.1 ASCII编码和UNICODE编码
ASCII编码
- 计算机中只有
256
个ASCII
字符 - 一个
ASCII
在内存中占用1 个字节的空间8
个0/1
的排列组合方式一共有256
种,也就是2 ** 8
Python 2.x
默认使用ASCII
编码格式,因此不能直接使用中文
UTF-8 编码格式
- 计算机中使用1~6个字节 来表示一个
UTF-8
字符,涵盖了地球上几乎所有地区的文字 - 大多数汉字会使用3 个字节表示
UTF-8
是UNICODE
编码的一种编码格式Python 3.x
默认使用UTF-8
编码格式,因此可以直接使用中文
11.5.2 Ptyhon 2.x 中如何使用中文
- 在
Python 2.x
文件的第一行增加以下代码,解释器会以UTF-8
编码来处理Python
文件
# *-* coding:utf8 *-*
- 也可以使用
# coding=utf8
11.5.3 Unicode字符串
- 在
Python 2.x
中,即使指定了文件使用UTF-8
的编码格式,但是在遍历字符串时,仍然会以字节为单位遍历字符串 - 要能够正确的遍历字符串,在定义字符串时,需要在字符串的引号前,增加一个小写字母
u
,告诉解释器这是一个unicode
字符串(使用UTF-8
编码格式的字符串)
12 eval 函数
eval()
函数十分强大 —— 将字符串当成有效的表达式来求值并返回计算结果
- 不要滥用
eval
,在开发时千万不要使用eval
直接转换input
的结果。因为用户可以直接通过eval调用终端命令进行各种操作。执行成功返回0,执行失败返回错误信息。
12.1 __import__方法
__import__('os').system('ls')
等价代码
import os
os.system("终端命令")