文章目录
-
-
- Python学习的预备工作
- Python快速面面观
- 玩转Python中的List
- Python中的函数与函数式编程
-
- **认识函数**
- **函数参数**
- **名称空间与作用域解析(*Namespace and Scope Resolution*)**
- 函数式编程
- 闭包
- 装饰器
- 面向对象编程
- Pythonic OOP
-
- **从语言设计层面理解Python的数据模型**
- **Pythonic OOP with Special Method and Attribute**
- 迭代器协议
- 异常处理
- 上下文管理器:Context Manager
-
- 导入时与运行时
- 课程总结
-
- 准备阶段
- Python语言的一个初步的认识
- Python最典型的内置数据结构
- Python中的函数式编程
- Python中的面向对象编程
- Python程序的运行机制
Python学习的预备工作
- Linux系统简介
- 严格地说,Linux单指操作系统内核
- Linux发行版:Linux内核+应用软件
- 知名的发行版: Ubuntu、RedHat、CentOS
- 跨硬件平台
- 在嵌入式上广泛应用:手机、平板电脑、路由器
- Android操作系统就是创建在Linux内核上
- 带着一切皆是文件的思想去理解Linux系统
- Linux命令行简介
- 说到命令行,真正指的其实是
shell
.shell
是一个程序,接受从键盘输入的命令,然后把命令行传递给操作系统去执行。
- 当使用图形用户界面时,我们需要另一个和
shell
交互的叫做终端仿真器的程序。
- 常用的
shell
程序是bash
- Linux文件系统及读写权限简介
Linux目录结构
- /:根目录
- /bin:binary的缩写,可执行二进制文件目录。系统所需要的那些命令位于此目录。
- /dev: device的缩写,包含了Linux系统中使用的所有外部设备
- /etc: 最重要的目录之一,系统管理中的各种配置文件均在此
- /lib: 存放系统动态链接共享库,所有用户共享
- /usr: 占用空间最大的目录。用户的应用程序多存放于此。
- /usr/local: 主要存放言手动安装的软件
- /home : 主要用于存放用户数据
- 帐户读写权限
Python快速面面观
数据类型
- 整数+浮点数+字符串+布尔值+空值None
- 一般用全部大写的变量表示常量
List:可变有序集合,用中括号声明
Tuple:初始化后不可修改的List就是Tuple,用小括号声明
Dict:Python中可变的key-value形式的数据结构,查找速度极快,用大括号声明 取值用D.get()
字符编码
- ASCLL编码省空间,但是容易出现乱码
- Unicode统一了各种语言的编码,但可能存在大量空间冗余
- UTF-8:可变长的Unicode编码
- ASCLL可被认为是UTF-8的一部分
- 一般来说:内存中:Unicode 存储时:UTF-8 传输时:UTF-8
- Python默认内存的字符串是str类型,以Unicode编码;存储或传输时用以字节为单位的bytes类型
再谈变量
条件判断
循环
- for循环:逐个取出进行操作
- while循环:只要满足条件则持续操作
- break语句:满足条件则跳出循环
- continue语句:满足条件则直接进入下个循环
函数
- 调用内置函数:abs\int\str\float\int\bool\max\min
自定义函数
常用语句:if __name__=='__main__'
- 通过assert来测试函数运行的是否满足我们的希望
- 函数可以返回多个值
- 函数可以定义默认参数,需注意的是,默认参数一般指向不可变对象
函数可以传入任意个参数,例如:def calc_sum(*numbers)
面向对象
分析场景、抽象逻辑、设计class、创建instance
类的构造函数:def __init__(self, name, score=-1)
Python代码的组织-模块
- 包->模块->类或功能函数
- 每个包里面都含有一个
__init__.py
文件,而且必须存在,用以区分普通目录还是包
- 创建包或者模块的时候,不可与系统自带的包或模块重名
引用sys库里的sys.argv方法 ,可以在执行程序时增加输入
使用 pip install 包名称
安装包
解释器默认搜索路径
- sys.path是解释器的默认搜索路径
是否能够import一个库,取决于你这个包是不是在目录里,没有的话就不能引入。
- 引不了的话,使用
sys.path.append('路径')
来增加搜索路径
玩转Python中的List
切片
- 切片功能的三个参数:
[起始位置:终止位置:步长]
(只有第一个:
是必须有的)
- 比较像
range
函数
- 步长也可以是负数,实现倒序
多重List的浅拷贝与深拷贝
- 采用
copy
模块中的copy
和deepcopy
方法来实现浅拷贝与深拷贝
序列的加法、乘法、初始化
- 同类型的序列是可以相加的
- 序列与整数相乘,也可以快速创建包含重复元素的序列
序列的常见内置方法
- 序列的包含关系:
in
- 序列的长度,最大值,最小值:
len
、max
、min
- 使用
.join
的方法很好
- 使用
list
实现List与str的相互转换
- 使用del可以对列表中的值删除
List的内置方法及其时间空间复杂度
sum([x for x in range(101) if x %2 == 0])
Python中的函数与函数式编程
认识函数
代码块
- 代码块是一组语句,类似于其他语言中的大括号
- Python中的代码块靠缩进建立,一般用4个空格
- Python中的代码块由
:
来引导,有缩进的提示终止
- 代码块里可以放条件语句,循环语句,函数定义,类定义,上下文管理器等
函数定义
- 函数是一个代码块
- 用
def
定义
- 返回一个值)
- Python中的调用符号:
()
- Python中是否可调用的判断函数:
callable
- 函数中的
return
语句:可省略;可单独出现;可返回多个变量
函数参数
形参与实参
- 定义函数的时候是形参,使用函数的时候传递的是实参
- 内部修改实参,不影响外部同名变量
就是说你在函数里面修改传进来的数的话,是不影响函数外面的值的
参数的传递
位置参数与关键字参数
- 参数位置解耦
- 默认参数设置
就是说你给函数传入参数值的时候有没有用参数名字,没用就是位置参数,用了就是关键字参数
名称空间与作用域解析(Namespace and Scope Resolution)
- 名称空间: 内置名称空间(Buile-in)+全局名称空间(Global)+嵌套名称空间(Enclosed)+局部名称空间(Local)
- 作用域的产生:LEGB规则
- 作用域内变量的遮盖:可采用nonlocal进行声明,声明全局变量时使用global进行声明
作用域的生命周期
- built-in:解释器在则在,解释器亡则亡
- global:导入模块时创建,直到解释器退出
- local:函数调用时才创建
函数式编程
函数式编程概述
- 前提:函数在Python中是一等对象
- 工具:built-in高阶函数;lambda函数;operator模块;functools模块
- 模式:闭包与装饰器
- 替代:用List comprehension可轻松替代map和filter
- 原则:No Side Effect
函数式编程可以认为只有两个核心,即:高阶函数(即把函数传给函数)+No Side Effect
何为No Side Effect?
函数的所有功能就仅仅是返回一个新值而已,没有其他行为,尤其是不得修改外部变量
因而,各个独立的部分的执行顺序可以随意打乱,带来执行顺序上的自由
执行顺序的自由使得一系列新的特性得以实现:无锁并发;惰性求值;编译器级别的性能优化等
命令式编程与函数式编程
- 程序的状态首先包含了当前定义的全部变量,有了程序的状态,我们的程序才能不断往前推进
- 命令式编程,就是通过不断修改变量的值,来保存当前运行的状态,来步步推进
命令式编程,即用当前全部变量的值来表示程序的状态
- 函数式编程,通过函数来保存程序的状态 (通过函数创建新的参数和返回值来保存状态)
- 命令式编程里一次变量值的修改,在函数式编程里变成了一个函数的转换
- 最自然的方式:递归
一等函数与一等对象
- 一等对象的定义:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传递给函数
- 能作为函数的返回结果
- Python中,所有函数都是一等对象,简称为一等函数
高阶函数
xx = map(square, range(10))
map 就是把一个行为映射到一个迭代器上,上式就是将range(10)中的每个元素执行square函数后输出
map可以被List Comprehension很好的替代
- filter高阶函数
- reduce高阶函数
- sorted高阶函数
- partial高阶函数:用于将一个函数中的某些关键字的值锁死后创建一个新的函数
reduce\sorted\List comprehension联合使用,往往可以解决很复杂的算法问题
匿名函数
- 定义:使用lambda表过式创建的函数,函数本身没有名字
- 特点:只能使用纯表达式,不能赋值,不能使用while和try等块语句
- 语法:
lambad [arg1 [,arg2 [ ,arg3]]]: expression
f_list = [lambda x: x+1, lambda x:x ** 2, lambda x: x ** 3]
闭包
实际上你看函数套函数,并且外层引了内层的函数,就基本判断是闭包了
- 装饰器的本质是一个闭包,而@仅仅是一个语法糖
- 闭包的基础是Python中的函数是一个对象
- 理解闭包需要知道Python如何识别变量所处的作用域
- 自定义变量所处的作用域有三种:global nonlocal local
金句
- 无声明的情况下,赋值即私有,若外部有相同变量名则将其遮挡
- 想修改外部相同变量名,需要将外部变量声明
- 根据外部变量的作用域级中坚力量不同,使用global 或者nonlocal
什么是闭包
- 定义:延伸了作用域的函数(能访问定义体之外定义的非全局变量)
- 闭包是一种函数,它会保留定义函数时存在的外层非全局变量的绑定
- 具体闭包的应用参考此处
- 闭包的作用:
- 共享变量时避免使用了不安全的全局变量
- 允许将函数与某些数据关联起来,类似于简化版面向对象编程
- 相同代码每次生成的闭包,其延伸的作用域都彼此独立
- 函数的一部分行为在编写时无法预知,需要动态实现,同时又想保持接口的一致性
- 较低的内存开销:类的生命周期远大于闭包
- 实现装饰器
实际上你看函数套函数,并且外层引了内层的函数,就基本判断是闭包了
Python四大神器
装饰器
简单的理解就是,装饰器是以函数为参数的函数,用于增强其他函数功能
为什么会有装饰器
名称管理+显示调用+就近原则+充分复用
- 有时候,写一个闭包,仅仅是为了增强一个函数的功能
- 功能增强完了之后,只对增强了功能的最终函数感兴趣
- 装饰之前的函数引用就变得多余
- 因崦出现了func = decorated(func)这种即席覆盖的写法
什么是装饰器
- 装饰器是一个可调用的对象,以某种方式增强函数的功能
- 装饰器是一个语法糖,在源码中标记函数(此源码指编译后的源码)
- 解释器解析源码的时候将被装饰的函数作为第一个位置参数传给装饰器
- 装饰器可以会直接处理被装饰函数,然后返回它(一般仅修改属性,不修改代码)
- 装饰器也可能用一个新的函数或可调用对象替换被装饰函数(但核心功能一般不变)
- 装饰器仅仅看着像闭包,其实功能定位与闭包有重合也有很大区别
- 装饰器模式的本质是元编程:在运行时改变程序行为
- 装饰器的一个不可忽视的特性:在模块加载时立即执行
- 装饰器是可以堆叠的,自底向上逐个装饰
- 装饰器是可以带参数的,但此时至少要写两个装饰器
- 装饰器的更加Pythonic的实现方式其实是在类中实现__call__()方法
带参数的装饰器
- 带参数的装饰器,要用闭包
- 要学好装饰器,要从模仿开始,多读别人的代码,多模仿,慢慢就有感觉了
- Python内置了很多的装饰器,功能都很强大,编写框架或大型程序的时候一定会用到
面向对象编程
面向对象与面向过程:
1. 面向过程就是正常人思维,把复杂问题拆成多个部分,一步步去解决
2. 而面向对象时,你作为一个上帝去给很多角色和种类建立生物圈
3. 面向过程思考的出发点是事情本身,面向对象思考的出发点是假设所有事物都有生命,他们之间会怎么分工协作
一般而言,注重复用和可维护性时,面向过程一般要胜出的。注重短期开发速度,而且是一次性的,面向过程肯定是首先。
面向对象设计的目标
- 可扩展:新特性很容易添加到现有系统中,基本不影响系统原有功能
- 可修改:当修改某一部分代码时,不会影响到其他不相关的部分
- 可替代: 用具有相同接口的代码去替换系统中某一部分的代码时,系统不受影响
上述三点用于检测软件设计是否合理。
此外面向对象还有SOLID原则,具体可再了解。
类的创建
class Model:
pass
def main():
model = Model()
print(model)
if __name__ == '__main__':
main()
- 要点:
- 类名一般大写,实例化出来的对象,名称一般小写
- 类在被定义时,也创建了一个局部作用域
- 类名加上()生成对象,就是实例化的过程
- 本质上讲,类本身就是一个对象
类的数据绑定
类的自定义实例化: __init__()
class Model:
name = "DNN"
def ___init__(self, name):
self.name = name
def main():
cnnmodel = Model("CNN")
if __name__ == '__main__':
main()
凡是出现self的话,就是绑定到对象上;没有self的话,就是对这个对象进行操作
- 要点:
- 类定义体中,
self
指代实例化出来的对象
- 没有跟在
self
后面的属性属于类属性
- 可以使用__init__()函数自定义初始化方式
- 隶属于类的方法是共享的,隶属于对象的方式是每个对象私有的
对象方法
类方法
写代码时,脑子里要清楚,哪些是可变对象,哪些是不可变对象
属性封装
- 通过双下划线开头,可以将数据属性私有化,对于方法一样适用
继承(隐式实例化)
- 如果子类没有定义自己的__init__()函数,则隐式调用父类的
- 子类可以使用父类中定义的所有属性和方法,便类方法的行为需要注意
继承(显式实例式)
class Model:
__name = "DNN"
def __init__(self, name):
self.__name = name
def print_name(self):
print(self.__name)
@classmethod
def print_cls_name)cls):
print(cls.__name)
class CNNModel(Model):
__name = "CNN"
def __init__(self, name, layer_num):
Model.__init__(self, name)
self.__layer__name = layer_num
def print_layer_num(self):
print(self.__layer_num)
def main():
cnnmodel = CNNModel("Lenet", 5)
cnnmodel.print_name()
cnnmodel.print_layer_name()
- 要点:如果子类中定义了__init__()函数,必须显示执行父类的初始化
多态
- 实质上可以这么理解,就是,对于继承自同一个父类的所有子类,你都可以对它们调用父类的方法,不管这个方法有没有在子类中被重写。
- 要点:
- 多态的设计就是要完成对于不同类型对象使用相同的方法调用能得到各自期望的输出
- 在数据封装,继承和多态中,多态是Python设计的核心,也叫鸭子类型
面向对象的重要特性总结:封装+继承+多态
继承的话可以记为纵向复用,多态的话,可以记为横向复用。
Pythonic OOP
从语言设计层面理解Python的数据模型
一切都是对象
- Python中的数据模型是Objects
- Python 程序中所有数据都是用objects或者objects之间的关系表示的
- 甚至Python代码都是objects
Objects的组成1:identity(当object创建后,identity再也不会改变直到被销毁;id()与is关注的就是一个object的identity)
要点:
- 变量存的是创建的object的identity
- 创建出来的不同的object有不同的identity
- 变量的id变了不是因为object的identity变了,而是对应的object变了
- 对于immutable object, 计算结果如果已经存在可直接返回相同的identity
Object的组成2:type
- 当objects创建后,其type也是不会改变的
- type()函数返回一个Object的type
- type决定了一个object支持哪些运算,可能的值在什么范围内
Object的组成3:value
- 有些Objects的value是可以改变的:mutable object即可变对象
- 有些Objects的value是不能改变的:immutable object即不可变对象
- 需要注意当一个object是个container的情况
- 一个Object的type决定了它是否mutable
Pythonic OOP with Special Method and Attribute
这里12、13集较难,讲的是Pythonic OOP,需要时再听
The implicit superclass - object & type object
- 每一个class在定久的时候如果没有继承,都会隐式继承object这个superclass
- 每一个自定义的class在Python中都是一个 type object
Arribuet Access and Properties
Arribute相关的操作一般有:
- Create
- Read
- Update
- Delete
迭代器协议
- 清楚什么是Python中的Iterator Protocol
- 清楚for循环背后的实现逻辑
- 清楚Iterable Iterator Generator三者之间的关系
- 清楚__iter__()与__next__()分别是做什么的
- 清楚为什么要使用Iterator
- 熟练使用Generator,并在代码中尽可能的用Generator替换List
迭代器协议:Iterator Protocol
- 迭代器是一个对象
- 迭代器可以被next()函数调用,并返回一个值
- 迭代器可以被iter()函数调用,并返回迭代器自己
- 连续被next()调用时依次返回一系列的值
- 如果到了迭代的末尾,则抛出StopIteration异常
- 迭代器也可以没有末尾,只要被next()调用,就一定会返回一个值
- Python中,next()内置函数调用的是对象的__next__()方法
- Python中,iter()内置函数调用的是对象的__iter__()方法
- 一个实现了迭代器协议的对象可以被for语句循环迭代直到终止
def f():
yield 1
yield 2
yield 3
def main():
f_gen = f()
for x in f_gen:
print(x)
实现生成器的方式二:Generator Expression
为什么需要生成器
- 相比迭代器协议,实现生成器的代码量小,可读性更高
- 相比在List中操作元素,直接使用生成器能节省大量内存
- 有时候我们会需要写出一个无法在内存中存放的无限数据流
- 你可以建成生成器管道(多个生成器链式调用)
用生成器表示全部的斐波那契数列
def fibonacci():
temp = [1,1]
while True:
temp.append(sum(temp))
yield temp.pop(0)
if __name__ == '__main__':
for x in fibonacci():
print(x)
if x > 20:
break
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i4sXMeon-1623684645854)(en-resource://database/1566:1)]
在Python中不要小瞧任何看似微小的区别
sum([x ** 2 for x in range(1000000)]) # 这是把一个List传给了sum
sum(x ** 2 for x in range(10000000)) # 这是把一个生成器传给了sum,效率要高的多
异常处理
这一节非常棒,可以多听听:
异常处理
什么是异常?
- 错误:比如代码语法有问题,程序无法启动;比如试图在除法中除以0
- 小概率事件:比如图像识别API遇到了图像尺寸为2×2的图片
为什么程序会出现异常
- 程序的某一部分不由程序编写者控制(使用别人的数据;数据等待外部输入;程序运行环凌晨一致性问题)
- 程序编写者难以考虑到全部情况并预先提供处理方式
通常如何处理
- 条件语句:
if/else
这种方式会带来代码冗余的问题
- 异常处理:
try/except/else/finally
Python中的异常及相关语法
Exception
:Python内置的异常类
raise
:抛出异常(就是我们想主动抛出的异常,使用raise抛出异常时并不拦截程序运行)
try
:尝试运行以下语句
except
:在try语句之后,捕获某个异常,为空则捕获全部异常(很危险,难以debug)
else
: 在try语句之后,如果没有捕获到异常,则执行
finally
: 在try语句之后,无论是否捕获异常,均执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W61ZYQkn-1623684645857)(en-resource://database/1568:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E8cHXnnj-1623684645858)(en-resource://database/1570:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4pD1f2sz-1623684645861)(en-resource://database/1572:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OhJl1UiT-1623684645864)(en-resource://database/1574:1)]
上下文管理器:Context Manager
为什么要讲Context Manager
- 类似于Decorator,Tensorflow里面出现了不少Context Manager
- Pythonic的代码利用工具,适用于所有有始必有终模式的代码复用
- 减少错误,降低编写代码的认知资源
- 提高代码可读性
Context Manager与Decorator之间的关系
- 如果说Decorator是对Function和Class的Wrapper
- 那么Context Manager就是对任意形式的Code Block的Wrapper
为什么用Context Manager
有始必有终
- 如果一段程序:有始必有终,那么, You need Context Manager
- 虽然使用try/finally可以实现,但更加Pythonic的方式是Context Manager
什么是Context Manager,与with是何关系
Context Manager is a protocol for Python with
statement
一个功能的代码实现与其工程化的区别
- 某种或某些情况下可用vs任何情况下都可用
- 资源足够的情况下可用vs有限的资源下可用
- 自己可以运行起该程序vs任何人可自行运行
- 自己可以继续开发修改vs任何人可继续开发
- 强调功能本身vs强调:可用性;可扩展;性能;资源占用;安全;快速开发;容易交接;不易犯错
导入时与运行时
课程总结
准备阶段
- 课程简介,设计原则,及学习方法
- Python学习的预备内容
Python语言的一个初步的认识
- Python面面观
Python最典型的内置数据结构
- 玩转Python中的List
Python中的函数式编程
- Python中的函数与函数式编程
- 闭包
- 装饰器
Python中的面向对象编程
- 理解面向对象编程(OOP)
- OOP in Python
- Pyhonic OOp
- 迭代器协议
- 异常处理
- 上下文管理器
Python程序的运行机制
- 导入时与运行时