03 Python 进阶
变量的进阶
变量的引用
在Python中函数的参数传递以及返回值都是靠引用传递
引用
- 变量和数据分开存储
- 数据保存在内存中的一个位置
- 变量中保存着数据在内存的地址
- 变量中记录数据的地址,就叫做引用
- 使用
id()
函数可以查看变量中保存数据所在的内存地址
注意:
如果变量已经被定义,当给一个变量赋值的时候,本质上是修改了数据的引用
- 变量不再对之前的数据引用
- 变量改为对新赋值的数据引用
示例
Python中,变量类似于便签纸贴在数据上
In [1]: a = 1
In [2]: id(1)
Out[2]: 10935488
In [3]: id(a)
Out[3]: 10935488
In [4]: b = a
In [5]: id(b)
Out[5]: 10935488
In [6]: a = 2
In [7]: id(a)
Out[7]: 10935520
In [8]: id(b)
Out[8]: 10935488
函数的参数和返回值的传递
- 函数的参数
def test(num):
print("传入函数num保存数据大的内存地址%d" % id(num))
num = 1
print("num保存数据大的内存地址%d" % id(num))
test(num)
num保存数据大的内存地址10935488
传入函数num保存数据大的内存地址10935488
- 函数的返回值
def test():
str = "hello world"
print("字符串的内存地址%d" % id(str))
return str
str = test()
print("函数返回值的内存地址%d" % id(str))
字符串的内存地址140592406854768
函数返回值的内存地址140592406854768
可变和不可变类型
- 不可变类型:内存中的数据不允许被修改:
- 数字类型:int, bool, float, complex, long(2, x)
- 字符串:str
- 元组:tuple
- 可变类型
- 列表:list
- 字典:dict;注意key不可变
全局变量和局部变量
全局变量
- 函数内部不能直接修改全局变量的引用
- 如果在函数中需要修改全部变量,需要使用global声明
num = 10
def demo1():
# global 告诉Python解释器, num是一个全局变量
global num
# 此处不会创建局部变量, 这样global就起到了修改全局变量的作用
num = 100
print(num)
- 全局变量定义的位置
为了保证所有的函数都能够正确使用到全局变量,应该将全局变量定义在其他函数上方 - 全局变量命名的建议
避免局部变量和全局变量的混淆,可以在全局变量名前加q_
或ql_
函数
函数参数和返回值的作用
函数的返回值
- 利用元组返回多个值
def measure():
temp = 29
wetness = 50
# 函数返回元组可以省略小括号
return (temp, wetness)
print(measure())
(29, 50)
- 希望单独处理函数返回的元组的数据
gl_temp, gl_wetness = measure()
交换2个数字的值
方法1:
c = b
b = a
z = c
方法2:
a = a + b
b = a - b
a = a - b
方法3:
a, b = b, a
函数的参数
不可变参数和可变参数
- 在函数内部,针对参数使用赋值语句,会不会影响到调用函数时传递的实参变量
无论传递的参数是可变还是不可变,只要针对参数使用赋值语句,会在函数内部修改局部变量,不会影响到外部变量的引用 - 如果传递的参数是可变类型,在函数内部,使用该可变类型的方法修改了数据的内容,同样会影响到外部的数据
+=
在Python中列表变量调用+=
本质上是在执行列表变量的extend方法,不会修改变量的引用
缺省参数
- 列表的排序
gl_num_list.sort(reverse=True)
指定函数的缺省参数
- 需要使用常用最常见的值作为默认值
- 如果一个参数的值不能确认,则不应该设置默认值
注意事项
- 带有默认值的缺省参数在参数列表末尾
- 调用带有多个缺省参数的函数,需要指定参数名
多值参数
即参数个数不定
- Python中的多值参数:
- 参数名前增加一个
*
可以接受元组 - 参数名前增加两个
**
可以接受字典
- 参数名前增加一个
- 一般在给多值参数命名时,习惯使用以下两个名字
*args
:存放元组参数**kwargs
:存放字典参数
args
:arguments
kw
:keyword
,kwargs
:键值对参数
def demo(num, *grgs, **kwargs):
print(num)
print(args)
print(kwargs)
栗子:计算任意多个数字的和
def sum(*args):
num = 0
for n in args:
num += n
return num
元组和字典的拆包
即调用带有多值参数的函数的方式
def demo(*args, **kwargs):
gl_nums = (1, 2, 3)
gl_info = {"name":"小明", "age":18}
demo(*gl_nums, **gl_info)
递归
- 递归出口
面向对象
类和对象
类
- 类是对一群具有相同特征或行为的事物的一个统称,是抽象,不能直接使用
- 特征被称为属性
- 行为被称为方法
- 类是一个模板,是负责创建对象的
对象
- 对象是由类创建出来的一个具体存在,可以直接使用
- 由哪一个类创建出来的对象,就拥有在哪一个类中的定义:属性,方法
类的设计
设计类的三要素
- 类名:大驼峰命名法
- 属性
- 方法
-大驼峰命名法
- 首字母大写
- 单词之间没有下划线
类名的设计
属性和方法的确定
面向对象的基础语法
dir内置函数
使用 dir 传入 标识符/数据 ,可以查看对象内的所有属性和方法
__方法名__
格式的方法是Python内置的方法/属性
- 属性
方法名 | 作用 |
---|---|
__new__ | 创建对象时,会被自动调用 |
__init__ | 对象被初始化,会自动调用 |
__del__ | 对象被从内存中销毁前,会被自动调用 |
__str__ | 返回对象的描述信息,print函数输出使用 |
定义类
-
方法中的
self
参数- 给对象增加属性:
对象.新增属性
,不推荐使用 self
:对象本身的引用
- 给对象增加属性:
-
__init()__
初始化方法__init()__
方法用于定义一个类具有的属性- 在初始化方法内部定义属性:
self.属性名 = 属性的初始值
- 可以使用参数初始化属性
-
__del__
方法- 如果希望在对象被销毁前,做一些事情,就可以考虑该方法
-
__str__
方法- 必须返回一个字符串
面向对象封装
身份运算符
身份运算符用于比较两个对象的内存地址是否一致,即是否是对同一个对象的引用
运算符 | 描述 |
---|---|
is | |
is not |
is 与 ==
is
:判断两个变量引用对象是否为同一个==
:用于判断引用变量的值是否相等
私有方法和私有属性
在定义属性或方法前加两个下划线__
伪私有方法和私有属性
继承
class 类名(父类名):
pass
单继承
方法的重写
-
覆盖父类的方法
-
对父类方法进行扩展
- 使用
super().父类方法
调用父类方法的实现
- 使用
父类的私有属性和私有方法
- 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
- 子类可以通过父类的公有方法间接访问到父类的私有属性或私有方法
多继承
class 子类名(父类名1, 父类名2,...):
pass
- 注意
- 如果父类存在同名的属性或方法,应该避免多继承
class A:
def demo(self):
print("A demo")
def test(self):
print("A test")
class B:
def demo(self):
print("B demo")
def test(self):
#### #### #### #### print("B test")========
class C(A, B):
pass
c = C()
c.test()
c.demo()
A test
A demo
MRO - 方法搜索顺序
__mro__
(methon resolution order)内置属性可以查看方法搜索顺序,主要用于在多继承时判断方法,属性的调用路径
搜索顺序从左至右
print(C.__mro__)
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
### 新式类和旧式类
建议定义类,如果没有父类,建议统一继承自object
.
Python3.X
默认以object
为父类
多态
- 不同的子类对象调用相同的父类方法,产生不同的执行结果
- 增加代码的灵活度
- 以继承和重写父类方法为前提
类的结构
实例
类是一个特殊的对象
-
class A:
:类对象 -
obj = A()
:实例对象 -
程序运行时,类会被加载到内存
-
类对象在内存中只有一份
-
除了实例封装的属性和方法外,类对象还可以拥有自己的属性和方法
- 类属性:
类名.属性
- 类方法:
类名.方法名()
- 类属性:
类属性和实例属性
- 类属性就是给类对象中定义的属性
- 用于记录与这个类相关的特征
- 类属性不会用于记录具体对象的特征
class Tool(object):
# 类属性
count = 0
def __init__(self, name):
self.name = name
Tool.count += 1
属性的获取机制
- 属性的获取,是向上查找机制
- 使用
对象.属性名 = 值
赋值语句,只会给对象添加一个属性,而不会影响到类属性的值
类方法和静态方法
- 类属性:针对类对象定义的属性
- 使用赋值语句在class关键字下方可以定义类属性
- 类属性用于记录与这个类相关的特征
- 类方法就是针对类对象定义的方法
- 在类方法内部可以直接访问类属性或调用其他的类方法
类方法
@classmethod
def 类方法名(cls):
pass
@classmethod
:修饰器,告诉解释器这是一个类方法- 参数
cls
:- 由哪一个类调用的方法,方法内的
cls
就是哪一个类的引用 - 这个参数和实例方法的第一个参数
self
类似 - 提示使用其他名称也可以
- 由哪一个类调用的方法,方法内的
- 通过
类名.
调用类方法时,不需要传递cls参数 - 在方法内部
- 通过
cls.
访问类的属性 - 通过
cls.
调用其他的类方法
- 通过
案例
class Tool(object):
count = 0
#classmethod
def show_tool_count(cls):
print("Tool Counts %d" % cls.count)
def __init__(self, name):
Tool.count += 1
静态方法
- 不需要访问实例属性或者调用实例方法
- 不需要访问类属性或者调用类方法
@staticmethod
def 静态方法名():
pass
通过类名.
调用静态方法
方法综合实例
需求
- 设计一个Game类
- 属性:
- 定义类属性top_score
记录游戏的历史最高分
- 定义一个实例属性player_name
记录当前游戏的玩家姓名 - 方法:
- 静态方法show_help
显示游戏帮助信息
- 类方法show_top_score
显示历史最高分
- 实例方法start_game
开始当前玩家的游戏 - 主程序步骤
- 查看帮助信息
- 查看历史最高分
- 创建游戏对象,开始游戏
class Game(object):
top_score = 0
def __init__(self, player_name):
self.player_name = player_name
@staticmethod
def show_help():
print("Welcome!")
@classmethod
def show_top_score(cls):
print("历史最高分 %d" % cls.top_score)
def start_game(self):
print("%s开始游戏" % self.player_name)
- 小结:
- 实例方法:在方法内部需要访问实例属性,使用
类名.
访问类属性 - 类方法:方法内部只需要访问类属性
- 静态方法:方法内部,不需要访问实例属性和类属性
- 如果在方法内部既需要访问实例属性,又需要访问类属性,应该定义成实例方法
- 实例方法:在方法内部需要访问实例属性,使用
设计模式
单例设计模式
设计模式:针对某一问题总结而成的成熟的解决方案
-
单例设计模式:让类创建的对象,在系统中只有唯一的一个实例;每一次执行
类名()
返回的对象,内存地址都是相同的 -
应用场景
- 音乐播放软件
- 回收站对象
- 打印机对象
- …
__new__
方法
- 在使用
类名()
创建时,解释器会调用__new__
方法为对象分配空间 __new__
是一个由object
基类提供的内置的静态方法- 在内存中为对象分配空间
- 返回对象的引用
- 解释器获得对象的引用后,将引用作为第一个参数,传递给
__init__
方法 - 重写
__new__
方法一定要return super().__new__(cls)
,否则解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法 - 注意:
__new__
是一个静态方法,在调用时需要主动传递cls
参数
重写
- 没有
return super().__new__(cls)
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
print("创建对象,分配空间")
def __init__(self):
print("播放器初始化")
player = MusicPlayer()
print(player)
创建对象,分配空间
None
return super().__new__(cls)
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
print("创建对象,分配空间")
return super().__new__(cls)
def __init__(self):
print("播放器初始化")
player = MusicPlayer()
print(player)
创建对象,分配空间
播放器初始化
<__main__.MusicPlayer object at 0x7f9377a47748>
Python中的单例
- 定义一个类属性,初始值是None,用于记录单例对象的引用
- 重写
__new__
方法 - 如果类属性
is None
调用父类方法分配空间,并在类属性中记录结果 - 返回类属性中记录的对象引用
class MusicPlayer(object):
instance = None
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
只进行一次初始化操作
解决:
- 定义一个类属性
init_flag
标记是否执行过初始化操作,初始值为false - 在
__init__
方法中,判断init_flag
,如果为false
就执行初始化操作 - 然后将
init_flag
设置为True
- 此后初始化不会被执行
class MusicPlayer(object):
instance = None
init_flag = False
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
if MusicPlayer.init_flag:
return
MusicPlayer.init_flag = True
异常
异常的概念
捕获异常
简单的捕获异常语法
try:
pass
except:
pass
错误类型捕获
- 针对不同类型的异常,做出不同的响应
- 语法
try:
pass
except 错误类型1:
pass
except (错误类型2, 错误类型3):
pass
except Exception as result:
print("未知错误 %s" % result)
- 当解释器抛出异常时,最后一行错误信息的第一个单词,就是错误类型
要求用户输入整数
try:
num = int(input("请输入整数: "))
result = 8/num
print(result)
except ValueError:
print("请输入正确的整数")
except ZeroDivisionError:
print("除 0 错误")
捕获未知错误
如果希望程序无论出现任何错误,都不会因为解释器抛出异常而被终止,需要增加一个except
except Exception as result:
print("未知错误 %s" % result)
异常捕获完整语法
try:
# 尝试执行的代码
pass
except 错误类型1:
#
pass
except 错误类型2:
#
pass
except (错误类型3, 错误类型4):
#
pass
except Exception as result:
# 打印错误信息
print(result)
else:
# 没有异常才会执行的代码
pass
finally:
# 无论是否有异常,都会执行的代码
print("无论是否有异常,都会执行的代码")
异常的传递
异常的传递:当函数/方法执行出现异常,会将异常传递给函数/方法的调用一方, 如果传递到主程序,仍然没有异常处理,程序才会被终止.
- 提示
在开发中,可以在主函数中增加异常捕获,而在主函数中调用的其它函数,只要出现异常,都会传递到主函数的异常捕获中,这样就不需要在代码中,增加大量的异常捕获,能够保证代码的整洁
排除raise异常/主动抛出异常
在开发中,除了代码执行错误解释器会抛出异常之外,还可以根据应用程序特有的业务需求主动抛出异常
示例:提示用户输入密码长度
- 如果用户输入密码长度少于8,抛出异常
def input_pwd():
pwd = input()
if len(pwd) >= 8
return pwd
print("主动抛出异常")
ex = Exception("密码长度不够")
raise ex
模块
模块的概念
- 每一个以
.py
为扩展名的Python源代码文件都是一个模块,模块命也要符合标识名的命名规则 - 在模块中定义的全局变量,函数,类都是提供给外界直接使用的工具
- 要使用模块,需要先进行导入
导入模块的两种方式
import
导入
import 模块1,模块2
import 模块1
import 模块2
- 导入之后
- 通过
模块名.
使用模块提供的工具
- 通过
使用as指定模块的别名
如果模块名太长,使用as指定模块的名字
import 模块名 as 别名
from...import...
局部导入
- 从某一个模块中,导入部分工具
import 模块名
是一次性把模块所有工具全部导入,并且通过模块名/别名访问
from 模块名 import 工具
- 导入之后
- 不需要使用
模块名.
- 可以直接使用模块提供的工具-全局变量,函数,类
- 不需要使用
- 注意
- 如果两个模块,存在同名函数,那么后导入模块的函数,会覆盖掉先导入的函数
- 开发时import代码应该统一写在代码顶部,更容易及时发现冲突
- 一旦发现冲突,可以使用as关键字给其中一个工具起一个别名
2.1.from...import*
from 模块名 import *
导入所有工具,不推荐使用,若有函数重名不会有提示
模块的搜索顺序
- 解释器在导入模块时会搜索当前目录指定模块名的文件,如果有会直接导入,如果没有,再搜索系统目录
- 自己创建的模块名不要与系统模块名重复
- Python中每一个模块都有一个内置属性
__file__
可以查看完整路径
原则:每一个文件都应该是可以被导入的
-
在导入文件时,文件中所有没有任何缩进的代码都会被执行一遍
-
实际开发:
- 开发人员通常会在模块下方增加一些测试代码,仅在模块内部使用,而被导入其他文件中不需要执行
-
如何防止被导入除了全局变量,函数,类外的代码被执行?
__name__
属性
__name__
属性:测试模块的代码,只能在测试情况下被运行,而在被导入时不会被执行
- 如果是被其他文件导入,
__name__
就是模块名 - 如果是当前执行的程序,
__name__
是__main__
# 导入模块
# 定义全局变量
# 定义类
# 定义函数
# 在代码最下方
def main():
#
pass
# 根据__name__判断是否执行下方代码
if __name__ == "__main__":
pass
包
包的概念
包是一个包含多个模块的特殊目录,目录下有一个特殊文件__init__.py
,包名的命名方式和变量名一致,小写字母 + _
使用import 包名
可以一次性导入包中所有的模块
__init__.py
要在外界使用包中的模块,需要在__init__.py
中制定对外界提供的模块列表
# 从当前目录导入模块列表
from . import send_message
from . import receive_message
发布模块
制作发布压缩包步骤
- 创建
setup.py
`
- 构建模块
python3 setup.py build
- 生成发布压缩包
python3 setup.py sdist
安装模块
tar -zxvf 模块.tar.gz
sudo python3 setup.py install
卸载模块
cd /usr/local/lib/python3.x/dist-packages/
sodu rm -r 模块
pip安装第三方模块
先安装pip
# 1. 更新系统包
sudo apt-get update
sudo apt-get upgrade
# 2. 安装Pip
sudo apt-get install python3-pip
# 3. 检查 pip 是否安装成功
pip3 -V
sudo pip3 install 模块
sudo pip3 uninstall 模块
sudo pip3 search 模块
python3 -m pygame.examples.aliens
文件
文件的概念
文件的存储方式
文件的基本操作
文件操作的过程
- 打开文件
- 读写文件
- 关闭文件
操作文件的函数/文件
函数/方法 | 说明 |
---|---|
open | 打开文件,并返回文件操作对象 |
read | 将文件内容读取到内存 |
write | 将制定内容写入文件 |
close | 关闭文件 |
read
- open函数的第一个参数为要打开文件的文件名
- 如果文件存在,返回文件操作对象
- 如果文件不存在,会抛出异常
- read方法可以一次性读入并返回文件的所有内容
- close方法负责关闭文件
- 方法执行后,会把文件指针移动到文件的末尾
file = open("README")
text = file.read()
print(text)
file.close()
-
提示:
- 在开发中,通常会先编写打开和关闭的代码,再编写中间针对文件的读/写操作
-
文件指针:
- 文件指针标记从哪个位置开始读取数据
- 第一次打开文件时,通常文件指针会指向文件的开始位置
- 当执行了read方法后,文件指针会移动到读取内容的末尾
- 默认情况下会移动到文件末尾
-
如果执行了一次read方法后,读取了所有内容,再次调用read方法,不会读取到任何内容
打开文件的方式
open
函数默认只读方式打开文件,并且返回文件对象
f = open("文件名", "访问方式")
-
访问方式
访问方式 | 说明
—|---
r | 以只读方式打开文件.文件的指针将会放在文件的开头.默认模式.如果文件不存在,抛出异常
w | 以只写方式打开文件.如果文件存在会被覆盖.如果文件不存在,创建新文件
a | 以追加方式打开文件.如果该文件已存在,文件指针将会放在文件的结尾.如果文件不存在,创建新文件进行写入
r+ | 以读写方式打开文件.文件的指针将会放在文件的开头.如果文件不存在,抛出异常
w+ | 以读写方式打开文件.如果文件存在会覆盖.如果文件不存在,创建新文件
a+ | 以读写方式打开文件.如果该文件已存在,文件指针将会放在文件的结尾.如果文件不存在,创建新文件进行写入 -
频繁的移动文件指针,会影响文件的读写效率,通常会以只读,只写的方式操作文件
按行读取文件内容
-
readline
- 一次读取一行内容
- 方法执行后,会把文件指针移动到下一行,准备再次读取
-
读取大文件
# 打开文件
file = open("README")
while True:
# 读入一行内容
text = file.readLine()
# 判断是否读到内容
if not text:
break
# 每读取一行的末尾已经有了一个`\n`
print(text, end="")
# 关闭文件
file.close()
复制文件
-
用代码的方式,来实现文件复制过程
-
小文件复制
- 打开一个已有的文件,读取完整内容,并写入到另外一个文件
file_read = open("README")
file_write = open("README[复件]")
text = file_read.read()
file_write.write(text)
file_read.close()
file_write.close()
- 大文件复制
- 打开一个已有文件,逐行读取内容,并顺序写入到另外一个文件
file_read = open("README")
file_write = open("README[复件]")
while True:
text = file_read.read()
if not text:
break
file_write.write(text)
file_read.close()
file_write.close()
文件/目录的常用管理操作
- 创建,重命名,删除,改变路径,查看目录内容…
- 导入os模块
import os
文件操作
方法名 | 说明 | 示例 |
---|---|---|
rename | 重命名文件 | os.rename(源文件名,目标文件名) |
remove | 删除文件 | os.remove(文件名) |
目录操作
方法名 | 说明 | 示例 |
---|---|---|
listdir | 目录列表 | os.listdir(目录名) |
mkdir | 创建目录 | os.mkdir(目录名) |
rmdir | 删除目录 | os.rmdir(目录名) |
getcwd | 获取当前目录 | os.getcwd() |
chdir | 修改当前工作目录 | os.chdir(目录名) |
path.isdir | 判断是否是目录 | os.path.isdir(文件路径) |
- 文件/目录操作都支持相对路径和绝对路径
文本文件的编码格式
- 文本文件存储的内容基于字符编码的文件
- Python2.X默认使用ASCII
- Python3.X默认使用UTF-8
ASCII编码和UNICODE编码
- ASCII编码
- UTF-8编码
如何在Python2.X中使用中文
#*-*coding:utf8*-*
# coding=utf-8
- unicode
- 要能够正确的遍历字符串,在定义字符串时,需要在字符串的引号前,增加一个小写字符
u
,告诉解释器这是一个unicode字符串
- 要能够正确的遍历字符串,在定义字符串时,需要在字符串的引号前,增加一个小写字符
str = u"hello world"
eval函数
-
将字符串当成有效的表达式来求值并返回计算结果
-
计算器
str = input("请输入一个算术题")
print(eval(str))
- 注意:不要使用eval直接转换input的结果 ,可能导致用户执行任何终端命令
__import__('os').system('ls')
等价于
import os
os.system("ls")