使用 dict 和 set - 廖雪峰的官方网站 (liaoxuefeng.com)
用于学习记录,后期便于复习,参考链接
python file
调用脚本时会先载入 pyhton 解释器,然后运行脚本
rpm:软件管理包
操作符优先级:
条件判断:
Python 为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容,被形象地称作 “内置电池(batteries included)”。用 Python 开发,许多功能不必从零编写,直接使用现成的即可。
语言定位:
Python 的定位是 “优雅”、“明确”、“简单”
Python 是解释型语言
# python
# 数据类型
# int 整型
long int 长整型
int 整型
# float 浮点型
浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时
双精度浮点型 e
浮点型
# String 字符串
字符串是以单引号 '
或双引号 "
括起来的任意文本
字符串是以 Unicode 编码
对于单个字符的编码,Python 提供了 ord()
函数获取字符的整数表示, chr()
函数把编码转换为对应的字符:
# Bool 布尔
True
Flase
# None 空值
None, None
不能理解为 0
,因为 0
是有意义的,
Null 无意义
.
<iframe src="http://127.0.0.1:6806/widgets/brython-editor" data-src="http://127.0.0.1:6806/widgets/brython-editor" data-subtype="widget" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 1341px; height: 276px;"></iframe>
# 变量
变量的概念基本上和初中代数的方程变量是一致的,
变量不仅可以是数字,还可以是任意数据类型。
变量名必须是大小写英文、数字和 _
的组合,且不能用数字开头,字母或下划线开头
#变量类型 | |
a=1 | |
b='我是变量' | |
c=True | |
d=12.2e3 | |
print(a,b,c,d) | |
print(type(a),type(b),type(c),type(d)) |
int a=1 静态语言 此时已经分配的 int 分区之后不能更改变量类型【不支持,Java】
a=3 动态语言,可以赋值成任意类型
# 动态定义
# 静态定义
a = 'ABC' | |
b = a | |
a = 'XYZ' | |
print(b) |
执行 a = 'ABC'
,解释器创建了字符串 'ABC'
和变量 a
,并把 a
指向 'ABC'
:
执行 b = a
,解释器创建了变量 b
,并把 b
指向 a
指向的字符串 'ABC'
:
执行 a = 'XYZ'
,解释器创建了字符串 'XYZ',并把 a
的指向改为 'XYZ'
,但 b
并没有更改:
# 常量
所谓常量就是不能变的变量,通常用全部大写的变量名表示常量:
PI = 3.14159265359 |
# list 列表
list 是一种有序的集合,可以随时添加和删除其中的元素。
list=['a','b','c'] | |
print(list) | |
print(len(list)) |
# 切分
# append()追加
str.append('a')
# insert 插入指定位置
# pop()删除末尾元素
要删除指定位置的元素,用 pop(i)
方法,其中 i
是索引位置
替换元素直接赋值即可
列表可以嵌套
s = ['python', 'java', ['asp', 'php'], 'scheme'] |
类型可以不同
L = ['Apple', 123, True] |
# 切片
list [:-1] 不包含最后一个元素
list [:] 全部列表
list [::] 全部列表
前 10 个数,每两个取一个
# 列表生成
list(range(1,11))生成 10 个数 1-10
print([m+n for m in '123' for n in 'yza']) |
k=[1,1,23,45,56,[1,12,3,467,[2,4,4,3,22]]] | |
print([x for x in k if x==1 ]) |
# tuple 元组
tuple 一旦初始化就不能修改
但元组初始化后就不能进行更改了
b=(1) | |
#定义的不是 tuple,是 1 这个数! | |
# 这是因为括号 () 既可以表示 tuple,又可以表示数学公式中的小括号, | |
# 这就产生了歧义,因此,Python 规定, | |
# 这种情况下,按小括号进行计算,计算结果自然是 1 | |
print(b) | |
print(type(b)) | |
c=(1,) | |
print(c) | |
print(type(c)) |
# dict 字典
其他语言叫 map,使用键 - 值(key-value)存储,具有极快的查找速度。dict 的 key 必须是不可变对象。key 计算位置的算法称为哈希算法(Hash)。
# 定义
d={'name':'ruanyifen','age':60,'happy':'write'} | |
d['add']='Im add' | |
d['name']='fix' |
# 取 value
# dict['key']
# 'key' in dict
# dict.get('key')
print(type(d)) | |
print(d['name']) | |
print('name' in d)#方法一判断是否有这个主键在字典 d 中 | |
print(d.get('name'))#方法二 取 |
# dict.pop ('key') 删除一个 key
# dict.keys 返回字典中所有 key 列表
# dict.update () 将 a 字典新 key,value 内容加入 b 字典中
dicta={"name":'ruan','age':20} | |
dictb={"name":'ruan2','age':40,'add':'w shi add'} | |
dictb.update(dicta) | |
print(dictb) |
# 内建函数使用
type()
cmp()
len()
hash()
内建 cmp()函数比较两个 dict 时,先比较长度,后比值,输出 1 或 - 1
# dict 特点
dict 有以下几个特点:
- 查找和插入的速度极快,不会随着 key 的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而 list 相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
# set 集合
也是一组 key 的集合,但不存储 value。由于 key 不能重复,所以,在 set 中,没有重复的 key。
重复元素在 set 中自动被过滤
# 定义
# set.add ('key') 添加元素
但重复元素不添加,自动去重
# set.remove ('key') 删除元素
set 可以看成数学意义上的无序和无重复元素的集合,
因此,两个 set 可以做数学意义上的交集、并集等操作:
# & 两个 set 交集
# | 两个 set 并集
# map () 的显示
打印 map 对象可以看到 map 对象返回的是一个地址,不是真实的数据
print(list(map对象)) | |
print([it for it in map对象]) |
# 数据类型转换
# int()
# float()
# str()
# bool()
# 条件判断
# if
if else
a=100 | |
if a>=0: | |
print(a) | |
else: | |
print(-a) |
if
if True: | |
print('True') |
if elif elif else
name='zhangsan' | |
if name=='zhangsan': | |
print('我是',name) | |
elif name=='lisi': | |
print('我是', name) | |
elif name=='wangwu': | |
print('我是', name) | |
else: | |
print('我谁的不是') |
# input()输入输出
input () 返回的数据类型是 str
print()
# 循环 迭代
list,tuple,dict 都可循环
Python 的 for
循环本质上就是通过不断调用 next()
函数实现的,计算是惰性的
dict 循环按照 value 时:for value in dict.values
for value in d.values(): | |
print(value) |
# for in
sum=0 | |
for i in range(1,100): | |
sum=sum+i | |
print(sum) |
# while
sum2=0 | |
k=0 | |
while(k<100): | |
sum2=sum2+k | |
k=k+1 | |
print(sum2) |
# break
如果要提前结束循环,可以用 break
语句
# continue
通过 continue
语句,跳过当前的这次循环,直接开始下一次循环
# 生成器
在 Python 中,这种一边循环一边计算的机制,称为生成器:generator。
包括生成器和带 yield
的 generator function。
g = (x * x for x in range(10)) |
访问大文件
yield
# isinstance()迭代器
直接作用于 for
循环的对象统称为可迭代对象,都是迭代器 Iterable
list
、 tuple
、 dict
、 set
、 str
list
、 dict
、 str
虽然是 Iterable
,却不是 Iterator
。
list
、 dict
、 str
等 Iterable
变成 Iterator
可以使用 **iter ()** 函数
以直接作用于 for
循环的数据类型有以下几种:
一类是集合数据类型,如 list
、 tuple
、 dict
、 set
、 str
等;
一类是 generator
,包括生成器和带 yield
的 generator function。
可以使用 **isinstance ()** 判断一个对象是否是 Iterable
对象__iter__:
迭代对象
判断是不是可以迭代,用 Iterable
from collections import Iterable | |
isinstance({}, Iterable) --> True | |
isinstance((), Iterable) --> True | |
isinstance(100, Iterable) --> False |
判断是不是迭代器,用 Iterator
from collections import Iterator | |
isinstance({}, Iterator) --> False | |
isinstance((), Iterator) --> False | |
isinstance( (x for x in range(10)), Iterator) --> True |
Python 中 list,truple,str,dict 这些都可以被迭代,但他们并不是迭代器,为什么::因为和迭代器相比有一个很大的不同,list/truple/map/dict 这些数据的大小是确定的,也就是说有多少事可知的。但迭代器不是,迭代器不知道要执行多少次,所以可以理解为不知道有多少个元素,每调用一次 next (),就会往下走一步,是惰性的。
# 函数
抽象
将函数抽象成一个函数名称,不看内部结构直接调用方法
返回类型 函数名(输入参数):
函数体
# 调用函数
要调用一个函数,需要知道函数的名称和参数
绝对值 abs
# 定义函数
def myabs(x): | |
if x>0: | |
return x | |
if x<0: | |
return -x | |
print(myabs(-100)) |
# 空函数
def nufun(): | |
pass |
pass
可以用来作为占位符
# 函数 参数检查
def my_init_abs(x): | |
if not isinstance(x,(int,float)): | |
raise TypeError('no no no') | |
else: | |
if x>0: | |
print(x) | |
if x<0: | |
print(-x) | |
my_init_abs(-90) |
# 可返回多个值,函数
def return_much(): | |
a='返回' | |
b='我也返回' | |
c='我也要返回' | |
return a,b,c | |
print(return_much()) | |
print(type(return_much())) |
# 函数参数
*args 是可变参数,args 接收的是一个 tuple;
**kw 是关键字参数,kw 接收的是一个 dict。
power(x)
函数,参数 x
就是一个位置参数,可单个变量,list,set,tuple
power(*x)
函数,可传入单个变量,list,set,tuple,可以传入任意个参数或 0 个参数
power(**kw)
函数,字典 dict
可变参数允许你传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个 tuple。
而关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个 dict.
power(x, n)
,用来计算 xn
power(x, n)
函数有两个参数: x
和 n
默认参数,此时 age 和 city 为默认参数,可传值改变也可不变【不用传值】
power(L=None)
函数有 None 这个不变对象,可用 list
def enroll(name, gender, age=6, city='Beijing'): | |
print('name:', name) | |
print('gender:', gender) | |
print('age:', age) | |
print('city:', city) |
# 必选参数
def a1(x): | |
return x | |
print(a1(2)) | |
print(a1(12.1)) | |
print(a1('ruan')) | |
print(a1(True)) | |
print(a1([1,2,3])) | |
print(a1({1,2,3})) | |
print(a1({"key":"vleaue",'name':'ruan','mun':23})) |
# = 默认参数
def a2(x=9): | |
return x | |
print(a2()) | |
print(a2(2)) | |
print(a2(12.1)) | |
print(a2('ruan')) | |
print(a2(True)) | |
print(a2([1,2,3])) | |
print(a2({1,2,3})) | |
print(a2({"key":"vleaue",'name':'ruan','mun':23})) |
# * 可变参数
def a3(*x): | |
return x | |
print(a3()) | |
print(a3(2)) | |
print(a3(12.1)) | |
print(a3('ruan')) | |
print(a3(True)) | |
print(a3([1,2,3])) | |
print(a3({1,2,3})) | |
print(a3({"key":"vleaue",'name':'ruan','mun':23})) |
# ** 关键字参数
def a3(**kw): | |
return kw | |
print(a3()) | |
print(a3(kw=2)) | |
print(a3(kw=12.1)) | |
print(a3(kw='ruan')) | |
print(a3(kw=True)) | |
print(a3(kw=[1,2,3])) | |
print(type(a3(kw=[1,2,3]))) | |
print(a3(kw={1,2,3})) | |
print(type(a3(kw={1,2,3}))) | |
print(a3(kw={"key":"vleaue",'name':'ruan','mun':23})) |
# 递归函数
在函数内部,可以调用其他函数。
一个函数在内部调用自身本身,这个函数就是递归函数。
使用递归函数需要注意防止栈溢出
#递归函数 | |
def funmyself(x): | |
if x>1: | |
return x+funmyself(x-1) | |
elif x==1: | |
return 1 | |
print(funmyself(3)) |
解决栈溢出方法:
尾递归优化,事实上尾递归和循环的效果是一样的
尾递归是指,在函数返回的时候,调用自身本身,并且,return 语句不能包含表达式
#尾递归 | |
def funmyself2(n): | |
return funmyself2_it(n,1) | |
def funmyself2_it(n,pro): | |
if n==1: | |
return pro | |
else: | |
return funmyself2(n-1)+n | |
print(funmyself2(100)) |
此时 funmyself2 是尾递归函数
# 转义字符 \
转义字符 \
可以转义很多字符,比如
\n
表示换行,
\t
表示制表符,
字符 \
本身也要转义,所以 \\
表示的字符就是 \
Python 还允许用 r''
表示 ''
内部的字符串默认不转义
# 运算符 and、or 和 not
运算优先级:not>or>and
# 除法 ///
print(10/3) | |
print(10//3) |
# 除法一 / 浮点数
# 除法二 // 地板除 整数
/
除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
//
,称为地板除,两个整数的除法仍然是整数:
# 取余 %
print(10%1) | |
print(10%3) | |
print(4%7) | |
print(2%20) | |
#如果 a% b a>b 则结果为 a |
# 字符编码
ASCII
编码
8 个比特(bit)作为一个字节(byte)
一个字节能表示的最大的整数就是 255(二进制 11111111 = 十进制 255)
两个字节可以表示的最大整数是 65535
,4 个字节可以表示的最大整数是 4294967295
大写字母 A
的编码是 65
,二进制的 01000001
,小写字母 z
的编码是 122
Unicode 把所有语言都统一到一套编码里,这样就不会再有乱码问题
ASCII 编码是 1 个字节,而 Unicode 编码通常是 2 个字节
ASCll 出现乱码问题引入 Unicode 编码存储空间多了一倍引入 UTF-8 编码
utf-8:将 Unicode 字符根据不同的数字大小编码成 1-6 个字节,常用的英文字母被编码成 1 个字节,汉字通常是 3 个字节,
字符 | ASCII | Unicode | UTF-8 |
---|---|---|---|
A | 01000001 | 00000000 01000001 | 01000001 |
中 | x | 01001110 00101101 | 11100100 10111000 10101101 |
在计算机内存中,统一使用 Unicode 编码,当需要保存到硬盘或者需要传输的时候,就转换为 UTF-8 编码。
用记事本编辑的时候,从文件读取的 UTF-8 字符被转换为 Unicode 字符到内存里,编辑完成后,保存的时候再把 Unicode 转换为 UTF-8 保存到文件
浏览网页的时候,服务器会把动态生成的 Unicode 内容转换为 UTF-8 再传输到浏览器:
# compile () 字符串编译为字节代码
# 编码转化
# ord ('A') 字母转字符
# chr (65) 字符转字母
a=ord('A') | |
b=chr(65) | |
print(a,b) |
# b'str' 转为字节类型 bytes
bytes
类型的数据用带 b
前缀的单引号或双引号表示
x = b'ABC' |
要注意区分 'ABC'
和 b'ABC'
,前者是 str
,后者虽然内容显示得和前者一样,但 bytes
的每个字符都只占用一个字节。
# str.encode('ascii') str
变为 bytes
ASCII
UTF-8
# str.decode('utf-8') bytes
变为 str
print(type(b'abc')) | |
print(b'abc'.decode('utf-8')) | |
print(type(b'abc'.decode('utf-8'))) |
len(str)计算字符数
函数计算的是 str
的字符数,如果换成 bytes
, len()
函数就计算字节数
print(len('abc')) | |
print(len(b'abc')) | |
print(len('中')) | |
print(len('中'.encode('utf-8'))) |
# 特殊注释
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- |
第一行注释是为了告诉 Linux/OS X 系统,这是一个 Python 可执行程序,Windows 系统会忽略这个注释;
第二行注释是为了告诉 Python 解释器,按照 UTF-8 编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
# 占位符 格式化
# 占位符 % s % d % f
格式化方式和 C 语言是一致
%
运算符就是用来格式化字符串的。
在字符串内部,
%s
表示用字符串替换,
%d
表示用整数替换,
有几个 %?
占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个 %?
,括号可以省略
占位符 | 替换内容 |
---|---|
%d | 整数 |
%f | 浮点数 |
%s | 字符串 |
%x | 十六进制整数 |
转义: %%
来表示一个 %
# format()格式化字符串
# f-string 格式化字符串
{r}
被变量 r
的值替换, {s:.2f}
被变量 s
的值替换,并且 :
后面的 .2f
指定了格式化参数(即保留两位小数),因此, {s:.2f}
的替换结果是 19.62
# 函数式编程
函数是 Python 内建支持的一种封装,通过层层函数进行调用
#面向过程的程序设计 #:把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计
函数式和函数的区别:
对比例子:计算和计算器的区别
编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如 C 语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如 Lisp 语言
Python 不是纯函数式编程语言
函数式编程就是一种抽象程度很高的编程范式,
纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的
# 函数式编程特点:
- 纯函数式编程语言函数没有变量,输入输出确定
- 允许本身作为参数传入另一个函数,允许返回一个函数
# 高阶函数
参数中有函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
# 变量可以指向函数
a = 函数
求绝对值的函数 abs()
为例
print(abs(-10)) | |
print(abs) |
abs(-10)是函数调用,abs 是函数本身
k=abs(-20) | |
print(k) | |
#函数本身也可以赋值给变量 | |
h=abs | |
print(h) | |
print(h(-100)) |
结论:函数本身也可以赋值给变量,即:# 变量可以指向函数。#
# 函数名也是变量
#函数名 #:其实就是指向函数的变量
a () 中 a 是指向函数 a()的变量
# 传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
#高阶函数 #:一个函数就可以接收另一个函数作为参数
b()
a(b)
x=a
def f(x): | |
return abs(int(x)) | |
def a(a,b,f): | |
return f(a)+f(b) | |
if __name__ == '__main__': | |
a1=input('a') | |
a2=input('b') | |
print(a(a1,a2,f)) |
此时函数 a 为高阶函数,需要调用 f 函数作为参数
# map/reduce 内建函数
内建了 map()
和 reduce()
函数 高阶函数
# map()函数处理生成新 Iterator 迭代器
两个参数,函数名【函数本身】,需要处理的编程式 iterator
<br />
创建一个迭代器,使用每个迭代器中的参数计算函数。当最短迭代用尽时停止。
map(func, *iterables) --> map object |
def f(x): | |
return x*x | |
r=map(f,[1,2,3,4,4,4,4,4,4,4,4]) | |
print(r) | |
print(type(r)) | |
print(list(r)) | |
print(type(list(r))) |
运算规则抽象
# reduce()函数作用在序列上
两个参数,函数名【函数本身】,需要处理的 #序列 #: sequence (序列) 是一组有顺序的元素的集合
序列基本样式 [下限:上限:步长]
reduce
把结果继续和序列的下一个元素做累积计算
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) |
from functools import reduce | |
>>> def fn(x, y): | |
... return x * 10 + y | |
... | |
>>> reduce(fn, [1, 3, 5, 7, 9]) | |
13579 |
# filter () 过滤序列
参数和 map()相似
filter()
也接收一个函数和一个序列
# sorted()排序
高阶函数
参数:排序对象,key = 函数
sorted([36, 5, -12, 9, -21], key=abs) |
排序的核心是比较两个元素的大小
print(sorted([1,2,353,6,3,234,43,435])) |
key 指定绝对值大小排序
print(sorted([1,2,353,6,3,234,43,435,-242,-34,34,35],key=abs)) |
# 返回函数
# 函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
# 如果不需要立刻求和,而是在后面的代码中, | |
# 根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数: | |
def zary_sum(a): | |
def sum(): | |
sum1=0 | |
for i in a: | |
sum1=sum1+i | |
return sum1 | |
return sum | |
print(type(zary_sum([1,2,3,4]))) | |
f=zary_sum([1,2,3,4]) | |
print(f()) |
调用返回函数时,每次调用都会新生成一个函数
# 闭包
当一个函数的返回值是另外一个函数,
而返回的那个函数如果调用了其父函数内部的其它变量,如果 返回的这个函数在外部被执行,就产生了闭包 。
返回函数中,返回的函数调用父函数的内部变量
#返回函数 | |
def count(): | |
fs=[] | |
for i in range(1,4): | |
def f(): | |
return i*i | |
fs.append(f) | |
return fs | |
f1,f2,f3=count() | |
print(f1(),f2(),f3()) |
返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。
# lambda()匿名函数
lambda 关键字 函数参数:函数表达式
传入函数时,有些时候,不需要显式地定义函数
Python 对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
lambda x:x*x | |
#等价于 | |
def f(x): | |
return x*x |
关键字 lambda
表示匿名函数,冒号前面的 x
表示函数参数,只能一个表达式
不用写 return
,返回值就是该表达式的结果。
匿名函数也是一个函数对象
f=lamdba x:x*x |
判断奇数函数
原函数:
def is_odd(n): | |
return n % 2 == 1 | |
L = list(filter(is_odd, range(1, 20))) |
采用匿名函数修改
l=list(filter(lambda x:x%2==1,range(1,20))) | |
print(l) |
# 装饰器 Decorator
# 本质上,装饰器就是一个返回函数的高阶函数
@log 等价于
now = log(now) |
由于函数也是一个对象,而且函数对象可以被赋值给变量,
所以,通过变量也能调用该函数
def log(func): | |
def wrapper(*args,**kwargs): | |
print('call %s'% func.__name__) | |
return func(*args,**kwargs) | |
return wrapper() | |
#func 为参数所以是高阶函数 | |
#return 函数所以是返回函数, | |
#没有调用父函数中参数,所以不是闭包 |
场景注意:
无 @装饰器时函数不调用,需要参数才调用
当 @时会直接调用装饰器定义函数然后执行函数,不用调用函数
三层时,传入参数
def log1(text): | |
def decorator(func): | |
def wapper(*args,**kw): | |
print('%s %s'%(text,func.__name__)) | |
return func(*args,**kw) | |
return wapper | |
return decorator | |
@log1('ruan') | |
def now3(): | |
print("hhh") | |
now3() |
相当于在返回高阶函数上还有一个函数,所以返回时应该还要调用一次
# @wraps 常用装饰器
当装饰器是个闭包时,装饰器调用变量会改变增加 @wraps 后装饰器内的变量不变
装饰器在装饰一个函数时,,原函数就成了一个新的函数,也
就是说其属性会发生变化,所以为了 不改变原函数的属性,
我们会调用 functools 中的 wraps 装饰器来保证原函数的属性不变
# 不加 wraps 时
@wraps(func) |
from functools import wraps | |
def wrap(func): | |
def b(): | |
'b' | |
print('decorator:',b.__name__) | |
print('funname',func.__name__) | |
func() | |
return b | |
@wrap | |
def a(): | |
'a' | |
print('name',a.__name__) | |
a() |
加装饰器 wraps 时
from functools import wraps | |
def wrap(func): | |
@wraps(func) | |
def b(): | |
'b' | |
print('decorator:',b.__name__) | |
print('funname',func.__name__) | |
func() | |
return b | |
@wrap | |
def a(): | |
'a' | |
print('name',a.__name__) | |
a() |
闭包的概念:调用父函数中的变量的函数,为了保证数据安全。变量作用域只在函数内部,可在闭包中操作数据。
装饰器返回为什么是函数名(函数内存地址)而不直接执行函数?
当有参数传入时,可直接与调用的函数中的值传入参数执行。
()是运算符 f () 与 f.call () 等价:将 f 对象变成变成可调用的对象
# 偏函数(functools 模块)
属于 functools 模块
# 作用:
通过设定参数的默认值,降低函数调用的参数
int()
函数默认按十进制转换
print(int('100',base=8))
经常调用于是重写一个函数 int2
def int2(x, base=8): | |
print(int(x, base)) | |
return int(x, base) | |
print(int2('2334')) |
采用偏函数
import functools | |
int3=functools.partial(int,base=8) | |
print(int3('46')) | |
print(int()) |
functools.partial 的作用是将函数的特定参数固定住(设定为默认值)
创建偏函数的时候也可以接收,函数对象,*args,**kw
# 模块
python 包:作用区分相同名称的模块
模块相当于一个 py 文件
# 作用域
仅仅在模块内部使用。在 Python 中,是通过 _
前缀来实现的。
# pubilc 公开
正常的函数和变量名是公开的(public)
# private 非公开_,__
_xxx 和__xxx 这样的函数或变量就是非公开的(private)
# 安装第三方模块 pip
pip install 模块名
# 模块搜索路径
import sys | |
print(sys.path) |
两种方式:
- 添加搜索路径
import sys
sys.path.append('/Users/michael/my_py_scripts')
- 设置环境变量
第二种方法是设置环境变量 PYTHONPATH
# 面向对象编程
面向对象编程 ——Object Oriented Programming,简称 OOP,是一种程序设计思想
对象作为程序的基本单元,
一个对象包含了数据和操作数据的函数
数据封装、继承和多态是面向对象的三大特点
# 类和实例
面向对象最重要的概念就是类(Class)和实例(Instance)
类是抽象出来的模板
实例是根据类创建出的对象,每个对象可能有属性和方法
定义类是通过 class
关键字,类名通常是大写开头的单词
class Student(object): | |
pass |
!!!在类中定义函数有一点不同,定义佛如方法第一个参数永远是实例变量本身 self
仍然可以用默认参数、可变参数、关键字参数和命名关键字参数
# 数据封装
class Student(object): | |
def __init__(self, name, score): | |
self.name = name | |
self.score = score | |
def get_grade(self): | |
if self.score >= 90: | |
return 'A' | |
elif self.score >= 60: | |
return 'B' | |
else: | |
return 'C' |
# 访问限制
# 作用:
确保了外部代码不能随意修改对象内部的状态
实例的变量名如果以 __
开头,就变成了一个私有变量(private)
外部无法访问_name
class Student(object): | |
def __init__(self,name,age): | |
self._name=name | |
self.age=age | |
def print_name(self): | |
print(self._name) | |
return self.age,self._name | |
a=Student('ruan',23) | |
h=a.print_name() | |
print(h) |
若是要获取,修改变量增加 get,set 方式即可
class Student(object): | |
def __init__(self,name,age): | |
self._name=name | |
self.age=age | |
def get_name(self): | |
return self.name | |
def set_name(self,name): | |
self._name=name |
Python 本身没有任何机制阻止你干坏事,一切全靠自觉。
类外部无法访问
# 继承和多态
# 继承
# 多态
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。
比如:动物是父类,狗和鱼是子类;鱼是鱼类,鱼是动物都成立。
判断一个变量是否是某个类型可以用 isinstance()
判断
# 鸭子类型
并不要求严格的继承体,一个对象只要 “看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
# 获取对象信息
# type()判断对象类型
# isinstance () 对于继承关系,判断 class 的类型
# dir()获取对象的所有属性和方法
# len()对象长度
# lower()返回小写的字符串
# getattr()获取属性 a
# setattr()设置属性 a
# hasattr(obj,'a')判断是否有属性 a
getattr(obj, 'z', 404) # 获取属性 'z',如果不存在,返回默认值 404 | |
404 |
# 实例属性和类属性
class Student(object): | |
name='ruan' | |
h=Student() | |
h.name='hhh' |
类中的 name 是类属性,
创建 h 类对象即实例后赋值的是实例属性 name,但由于实例对象的优先级比类属性高,会屏蔽类中的 name 属性,即 h.name 的值为 hhh
# 总结:
- 实例属性属于各个实例所有,互不干扰;
- 类属性属于类所有,所有实例共享一个属性;
- 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误
# 面向对象高级编程
数据封装、继承和多态只是面向对象程序设计中最基础的 3 个概念
多重继承、定制类、元类
# _slots_使用
可以给创建的实例绑定属性和方法
给一个实例绑定的方法对另外一个实例对象是不起作用的
class A: | |
def run(self): | |
print("i im ferther runing....") | |
sun1=A() | |
#给实例 sun1 设置 name 属性 | |
sun1.name='i im name' | |
#创建实例对象 2 | |
sun2=A() | |
#实例对象 sun1 的属性和 sun2 无关,即 sun2 没有 name 属性 | |
#给实例 sun1 绑定方法,方法和属性同理 | |
#定义方法 | |
def setAll(self,num): | |
print(num) | |
sun1.newfun=MethodType(setAll, sun1) | |
sun1.newfun(37) | |
#若所有实例都需要绑定方法则给类绑定方法 | |
A.setAll=setAll | |
#给类绑定方法后,所有创建的实例的均可调用 |
def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
# 限制实例属性,定义一个特殊的 __slots__
变量
class Student(object): | |
__slots__ = ('name', 'age') # 用 tuple 定义允许绑定的属性名称 | |
s=Student() | |
s.name='ruan' | |
s.firstname='i im firstname' | |
#输出的时候 firstname 的属性会报错, | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
AttributeError: 'Student' object has no attribute 'firstaname' |
# 注意:
_slots_使用时要注意,定义的属性只在当前的类的实例中,对于继承的子类是不起作用的
class People(object): | |
__slots__ = ('name','age') | |
def run(self): | |
print('i im run people......') | |
class Teacher(People): | |
def say(self): | |
print('i im teacher....') | |
t=Teacher() | |
t.tall='shouhua' | |
print(t.tall) | |
p=People() | |
p.tall('shouhuap') | |
print(p.tall) |
只限制父类 People 的属性,而子类 Teacher 中不限制
# @property
在绑定属性时,如果我们直接把属性暴露出去,导致可以随意更改。通过 get,set 来获取更改属性值。
在 python 中直接调用装饰器将一个方法变成属性调用
class Student(object): | |
@property | |
#使用 get 方法是调用装饰器 @peoperty, | |
# 同时自动创建了另一个装饰器 @属性.setter | |
def score(self): | |
return self.score | |
@score.setter | |
def score(self,value): | |
self._score=value |
# 总结:
- 权限限制只对类对象实际起作用,想要达到方法和属性强制访问权限,需要使用 @property 装饰器进行 get,set 方法
属性名与方法名一定要区分开,不然会进入死循环(self._age,def age ())
实例化的对象使用属性时,不是调用属性(meizi._age),而是用的方法名(meizi.age)
@property 其实就是实现了 getter 功能; @xxx.setter 实现的是 setter 功能;还有一个 @xxx.deleter 实现删除功能
定义方法的时候 @property 必须在 @xxx.setter 之前,且二者修饰的方法名相同(age ())
如果只实现了 @property(而没有实现 @xxx.setter),那么该属性为 只读属性
#请利用 @property 给一个 Screen 对象加上 width 和 height 属性, | |
# 以及一个只读属性 resolution: | |
class Screen(object): | |
__slots__ = ('_width','_height','_resolution') | |
@property | |
def width(self): | |
return self._width | |
# 方法名称和实例变量均为 width: | |
@width.setter | |
def width(self,widthValue): | |
self._width=widthValue | |
@property | |
def height(self): | |
return self._height | |
@width.setter | |
def height(self, height): | |
self._height = height | |
@property | |
def resolution(self): | |
return self._width * self._height | |
s=Screen() | |
s.width=23 | |
s.height=12 | |
print(s.resolution) |
s.score = 60 # OK,实际转化为 s.set_score (60) | |
s.score # OK,实际转化为 s.get_score () |
要特别注意:属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
class Student(object):
# 方法名称和实例变量均为birth:
@property
def birth(self):
return self.birth
出现递归调用错误
之前的例子中 width 和_width 不同所以可以运行
# 多重继承
python 可以支持多继承,即一个子类可以继承多个父类;但 java 是单继承,只能有一个父类
Tercher(Name,study,teach)即 Teacher 可以继承多个父类
# MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如, Teacher
继承自 Name。但是,如果需要 “混入” 额外的功能,通过多重继承就可以实现,比如 Teacher 除了继承自 Name
外,再同时继承 Teach
。这种设计通常称之为 MixIn
Python 自带了 TCPServer
和 UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由 ForkingMixIn
和 ThreadingMixIn
提供。
# 多继承
多重继承这个名词一般用来形容继承链条可以很长,多个层次。
# 多重继承
多继承则指一个类可以有多个基类,相反则是单继承。任何面向对象编程语言都支持多重继承,但像 java 这种只能通过接口实现有限程度的多继承
问:多继承 如果多个类有共同得方法名 怎么区分是调得哪个类🤡
答:调用该方法的时候,会调用第一顺位继承父类的方法
# 总结:
- Python 允许使用多重继承,因此,MixIn 就是一种常见的设计
- 只允许单一继承的语言(如 Java)不能使用 MixIn 的设计
# 定制类
Python 的 class 中还有__xxx__有特殊用途的函数,可以帮助我们定制类
# str () 回用户看到的字符串
将对象 <__main__.Student object at 0x109afb190>
变成易读的数据
只在调用 print 时会调用__str__,交互界面时还是现实上方不易读的对象内容,此时用
# repr () 返回程序开发者看到的字符串
__str__()
返回用户看到的字符串,而 __repr__()
返回程序开发者看到的字符串,
也就是说, __repr__()
是为调试服务的
简写
def __str__(self): | |
return 'xxx object (name=%s)' % self.name | |
__repr__ = __str__ |
# _iter () 返回一个迭代对象
需要用到 for in 迭代,需要转化为迭代对象
该方法返回一个迭代对象,然后,Python 的 for 循环就会不断调用该迭代对象的 __next__()
方法拿到循环的下一个值,直到遇到 StopIteration
错误时退出循环
例子:
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
a=Fib() | |
for i in a: | |
print(i) |
# getitem () 表现得像 list 那样按照下标取出元素
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
def __getitem__(self, item): | |
a,b=1,1 | |
for i in range(item): | |
a,b=b,a+b | |
return a | |
a=Fib() | |
print(a[3]) |
以上是传入 int,切片功能实现,isinstance 判断类型
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
def __getitem__(self, item): | |
if isinstance(item,int): | |
a, b = 1, 1 | |
for i in range(item): | |
a, b = b, a + b | |
return a | |
if isinstance(item,slice): | |
start=item.start | |
stop=item.stop | |
if start is None: | |
start=0 | |
a,b=1,1 | |
L=[] | |
for x in range(stop): | |
L.append(a) | |
a,b=b,a+b | |
return L | |
a=Fib() | |
print(a[3:12]) |
# getattr () 动态返回一个属性
调用类属性或方法时,先在__init__() 获取后,再从__getattr__() 获取,获取不到才报错
# call () 直接调用实例本身
与直接调用这个函数一样
class People(object): | |
def __init__(self,name): | |
self.name=name | |
def __call__(self, *args, **kwargs): | |
print('i im call %s'% self.name) | |
p=People('ruan') | |
p() |
# 使用枚举类
枚举类:在某些情况下,一个类的 实例对象 的数量是 有限且固定 的,如季节类,它的实例对象只有春、夏、秋、冬。 在 Java 中像这种对象实例有限且固定的类被称为枚举类;这样的枚举类型定义一个 class 类型,然后,每个常量都是 class 的一个唯一实例。Python 提供了 Enum
类来实现这个功能。
from enum import Enum | |
M=Enum('a',('sun1','sun2','sun3','sun4')) | |
print(M.sun1) |
自定义枚举类
from enum import Enum,unique | |
@unique | |
class Week(Enum): | |
sun1=1 | |
sun2=2 | |
sun3=3 | |
day2=Week.sun2 | |
print(day2) |
from enum import Enum,unique | |
@unique | |
class Gender(Enum): | |
Male=0 | |
Female=1 | |
class Student(object): | |
def __init__(self,name,gender): | |
self.name=name | |
self.gender=gender | |
# 测试: | |
bart = Student('Bart', Gender.Male) | |
if bart.gender == Gender.Male: | |
print('测试通过!') | |
else: | |
print('测试失败!') |
# 使用元类 [创建类]
实例对象是类创建
类是元类创建
创建类的方式
# 方式一:type()
type()
函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过 type()
函数创建出 Hello
类
from class1104 import * | |
h=Hello() | |
print(type(h)) | |
print(type(Hello)) |
Hello = type('Hello', (object,), dict(hello=fn)) |
要创建一个 class 对象, type()
函数依次传入 3 个参数:
- class 的名称;
- 继承的父类集合,注意 Python 支持多重继承,如果只有一个父类,别忘了 tuple 的单元素写法;
- class 的方法名称与函数绑定,这里我们把函数
fn
绑定到方法名hello
上
# 方式二:元类 metaclass
先定义 metaclass,然后创建类。
先定义类,然后创建实例。
metaclass 是 Python 面向对象里最难理解,也是最难使用的魔术代码。
按照默认习惯,metaclass 的类名总是以 Metaclass 结尾,以便清楚地表示这是一个 metaclass
# metaclass 采用 type 创建类 ,metaclass 是类的模板,所以必须从 `type` 类型派生 | |
class ListMetaclass(type): | |
def __new__(cls, name,bases,attrs): | |
attrs['add']=lambda self, value:self.append(value) | |
return type.__new__(cls,name,bases,attrs) | |
class MyList(list,metaclass=ListMetaclass): | |
pass | |
mylist=MyList() | |
mylist.add(1) | |
print(mylist) |
__new__()
方法接收到的参数依次是:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合
# 应用场景
ORM 全称 “Object Relational Mapping”,即对象 - 关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作 SQL 语句。
要编写一个 ORM 框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
# 错误处理 try
try: | |
print('try...') | |
r = 10 / 0 | |
print('result:', r) | |
except ValueError as e: | |
print('ValueError:', e) | |
except ZeroDivisionError as e: | |
print('ZeroDivisionError:', e)e) | |
finally: | |
print('finally...') | |
print('END') |
Python 的错误其实也是 class,所有的错误类型都继承自 BaseException
UnicodeError
是 ValueError
的子类🤡
Built-in Exceptions — Python 3.10.0 documentation
# 调用栈
让 Python 解释器来打印出错误堆栈
# 记录错误 logging
可将 logging 生成一个 txt 方便查看
try: | |
xxx | |
except Exception as e: | |
logging.exception(e) |
# 抛出错误 raise
except ValueError as e: | |
print('ValueError!') | |
raise |
在 bar()
函数中,我们明明已经捕获了错误,但是,打印一个 ValueError!
后,又把错误通过 raise
语句抛出去了,这不有病么?
其实这种错误处理方式不但没病,而且相当常见。捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。
# 调试方法
# 1. print()
# 2. 断言 assert
assert n != 0, 'n is zero!' |
assert
的意思是,表达式 n != 0
应该是 True
,否则,根据程序运行的逻辑,后面的代码肯定会出错。
采用断言的好处:
启动 Python 解释器时可以用 -O
参数来关闭 assert
:
$ python -O err.py |
关闭后,你可以把所有的 assert
语句当成 pass
来看
# 3. logging
import logging | |
s = '0' | |
n = int(s) | |
logging.info('n = %d' % n) | |
print(10 / n) |
# 4.pbd 单步执行
启动 Python 的调试器 pdb,让程序以单步方式运行,可以随时查看运行状态。
python -m pdb xxx.py | |
(Pbd) 1#查看第一行代码,单步执行第一行代码 |
# 5. pdb.set_trace()
这个方法也是用 pdb,但是不需要单步执行,我们只需要 import pdb
,然后,在可能出错的地方放一个 pdb.set_trace()
,就可以设置一个断点:
import pdb | |
s = '0' | |
n = int(s) | |
pdb.set_trace() # 运行到这里会自动暂停 | |
print(10 / n) |
可以用命令 p
查看变量,或者用命令 c
继续运行:
# 6.IDE 工具
vscode,pycharm....
# 单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
# 文档测试
doctest 非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含 doctest 的注释提取出来。用户看文档的时候,同时也看到了 doctest。
Python 内置的 “文档测试”(doctest)模块可以直接提取注释中的代码并执行测试.
class Dict(dict): | |
"""" | |
这一段就是文档测试 | |
Simple dict but also support access as x.y style. | |
>>> d1 = Dict() | |
>>> d1['x'] = 100 | |
>>> d1.x | |
100 | |
>>> d1.y = 200 | |
>>> d1['y'] | |
200 | |
>>> d2 = Dict(a=1, b=2, c='3') | |
>>> d2.c | |
'3' | |
>>> d2['empty'] | |
Traceback (most recent call last): | |
... | |
KeyError: 'empty' | |
>>> d2.empty | |
Traceback (most recent call last): | |
... | |
AttributeError: 'Dict' object has no attribute 'empty' | |
""" | |
def __init__(self, **kw): | |
super(Dict, self).__init__(**kw) | |
def __getattr__(self, key): | |
try: | |
return self[key] | |
except KeyError: | |
raise AttributeError(r"'Dict' object has no attribute '%s'" % key) | |
def __setattr__(self, key, value): | |
self[key] = value | |
if __name__ == '__main__': | |
import doctest | |
doctest.testmod() |
将其中一个函数注释,运行让它报错
# IO 编程
程序和运行时的数据在内存中驻留
涉及到数据交换的地方,通常是磁盘、网络等,就需要 IO 接口
通常,程序完成 IO 操作会有 Input 和 Output 两个数据流
Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。
在 IO 编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把 100M 的数据写入磁盘,CPU 输出 100M 的数据只需要 0.01 秒,可是磁盘要接收这 100M 数据可能需要 10 秒,怎么办呢?有两种办法:
# 同步 IO
第一种是 CPU 等着,也就是程序暂停执行后续代码,等 100M 的数据在 10 秒后写入磁盘,再接着往下执行,这种模式称为同步 IO;
# 异步 IO
另一种方法是 CPU 不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步 IO。
如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步 IO 的复杂度远远高于同步 IO。
# 文件读写
# 读文件 open()
传入文件名,标示符
参数:'rb' 二进制
encoding='gbk' 字符编码
f = open('/Users/michael/test.txt', 'r') |
# read () 一次读取全部内容
f.read() | |
'Hello, world!' |
# f.close()关闭文件
简化方法
# with open('filepath', 'r') as f: print(f.read())
Python 引入了 with
语句来自动帮我们调用 close()
方法,并且不必调用 f.close()
方法
with open('/path/to/file', 'r') as f: | |
print(f.read()) |
如果文件很小, read()
一次性读取最方便;
如果不能确定文件大小,反复调用 read(size)
比较保险;
如果是配置文件,调用 readlines()
最方便
for line in f.readlines(): | |
print(line.strip()) # 把末尾的 '\n' 删掉 |
file 和缓存时 = 是 file-like Object 对象,不要求从特定类继承,只要写个 read()
方法就行
# f.write () 写文件
f = open('/Users/michael/test.txt', 'w') | |
f.write('Hello, world!') | |
f.close() |
或
with open('/Users/michael/test.txt', 'w') as f: | |
f.write('Hello, world!') |
使用 with
语句操作文件 IO 是个好习惯
# StringIO 和 BytesIO
# StringIO
StringIO 顾名思义就是在内存中读写 str
from io import StringIO | |
f = StringIO() | |
f.write('hello') |
getvalue()
方法用于获得写入后的 str
from io import StringIO | |
>>> f = StringIO('Hello!\nHi!\nGoodbye!') | |
>>> while True: | |
... s = f.readline() | |
... if s == '': | |
... break | |
... print(s.strip()) |
# BytesIO
操作二进制数据,就需要使用 BytesIO
>>> from io import BytesIO | |
>>> f = BytesIO() | |
>>> f.write('中文'.encode('utf-8')) | |
6 | |
>>> print(f.getvalue()) | |
b'\xe4\xb8\xad\xe6\x96\x87' |
# os 模块
# os.name 操作系统类型
# os.uname () 详细系统信息
# os.enciron 环境变量
要获取某个环境变量的值,可以调用 os.environ.get('key')
# 查看当前目录的绝对路径: | |
>>> os.path.abspath('.') | |
'/Users/michael' | |
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来: | |
>>> os.path.join('/Users/michael', 'testdir') | |
'/Users/michael/testdir' | |
# 然后创建一个目录: | |
>>> os.mkdir('/Users/michael/testdir') | |
# 删掉一个目录: | |
>>> os.rmdir('/Users/michael/testdir') |
通过 os.path.join()
函数,这样可以正确处理不同操作系统的路径分隔符
# os.path.join(
) 连接路径
# os.path.split()
拆分路径
# os.path.splitext()
文件扩展名
# 对文件重命名: | |
>>> os.rename('test.txt', 'test.py') | |
# 删掉文件: | |
>>> os.remove('test.py') |
shutil
模块提供了 copyfile()
的函数,它们可以看做是 os
模块的补充
最后看看如何利用 Python 的特性来过滤文件。比如我们要列出当前目录下的所有目录,只需要一行代码:
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
要列出所有的 .py
文件,也只需一行代码:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
# 序列化 pickle 模块
变量从内存中变成可存储或传输的过程称之为序列化,Python 中叫 pickling
变量内容从序列化的对象重新读到内存里称之为反序列化,即 unpickling
# pickle.dumps () 对象 -》字节 [序列化]
pickle.dumps()
方法把任意对象序列化成一个 bytes
pickle.dumps()
方法把任意对象序列化成一个 bytes
, 并写入文件中
import pickle | |
d=dict(name='ruan',age=34,freand='woman') | |
# print(pickle.dumps(d)) | |
f = open('timezone.txt', 'wb') | |
pickle.dump(d, f) | |
f.close() |
# pickle.load () 字节 -》对象【反序列化】
import pickle | |
f=open(r'C:\Users\yangs\PycharmProjects\python_study\fun\timezone.txt','rb') | |
d=pickle.load(f) | |
f.close() | |
print(d) |
# json 模块
json
模块的 dumps()
和 loads()
函数是定义得非常好的接口的典范。
# json.dumps (python 对象) python 对象 -》json 对象
dumps()
方法返回一个 str
,内容就是标准的 JSON
# json.loads (json 对象) json 对象 -》python 对象
json.``dump
(obj, fp, , skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, kw)*
# 类变为字典并序列化
json.dumps(s, default=lambda obj: obj.__dict__) |
# 进程和线程
Python 的标准库提供了两个模块: _thread
和 threading
, _thread
是低级模块, threading
是高级模块
线程是最小的执行单元,而进程由至少一个线程组成
操作系统轮流让各个任务交替执行
真正的并行执行多任务只能在多核 CPU 上实现
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程
Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个 “子任务”,我们把进程内的这些 “子任务” 称为线程(Thread)
- 多进程模式;
- 多线程模式;
- 多进程 + 多线程模式。
# 多进程
Unix/Linux 操作系统提供了一个 fork()
系统调用,普通的函数调用,调用一次,返回一次,但是 fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
创建子进程
from multiprocessing import Process | |
import os | |
def run_pro(name): | |
print('开始运行子进程%s,%s'%(name,os.getpid())) | |
if __name__ == '__main__': | |
print('开始运行进程%s' % (os.getpid())) | |
p=Process(target=run_pro,args=('test',)) | |
p.start() | |
p.join() | |
print('end') |
# 启动大量子进程 pool
进程池
import time, threading | |
# 新线程执行的代码: | |
def loop(): | |
print('thread %s is running...' % threading.current_thread().name) | |
n = 0 | |
while n < 5: | |
n = n + 1 | |
print('thread %s >>> %s' % (threading.current_thread().name, n)) | |
time.sleep(1) | |
print('thread %s ended.' % threading.current_thread().name) | |
print('thread %s is running...' % threading.current_thread().name) | |
t = threading.Thread(target=loop, name='LoopThread') | |
t.start() | |
t.join() | |
print('thread %s ended.' % threading.current_thread().name) |
# 模块总结
# doctest 文档测试
# os.path 文件路径
# pickle 序列化
# json
使用 dict 和 set - 廖雪峰的官方网站 (liaoxuefeng.com)
用于学习记录,后期便于复习,参考链接
python file
调用脚本时会先载入 pyhton 解释器,然后运行脚本
rpm:软件管理包
操作符优先级:
条件判断:
Python 为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容,被形象地称作 “内置电池(batteries included)”。用 Python 开发,许多功能不必从零编写,直接使用现成的即可。
语言定位:
Python 的定位是 “优雅”、“明确”、“简单”
Python 是解释型语言
# python
# 数据类型
# int 整型
long int 长整型
int 整型
# float 浮点型
浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时
双精度浮点型 e
浮点型
# String 字符串
字符串是以单引号 '
或双引号 "
括起来的任意文本
字符串是以 Unicode 编码
对于单个字符的编码,Python 提供了 ord()
函数获取字符的整数表示, chr()
函数把编码转换为对应的字符:
# Bool 布尔
True
Flase
# None 空值
None, None
不能理解为 0
,因为 0
是有意义的,
Null 无意义
.
<iframe src="http://127.0.0.1:6806/widgets/brython-editor" data-src="http://127.0.0.1:6806/widgets/brython-editor" data-subtype="widget" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 1341px; height: 276px;"></iframe>
# 变量
变量的概念基本上和初中代数的方程变量是一致的,
变量不仅可以是数字,还可以是任意数据类型。
变量名必须是大小写英文、数字和 _
的组合,且不能用数字开头,字母或下划线开头
#变量类型 | |
a=1 | |
b='我是变量' | |
c=True | |
d=12.2e3 | |
print(a,b,c,d) | |
print(type(a),type(b),type(c),type(d)) |
int a=1 静态语言 此时已经分配的 int 分区之后不能更改变量类型【不支持,Java】
a=3 动态语言,可以赋值成任意类型
# 动态定义
# 静态定义
a = 'ABC' | |
b = a | |
a = 'XYZ' | |
print(b) |
执行 a = 'ABC'
,解释器创建了字符串 'ABC'
和变量 a
,并把 a
指向 'ABC'
:
执行 b = a
,解释器创建了变量 b
,并把 b
指向 a
指向的字符串 'ABC'
:
执行 a = 'XYZ'
,解释器创建了字符串 'XYZ',并把 a
的指向改为 'XYZ'
,但 b
并没有更改:
# 常量
所谓常量就是不能变的变量,通常用全部大写的变量名表示常量:
PI = 3.14159265359 |
# list 列表
list 是一种有序的集合,可以随时添加和删除其中的元素。
list=['a','b','c'] | |
print(list) | |
print(len(list)) |
# 切分
# append()追加
str.append('a')
# insert 插入指定位置
# pop()删除末尾元素
要删除指定位置的元素,用 pop(i)
方法,其中 i
是索引位置
替换元素直接赋值即可
列表可以嵌套
s = ['python', 'java', ['asp', 'php'], 'scheme'] |
类型可以不同
L = ['Apple', 123, True] |
# 切片
list [:-1] 不包含最后一个元素
list [:] 全部列表
list [::] 全部列表
前 10 个数,每两个取一个
# 列表生成
list(range(1,11))生成 10 个数 1-10
print([m+n for m in '123' for n in 'yza']) |
k=[1,1,23,45,56,[1,12,3,467,[2,4,4,3,22]]] | |
print([x for x in k if x==1 ]) |
# tuple 元组
tuple 一旦初始化就不能修改
但元组初始化后就不能进行更改了
b=(1) | |
#定义的不是 tuple,是 1 这个数! | |
# 这是因为括号 () 既可以表示 tuple,又可以表示数学公式中的小括号, | |
# 这就产生了歧义,因此,Python 规定, | |
# 这种情况下,按小括号进行计算,计算结果自然是 1 | |
print(b) | |
print(type(b)) | |
c=(1,) | |
print(c) | |
print(type(c)) |
# dict 字典
其他语言叫 map,使用键 - 值(key-value)存储,具有极快的查找速度。dict 的 key 必须是不可变对象。key 计算位置的算法称为哈希算法(Hash)。
# 定义
d={'name':'ruanyifen','age':60,'happy':'write'} | |
d['add']='Im add' | |
d['name']='fix' |
# 取 value
# dict['key']
# 'key' in dict
# dict.get('key')
print(type(d)) | |
print(d['name']) | |
print('name' in d)#方法一判断是否有这个主键在字典 d 中 | |
print(d.get('name'))#方法二 取 |
# dict.pop ('key') 删除一个 key
# dict.keys 返回字典中所有 key 列表
# dict.update () 将 a 字典新 key,value 内容加入 b 字典中
dicta={"name":'ruan','age':20} | |
dictb={"name":'ruan2','age':40,'add':'w shi add'} | |
dictb.update(dicta) | |
print(dictb) |
# 内建函数使用
type()
cmp()
len()
hash()
内建 cmp()函数比较两个 dict 时,先比较长度,后比值,输出 1 或 - 1
# dict 特点
dict 有以下几个特点:
- 查找和插入的速度极快,不会随着 key 的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而 list 相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
# set 集合
也是一组 key 的集合,但不存储 value。由于 key 不能重复,所以,在 set 中,没有重复的 key。
重复元素在 set 中自动被过滤
# 定义
# set.add ('key') 添加元素
但重复元素不添加,自动去重
# set.remove ('key') 删除元素
set 可以看成数学意义上的无序和无重复元素的集合,
因此,两个 set 可以做数学意义上的交集、并集等操作:
# & 两个 set 交集
# | 两个 set 并集
# map () 的显示
打印 map 对象可以看到 map 对象返回的是一个地址,不是真实的数据
print(list(map对象)) | |
print([it for it in map对象]) |
# 数据类型转换
# int()
# float()
# str()
# bool()
# 条件判断
# if
if else
a=100 | |
if a>=0: | |
print(a) | |
else: | |
print(-a) |
if
if True: | |
print('True') |
if elif elif else
name='zhangsan' | |
if name=='zhangsan': | |
print('我是',name) | |
elif name=='lisi': | |
print('我是', name) | |
elif name=='wangwu': | |
print('我是', name) | |
else: | |
print('我谁的不是') |
# input()输入输出
input () 返回的数据类型是 str
print()
# 循环 迭代
list,tuple,dict 都可循环
Python 的 for
循环本质上就是通过不断调用 next()
函数实现的,计算是惰性的
dict 循环按照 value 时:for value in dict.values
for value in d.values(): | |
print(value) |
# for in
sum=0 | |
for i in range(1,100): | |
sum=sum+i | |
print(sum) |
# while
sum2=0 | |
k=0 | |
while(k<100): | |
sum2=sum2+k | |
k=k+1 | |
print(sum2) |
# break
如果要提前结束循环,可以用 break
语句
# continue
通过 continue
语句,跳过当前的这次循环,直接开始下一次循环
# 生成器
在 Python 中,这种一边循环一边计算的机制,称为生成器:generator。
包括生成器和带 yield
的 generator function。
g = (x * x for x in range(10)) |
访问大文件
yield
# isinstance()迭代器
直接作用于 for
循环的对象统称为可迭代对象,都是迭代器 Iterable
list
、 tuple
、 dict
、 set
、 str
list
、 dict
、 str
虽然是 Iterable
,却不是 Iterator
。
list
、 dict
、 str
等 Iterable
变成 Iterator
可以使用 **iter ()** 函数
以直接作用于 for
循环的数据类型有以下几种:
一类是集合数据类型,如 list
、 tuple
、 dict
、 set
、 str
等;
一类是 generator
,包括生成器和带 yield
的 generator function。
可以使用 **isinstance ()** 判断一个对象是否是 Iterable
对象__iter__:
迭代对象
判断是不是可以迭代,用 Iterable
from collections import Iterable | |
isinstance({}, Iterable) --> True | |
isinstance((), Iterable) --> True | |
isinstance(100, Iterable) --> False |
判断是不是迭代器,用 Iterator
from collections import Iterator | |
isinstance({}, Iterator) --> False | |
isinstance((), Iterator) --> False | |
isinstance( (x for x in range(10)), Iterator) --> True |
Python 中 list,truple,str,dict 这些都可以被迭代,但他们并不是迭代器,为什么::因为和迭代器相比有一个很大的不同,list/truple/map/dict 这些数据的大小是确定的,也就是说有多少事可知的。但迭代器不是,迭代器不知道要执行多少次,所以可以理解为不知道有多少个元素,每调用一次 next (),就会往下走一步,是惰性的。
# 函数
抽象
将函数抽象成一个函数名称,不看内部结构直接调用方法
返回类型 函数名(输入参数):
函数体
# 调用函数
要调用一个函数,需要知道函数的名称和参数
绝对值 abs
# 定义函数
def myabs(x): | |
if x>0: | |
return x | |
if x<0: | |
return -x | |
print(myabs(-100)) |
# 空函数
def nufun(): | |
pass |
pass
可以用来作为占位符
# 函数 参数检查
def my_init_abs(x): | |
if not isinstance(x,(int,float)): | |
raise TypeError('no no no') | |
else: | |
if x>0: | |
print(x) | |
if x<0: | |
print(-x) | |
my_init_abs(-90) |
# 可返回多个值,函数
def return_much(): | |
a='返回' | |
b='我也返回' | |
c='我也要返回' | |
return a,b,c | |
print(return_much()) | |
print(type(return_much())) |
# 函数参数
*args 是可变参数,args 接收的是一个 tuple;
**kw 是关键字参数,kw 接收的是一个 dict。
power(x)
函数,参数 x
就是一个位置参数,可单个变量,list,set,tuple
power(*x)
函数,可传入单个变量,list,set,tuple,可以传入任意个参数或 0 个参数
power(**kw)
函数,字典 dict
可变参数允许你传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个 tuple。
而关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个 dict.
power(x, n)
,用来计算 xn
power(x, n)
函数有两个参数: x
和 n
默认参数,此时 age 和 city 为默认参数,可传值改变也可不变【不用传值】
power(L=None)
函数有 None 这个不变对象,可用 list
def enroll(name, gender, age=6, city='Beijing'): | |
print('name:', name) | |
print('gender:', gender) | |
print('age:', age) | |
print('city:', city) |
# 必选参数
def a1(x): | |
return x | |
print(a1(2)) | |
print(a1(12.1)) | |
print(a1('ruan')) | |
print(a1(True)) | |
print(a1([1,2,3])) | |
print(a1({1,2,3})) | |
print(a1({"key":"vleaue",'name':'ruan','mun':23})) |
# = 默认参数
def a2(x=9): | |
return x | |
print(a2()) | |
print(a2(2)) | |
print(a2(12.1)) | |
print(a2('ruan')) | |
print(a2(True)) | |
print(a2([1,2,3])) | |
print(a2({1,2,3})) | |
print(a2({"key":"vleaue",'name':'ruan','mun':23})) |
# * 可变参数
def a3(*x): | |
return x | |
print(a3()) | |
print(a3(2)) | |
print(a3(12.1)) | |
print(a3('ruan')) | |
print(a3(True)) | |
print(a3([1,2,3])) | |
print(a3({1,2,3})) | |
print(a3({"key":"vleaue",'name':'ruan','mun':23})) |
# ** 关键字参数
def a3(**kw): | |
return kw | |
print(a3()) | |
print(a3(kw=2)) | |
print(a3(kw=12.1)) | |
print(a3(kw='ruan')) | |
print(a3(kw=True)) | |
print(a3(kw=[1,2,3])) | |
print(type(a3(kw=[1,2,3]))) | |
print(a3(kw={1,2,3})) | |
print(type(a3(kw={1,2,3}))) | |
print(a3(kw={"key":"vleaue",'name':'ruan','mun':23})) |
# 递归函数
在函数内部,可以调用其他函数。
一个函数在内部调用自身本身,这个函数就是递归函数。
使用递归函数需要注意防止栈溢出
#递归函数 | |
def funmyself(x): | |
if x>1: | |
return x+funmyself(x-1) | |
elif x==1: | |
return 1 | |
print(funmyself(3)) |
解决栈溢出方法:
尾递归优化,事实上尾递归和循环的效果是一样的
尾递归是指,在函数返回的时候,调用自身本身,并且,return 语句不能包含表达式
#尾递归 | |
def funmyself2(n): | |
return funmyself2_it(n,1) | |
def funmyself2_it(n,pro): | |
if n==1: | |
return pro | |
else: | |
return funmyself2(n-1)+n | |
print(funmyself2(100)) |
此时 funmyself2 是尾递归函数
# 转义字符 \
转义字符 \
可以转义很多字符,比如
\n
表示换行,
\t
表示制表符,
字符 \
本身也要转义,所以 \\
表示的字符就是 \
Python 还允许用 r''
表示 ''
内部的字符串默认不转义
# 运算符 and、or 和 not
运算优先级:not>or>and
# 除法 ///
print(10/3) | |
print(10//3) |
# 除法一 / 浮点数
# 除法二 // 地板除 整数
/
除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
//
,称为地板除,两个整数的除法仍然是整数:
# 取余 %
print(10%1) | |
print(10%3) | |
print(4%7) | |
print(2%20) | |
#如果 a% b a>b 则结果为 a |
# 字符编码
ASCII
编码
8 个比特(bit)作为一个字节(byte)
一个字节能表示的最大的整数就是 255(二进制 11111111 = 十进制 255)
两个字节可以表示的最大整数是 65535
,4 个字节可以表示的最大整数是 4294967295
大写字母 A
的编码是 65
,二进制的 01000001
,小写字母 z
的编码是 122
Unicode 把所有语言都统一到一套编码里,这样就不会再有乱码问题
ASCII 编码是 1 个字节,而 Unicode 编码通常是 2 个字节
ASCll 出现乱码问题引入 Unicode 编码存储空间多了一倍引入 UTF-8 编码
utf-8:将 Unicode 字符根据不同的数字大小编码成 1-6 个字节,常用的英文字母被编码成 1 个字节,汉字通常是 3 个字节,
字符 | ASCII | Unicode | UTF-8 |
---|---|---|---|
A | 01000001 | 00000000 01000001 | 01000001 |
中 | x | 01001110 00101101 | 11100100 10111000 10101101 |
在计算机内存中,统一使用 Unicode 编码,当需要保存到硬盘或者需要传输的时候,就转换为 UTF-8 编码。
用记事本编辑的时候,从文件读取的 UTF-8 字符被转换为 Unicode 字符到内存里,编辑完成后,保存的时候再把 Unicode 转换为 UTF-8 保存到文件
浏览网页的时候,服务器会把动态生成的 Unicode 内容转换为 UTF-8 再传输到浏览器:
# compile () 字符串编译为字节代码
# 编码转化
# ord ('A') 字母转字符
# chr (65) 字符转字母
a=ord('A') | |
b=chr(65) | |
print(a,b) |
# b'str' 转为字节类型 bytes
bytes
类型的数据用带 b
前缀的单引号或双引号表示
x = b'ABC' |
要注意区分 'ABC'
和 b'ABC'
,前者是 str
,后者虽然内容显示得和前者一样,但 bytes
的每个字符都只占用一个字节。
# str.encode('ascii') str
变为 bytes
ASCII
UTF-8
# str.decode('utf-8') bytes
变为 str
print(type(b'abc')) | |
print(b'abc'.decode('utf-8')) | |
print(type(b'abc'.decode('utf-8'))) |
len(str)计算字符数
函数计算的是 str
的字符数,如果换成 bytes
, len()
函数就计算字节数
print(len('abc')) | |
print(len(b'abc')) | |
print(len('中')) | |
print(len('中'.encode('utf-8'))) |
# 特殊注释
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- |
第一行注释是为了告诉 Linux/OS X 系统,这是一个 Python 可执行程序,Windows 系统会忽略这个注释;
第二行注释是为了告诉 Python 解释器,按照 UTF-8 编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
# 占位符 格式化
# 占位符 % s % d % f
格式化方式和 C 语言是一致
%
运算符就是用来格式化字符串的。
在字符串内部,
%s
表示用字符串替换,
%d
表示用整数替换,
有几个 %?
占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个 %?
,括号可以省略
占位符 | 替换内容 |
---|---|
%d | 整数 |
%f | 浮点数 |
%s | 字符串 |
%x | 十六进制整数 |
转义: %%
来表示一个 %
# format()格式化字符串
# f-string 格式化字符串
{r}
被变量 r
的值替换, {s:.2f}
被变量 s
的值替换,并且 :
后面的 .2f
指定了格式化参数(即保留两位小数),因此, {s:.2f}
的替换结果是 19.62
# 函数式编程
函数是 Python 内建支持的一种封装,通过层层函数进行调用
#面向过程的程序设计 #:把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计
函数式和函数的区别:
对比例子:计算和计算器的区别
编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如 C 语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如 Lisp 语言
Python 不是纯函数式编程语言
函数式编程就是一种抽象程度很高的编程范式,
纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的
# 函数式编程特点:
- 纯函数式编程语言函数没有变量,输入输出确定
- 允许本身作为参数传入另一个函数,允许返回一个函数
# 高阶函数
参数中有函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
# 变量可以指向函数
a = 函数
求绝对值的函数 abs()
为例
print(abs(-10)) | |
print(abs) |
abs(-10)是函数调用,abs 是函数本身
k=abs(-20) | |
print(k) | |
#函数本身也可以赋值给变量 | |
h=abs | |
print(h) | |
print(h(-100)) |
结论:函数本身也可以赋值给变量,即:# 变量可以指向函数。#
# 函数名也是变量
#函数名 #:其实就是指向函数的变量
a () 中 a 是指向函数 a()的变量
# 传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
#高阶函数 #:一个函数就可以接收另一个函数作为参数
b()
a(b)
x=a
def f(x): | |
return abs(int(x)) | |
def a(a,b,f): | |
return f(a)+f(b) | |
if __name__ == '__main__': | |
a1=input('a') | |
a2=input('b') | |
print(a(a1,a2,f)) |
此时函数 a 为高阶函数,需要调用 f 函数作为参数
# map/reduce 内建函数
内建了 map()
和 reduce()
函数 高阶函数
# map()函数处理生成新 Iterator 迭代器
两个参数,函数名【函数本身】,需要处理的编程式 iterator
<br />
创建一个迭代器,使用每个迭代器中的参数计算函数。当最短迭代用尽时停止。
map(func, *iterables) --> map object |
def f(x): | |
return x*x | |
r=map(f,[1,2,3,4,4,4,4,4,4,4,4]) | |
print(r) | |
print(type(r)) | |
print(list(r)) | |
print(type(list(r))) |
运算规则抽象
# reduce()函数作用在序列上
两个参数,函数名【函数本身】,需要处理的 #序列 #: sequence (序列) 是一组有顺序的元素的集合
序列基本样式 [下限:上限:步长]
reduce
把结果继续和序列的下一个元素做累积计算
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) |
from functools import reduce | |
>>> def fn(x, y): | |
... return x * 10 + y | |
... | |
>>> reduce(fn, [1, 3, 5, 7, 9]) | |
13579 |
# filter () 过滤序列
参数和 map()相似
filter()
也接收一个函数和一个序列
# sorted()排序
高阶函数
参数:排序对象,key = 函数
sorted([36, 5, -12, 9, -21], key=abs) |
排序的核心是比较两个元素的大小
print(sorted([1,2,353,6,3,234,43,435])) |
key 指定绝对值大小排序
print(sorted([1,2,353,6,3,234,43,435,-242,-34,34,35],key=abs)) |
# 返回函数
# 函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
# 如果不需要立刻求和,而是在后面的代码中, | |
# 根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数: | |
def zary_sum(a): | |
def sum(): | |
sum1=0 | |
for i in a: | |
sum1=sum1+i | |
return sum1 | |
return sum | |
print(type(zary_sum([1,2,3,4]))) | |
f=zary_sum([1,2,3,4]) | |
print(f()) |
调用返回函数时,每次调用都会新生成一个函数
# 闭包
当一个函数的返回值是另外一个函数,
而返回的那个函数如果调用了其父函数内部的其它变量,如果 返回的这个函数在外部被执行,就产生了闭包 。
返回函数中,返回的函数调用父函数的内部变量
#返回函数 | |
def count(): | |
fs=[] | |
for i in range(1,4): | |
def f(): | |
return i*i | |
fs.append(f) | |
return fs | |
f1,f2,f3=count() | |
print(f1(),f2(),f3()) |
返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。
# lambda()匿名函数
lambda 关键字 函数参数:函数表达式
传入函数时,有些时候,不需要显式地定义函数
Python 对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
lambda x:x*x | |
#等价于 | |
def f(x): | |
return x*x |
关键字 lambda
表示匿名函数,冒号前面的 x
表示函数参数,只能一个表达式
不用写 return
,返回值就是该表达式的结果。
匿名函数也是一个函数对象
f=lamdba x:x*x |
判断奇数函数
原函数:
def is_odd(n): | |
return n % 2 == 1 | |
L = list(filter(is_odd, range(1, 20))) |
采用匿名函数修改
l=list(filter(lambda x:x%2==1,range(1,20))) | |
print(l) |
# 装饰器 Decorator
# 本质上,装饰器就是一个返回函数的高阶函数
@log 等价于
now = log(now) |
由于函数也是一个对象,而且函数对象可以被赋值给变量,
所以,通过变量也能调用该函数
def log(func): | |
def wrapper(*args,**kwargs): | |
print('call %s'% func.__name__) | |
return func(*args,**kwargs) | |
return wrapper() | |
#func 为参数所以是高阶函数 | |
#return 函数所以是返回函数, | |
#没有调用父函数中参数,所以不是闭包 |
场景注意:
无 @装饰器时函数不调用,需要参数才调用
当 @时会直接调用装饰器定义函数然后执行函数,不用调用函数
三层时,传入参数
def log1(text): | |
def decorator(func): | |
def wapper(*args,**kw): | |
print('%s %s'%(text,func.__name__)) | |
return func(*args,**kw) | |
return wapper | |
return decorator | |
@log1('ruan') | |
def now3(): | |
print("hhh") | |
now3() |
相当于在返回高阶函数上还有一个函数,所以返回时应该还要调用一次
# @wraps 常用装饰器
当装饰器是个闭包时,装饰器调用变量会改变增加 @wraps 后装饰器内的变量不变
装饰器在装饰一个函数时,,原函数就成了一个新的函数,也
就是说其属性会发生变化,所以为了 不改变原函数的属性,
我们会调用 functools 中的 wraps 装饰器来保证原函数的属性不变
# 不加 wraps 时
@wraps(func) |
from functools import wraps | |
def wrap(func): | |
def b(): | |
'b' | |
print('decorator:',b.__name__) | |
print('funname',func.__name__) | |
func() | |
return b | |
@wrap | |
def a(): | |
'a' | |
print('name',a.__name__) | |
a() |
加装饰器 wraps 时
from functools import wraps | |
def wrap(func): | |
@wraps(func) | |
def b(): | |
'b' | |
print('decorator:',b.__name__) | |
print('funname',func.__name__) | |
func() | |
return b | |
@wrap | |
def a(): | |
'a' | |
print('name',a.__name__) | |
a() |
闭包的概念:调用父函数中的变量的函数,为了保证数据安全。变量作用域只在函数内部,可在闭包中操作数据。
装饰器返回为什么是函数名(函数内存地址)而不直接执行函数?
当有参数传入时,可直接与调用的函数中的值传入参数执行。
()是运算符 f () 与 f.call () 等价:将 f 对象变成变成可调用的对象
# 偏函数(functools 模块)
属于 functools 模块
# 作用:
通过设定参数的默认值,降低函数调用的参数
int()
函数默认按十进制转换
print(int('100',base=8))
经常调用于是重写一个函数 int2
def int2(x, base=8): | |
print(int(x, base)) | |
return int(x, base) | |
print(int2('2334')) |
采用偏函数
import functools | |
int3=functools.partial(int,base=8) | |
print(int3('46')) | |
print(int()) |
functools.partial 的作用是将函数的特定参数固定住(设定为默认值)
创建偏函数的时候也可以接收,函数对象,*args,**kw
# 模块
python 包:作用区分相同名称的模块
模块相当于一个 py 文件
# 作用域
仅仅在模块内部使用。在 Python 中,是通过 _
前缀来实现的。
# pubilc 公开
正常的函数和变量名是公开的(public)
# private 非公开_,__
_xxx 和__xxx 这样的函数或变量就是非公开的(private)
# 安装第三方模块 pip
pip install 模块名
# 模块搜索路径
import sys | |
print(sys.path) |
两种方式:
- 添加搜索路径
import sys
sys.path.append('/Users/michael/my_py_scripts')
- 设置环境变量
第二种方法是设置环境变量 PYTHONPATH
# 面向对象编程
面向对象编程 ——Object Oriented Programming,简称 OOP,是一种程序设计思想
对象作为程序的基本单元,
一个对象包含了数据和操作数据的函数
数据封装、继承和多态是面向对象的三大特点
# 类和实例
面向对象最重要的概念就是类(Class)和实例(Instance)
类是抽象出来的模板
实例是根据类创建出的对象,每个对象可能有属性和方法
定义类是通过 class
关键字,类名通常是大写开头的单词
class Student(object): | |
pass |
!!!在类中定义函数有一点不同,定义佛如方法第一个参数永远是实例变量本身 self
仍然可以用默认参数、可变参数、关键字参数和命名关键字参数
# 数据封装
class Student(object): | |
def __init__(self, name, score): | |
self.name = name | |
self.score = score | |
def get_grade(self): | |
if self.score >= 90: | |
return 'A' | |
elif self.score >= 60: | |
return 'B' | |
else: | |
return 'C' |
# 访问限制
# 作用:
确保了外部代码不能随意修改对象内部的状态
实例的变量名如果以 __
开头,就变成了一个私有变量(private)
外部无法访问_name
class Student(object): | |
def __init__(self,name,age): | |
self._name=name | |
self.age=age | |
def print_name(self): | |
print(self._name) | |
return self.age,self._name | |
a=Student('ruan',23) | |
h=a.print_name() | |
print(h) |
若是要获取,修改变量增加 get,set 方式即可
class Student(object): | |
def __init__(self,name,age): | |
self._name=name | |
self.age=age | |
def get_name(self): | |
return self.name | |
def set_name(self,name): | |
self._name=name |
Python 本身没有任何机制阻止你干坏事,一切全靠自觉。
类外部无法访问
# 继承和多态
# 继承
# 多态
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。
比如:动物是父类,狗和鱼是子类;鱼是鱼类,鱼是动物都成立。
判断一个变量是否是某个类型可以用 isinstance()
判断
# 鸭子类型
并不要求严格的继承体,一个对象只要 “看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
# 获取对象信息
# type()判断对象类型
# isinstance () 对于继承关系,判断 class 的类型
# dir()获取对象的所有属性和方法
# len()对象长度
# lower()返回小写的字符串
# getattr()获取属性 a
# setattr()设置属性 a
# hasattr(obj,'a')判断是否有属性 a
getattr(obj, 'z', 404) # 获取属性 'z',如果不存在,返回默认值 404 | |
404 |
# 实例属性和类属性
class Student(object): | |
name='ruan' | |
h=Student() | |
h.name='hhh' |
类中的 name 是类属性,
创建 h 类对象即实例后赋值的是实例属性 name,但由于实例对象的优先级比类属性高,会屏蔽类中的 name 属性,即 h.name 的值为 hhh
# 总结:
- 实例属性属于各个实例所有,互不干扰;
- 类属性属于类所有,所有实例共享一个属性;
- 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误
# 面向对象高级编程
数据封装、继承和多态只是面向对象程序设计中最基础的 3 个概念
多重继承、定制类、元类
# _slots_使用
可以给创建的实例绑定属性和方法
给一个实例绑定的方法对另外一个实例对象是不起作用的
class A: | |
def run(self): | |
print("i im ferther runing....") | |
sun1=A() | |
#给实例 sun1 设置 name 属性 | |
sun1.name='i im name' | |
#创建实例对象 2 | |
sun2=A() | |
#实例对象 sun1 的属性和 sun2 无关,即 sun2 没有 name 属性 | |
#给实例 sun1 绑定方法,方法和属性同理 | |
#定义方法 | |
def setAll(self,num): | |
print(num) | |
sun1.newfun=MethodType(setAll, sun1) | |
sun1.newfun(37) | |
#若所有实例都需要绑定方法则给类绑定方法 | |
A.setAll=setAll | |
#给类绑定方法后,所有创建的实例的均可调用 |
def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
# 限制实例属性,定义一个特殊的 __slots__
变量
class Student(object): | |
__slots__ = ('name', 'age') # 用 tuple 定义允许绑定的属性名称 | |
s=Student() | |
s.name='ruan' | |
s.firstname='i im firstname' | |
#输出的时候 firstname 的属性会报错, | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
AttributeError: 'Student' object has no attribute 'firstaname' |
# 注意:
_slots_使用时要注意,定义的属性只在当前的类的实例中,对于继承的子类是不起作用的
class People(object): | |
__slots__ = ('name','age') | |
def run(self): | |
print('i im run people......') | |
class Teacher(People): | |
def say(self): | |
print('i im teacher....') | |
t=Teacher() | |
t.tall='shouhua' | |
print(t.tall) | |
p=People() | |
p.tall('shouhuap') | |
print(p.tall) |
只限制父类 People 的属性,而子类 Teacher 中不限制
# @property
在绑定属性时,如果我们直接把属性暴露出去,导致可以随意更改。通过 get,set 来获取更改属性值。
在 python 中直接调用装饰器将一个方法变成属性调用
class Student(object): | |
@property | |
#使用 get 方法是调用装饰器 @peoperty, | |
# 同时自动创建了另一个装饰器 @属性.setter | |
def score(self): | |
return self.score | |
@score.setter | |
def score(self,value): | |
self._score=value |
# 总结:
- 权限限制只对类对象实际起作用,想要达到方法和属性强制访问权限,需要使用 @property 装饰器进行 get,set 方法
属性名与方法名一定要区分开,不然会进入死循环(self._age,def age ())
实例化的对象使用属性时,不是调用属性(meizi._age),而是用的方法名(meizi.age)
@property 其实就是实现了 getter 功能; @xxx.setter 实现的是 setter 功能;还有一个 @xxx.deleter 实现删除功能
定义方法的时候 @property 必须在 @xxx.setter 之前,且二者修饰的方法名相同(age ())
如果只实现了 @property(而没有实现 @xxx.setter),那么该属性为 只读属性
#请利用 @property 给一个 Screen 对象加上 width 和 height 属性, | |
# 以及一个只读属性 resolution: | |
class Screen(object): | |
__slots__ = ('_width','_height','_resolution') | |
@property | |
def width(self): | |
return self._width | |
# 方法名称和实例变量均为 width: | |
@width.setter | |
def width(self,widthValue): | |
self._width=widthValue | |
@property | |
def height(self): | |
return self._height | |
@width.setter | |
def height(self, height): | |
self._height = height | |
@property | |
def resolution(self): | |
return self._width * self._height | |
s=Screen() | |
s.width=23 | |
s.height=12 | |
print(s.resolution) |
s.score = 60 # OK,实际转化为 s.set_score (60) | |
s.score # OK,实际转化为 s.get_score () |
要特别注意:属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
class Student(object):
# 方法名称和实例变量均为birth:
@property
def birth(self):
return self.birth
出现递归调用错误
之前的例子中 width 和_width 不同所以可以运行
# 多重继承
python 可以支持多继承,即一个子类可以继承多个父类;但 java 是单继承,只能有一个父类
Tercher(Name,study,teach)即 Teacher 可以继承多个父类
# MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如, Teacher
继承自 Name。但是,如果需要 “混入” 额外的功能,通过多重继承就可以实现,比如 Teacher 除了继承自 Name
外,再同时继承 Teach
。这种设计通常称之为 MixIn
Python 自带了 TCPServer
和 UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由 ForkingMixIn
和 ThreadingMixIn
提供。
# 多继承
多重继承这个名词一般用来形容继承链条可以很长,多个层次。
# 多重继承
多继承则指一个类可以有多个基类,相反则是单继承。任何面向对象编程语言都支持多重继承,但像 java 这种只能通过接口实现有限程度的多继承
问:多继承 如果多个类有共同得方法名 怎么区分是调得哪个类🤡
答:调用该方法的时候,会调用第一顺位继承父类的方法
# 总结:
- Python 允许使用多重继承,因此,MixIn 就是一种常见的设计
- 只允许单一继承的语言(如 Java)不能使用 MixIn 的设计
# 定制类
Python 的 class 中还有__xxx__有特殊用途的函数,可以帮助我们定制类
# str () 回用户看到的字符串
将对象 <__main__.Student object at 0x109afb190>
变成易读的数据
只在调用 print 时会调用__str__,交互界面时还是现实上方不易读的对象内容,此时用
# repr () 返回程序开发者看到的字符串
__str__()
返回用户看到的字符串,而 __repr__()
返回程序开发者看到的字符串,
也就是说, __repr__()
是为调试服务的
简写
def __str__(self): | |
return 'xxx object (name=%s)' % self.name | |
__repr__ = __str__ |
# _iter () 返回一个迭代对象
需要用到 for in 迭代,需要转化为迭代对象
该方法返回一个迭代对象,然后,Python 的 for 循环就会不断调用该迭代对象的 __next__()
方法拿到循环的下一个值,直到遇到 StopIteration
错误时退出循环
例子:
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
a=Fib() | |
for i in a: | |
print(i) |
# getitem () 表现得像 list 那样按照下标取出元素
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
def __getitem__(self, item): | |
a,b=1,1 | |
for i in range(item): | |
a,b=b,a+b | |
return a | |
a=Fib() | |
print(a[3]) |
以上是传入 int,切片功能实现,isinstance 判断类型
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
def __getitem__(self, item): | |
if isinstance(item,int): | |
a, b = 1, 1 | |
for i in range(item): | |
a, b = b, a + b | |
return a | |
if isinstance(item,slice): | |
start=item.start | |
stop=item.stop | |
if start is None: | |
start=0 | |
a,b=1,1 | |
L=[] | |
for x in range(stop): | |
L.append(a) | |
a,b=b,a+b | |
return L | |
a=Fib() | |
print(a[3:12]) |
# getattr () 动态返回一个属性
调用类属性或方法时,先在__init__() 获取后,再从__getattr__() 获取,获取不到才报错
# call () 直接调用实例本身
与直接调用这个函数一样
class People(object): | |
def __init__(self,name): | |
self.name=name | |
def __call__(self, *args, **kwargs): | |
print('i im call %s'% self.name) | |
p=People('ruan') | |
p() |
# 使用枚举类
枚举类:在某些情况下,一个类的 实例对象 的数量是 有限且固定 的,如季节类,它的实例对象只有春、夏、秋、冬。 在 Java 中像这种对象实例有限且固定的类被称为枚举类;这样的枚举类型定义一个 class 类型,然后,每个常量都是 class 的一个唯一实例。Python 提供了 Enum
类来实现这个功能。
from enum import Enum | |
M=Enum('a',('sun1','sun2','sun3','sun4')) | |
print(M.sun1) |
自定义枚举类
from enum import Enum,unique | |
@unique | |
class Week(Enum): | |
sun1=1 | |
sun2=2 | |
sun3=3 | |
day2=Week.sun2 | |
print(day2) |
from enum import Enum,unique | |
@unique | |
class Gender(Enum): | |
Male=0 | |
Female=1 | |
class Student(object): | |
def __init__(self,name,gender): | |
self.name=name | |
self.gender=gender | |
# 测试: | |
bart = Student('Bart', Gender.Male) | |
if bart.gender == Gender.Male: | |
print('测试通过!') | |
else: | |
print('测试失败!') |
# 使用元类 [创建类]
实例对象是类创建
类是元类创建
创建类的方式
# 方式一:type()
type()
函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过 type()
函数创建出 Hello
类
from class1104 import * | |
h=Hello() | |
print(type(h)) | |
print(type(Hello)) |
Hello = type('Hello', (object,), dict(hello=fn)) |
要创建一个 class 对象, type()
函数依次传入 3 个参数:
- class 的名称;
- 继承的父类集合,注意 Python 支持多重继承,如果只有一个父类,别忘了 tuple 的单元素写法;
- class 的方法名称与函数绑定,这里我们把函数
fn
绑定到方法名hello
上
# 方式二:元类 metaclass
先定义 metaclass,然后创建类。
先定义类,然后创建实例。
metaclass 是 Python 面向对象里最难理解,也是最难使用的魔术代码。
按照默认习惯,metaclass 的类名总是以 Metaclass 结尾,以便清楚地表示这是一个 metaclass
# metaclass 采用 type 创建类 ,metaclass 是类的模板,所以必须从 `type` 类型派生 | |
class ListMetaclass(type): | |
def __new__(cls, name,bases,attrs): | |
attrs['add']=lambda self, value:self.append(value) | |
return type.__new__(cls,name,bases,attrs) | |
class MyList(list,metaclass=ListMetaclass): | |
pass | |
mylist=MyList() | |
mylist.add(1) | |
print(mylist) |
__new__()
方法接收到的参数依次是:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合
# 应用场景
ORM 全称 “Object Relational Mapping”,即对象 - 关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作 SQL 语句。
要编写一个 ORM 框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
# 错误处理 try
try: | |
print('try...') | |
r = 10 / 0 | |
print('result:', r) | |
except ValueError as e: | |
print('ValueError:', e) | |
except ZeroDivisionError as e: | |
print('ZeroDivisionError:', e)e) | |
finally: | |
print('finally...') | |
print('END') |
Python 的错误其实也是 class,所有的错误类型都继承自 BaseException
UnicodeError
是 ValueError
的子类🤡
Built-in Exceptions — Python 3.10.0 documentation
# 调用栈
让 Python 解释器来打印出错误堆栈
# 记录错误 logging
可将 logging 生成一个 txt 方便查看
try: | |
xxx | |
except Exception as e: | |
logging.exception(e) |
# 抛出错误 raise
except ValueError as e: | |
print('ValueError!') | |
raise |
在 bar()
函数中,我们明明已经捕获了错误,但是,打印一个 ValueError!
后,又把错误通过 raise
语句抛出去了,这不有病么?
其实这种错误处理方式不但没病,而且相当常见。捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。
# 调试方法
# 1. print()
# 2. 断言 assert
assert n != 0, 'n is zero!' |
assert
的意思是,表达式 n != 0
应该是 True
,否则,根据程序运行的逻辑,后面的代码肯定会出错。
采用断言的好处:
启动 Python 解释器时可以用 -O
参数来关闭 assert
:
$ python -O err.py |
关闭后,你可以把所有的 assert
语句当成 pass
来看
# 3. logging
import logging | |
s = '0' | |
n = int(s) | |
logging.info('n = %d' % n) | |
print(10 / n) |
# 4.pbd 单步执行
启动 Python 的调试器 pdb,让程序以单步方式运行,可以随时查看运行状态。
python -m pdb xxx.py | |
(Pbd) 1#查看第一行代码,单步执行第一行代码 |
# 5. pdb.set_trace()
这个方法也是用 pdb,但是不需要单步执行,我们只需要 import pdb
,然后,在可能出错的地方放一个 pdb.set_trace()
,就可以设置一个断点:
import pdb | |
s = '0' | |
n = int(s) | |
pdb.set_trace() # 运行到这里会自动暂停 | |
print(10 / n) |
可以用命令 p
查看变量,或者用命令 c
继续运行:
# 6.IDE 工具
vscode,pycharm....
# 单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
# 文档测试
doctest 非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含 doctest 的注释提取出来。用户看文档的时候,同时也看到了 doctest。
Python 内置的 “文档测试”(doctest)模块可以直接提取注释中的代码并执行测试.
class Dict(dict): | |
"""" | |
这一段就是文档测试 | |
Simple dict but also support access as x.y style. | |
>>> d1 = Dict() | |
>>> d1['x'] = 100 | |
>>> d1.x | |
100 | |
>>> d1.y = 200 | |
>>> d1['y'] | |
200 | |
>>> d2 = Dict(a=1, b=2, c='3') | |
>>> d2.c | |
'3' | |
>>> d2['empty'] | |
Traceback (most recent call last): | |
... | |
KeyError: 'empty' | |
>>> d2.empty | |
Traceback (most recent call last): | |
... | |
AttributeError: 'Dict' object has no attribute 'empty' | |
""" | |
def __init__(self, **kw): | |
super(Dict, self).__init__(**kw) | |
def __getattr__(self, key): | |
try: | |
return self[key] | |
except KeyError: | |
raise AttributeError(r"'Dict' object has no attribute '%s'" % key) | |
def __setattr__(self, key, value): | |
self[key] = value | |
if __name__ == '__main__': | |
import doctest | |
doctest.testmod() |
将其中一个函数注释,运行让它报错
# IO 编程
程序和运行时的数据在内存中驻留
涉及到数据交换的地方,通常是磁盘、网络等,就需要 IO 接口
通常,程序完成 IO 操作会有 Input 和 Output 两个数据流
Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。
在 IO 编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把 100M 的数据写入磁盘,CPU 输出 100M 的数据只需要 0.01 秒,可是磁盘要接收这 100M 数据可能需要 10 秒,怎么办呢?有两种办法:
# 同步 IO
第一种是 CPU 等着,也就是程序暂停执行后续代码,等 100M 的数据在 10 秒后写入磁盘,再接着往下执行,这种模式称为同步 IO;
# 异步 IO
另一种方法是 CPU 不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步 IO。
如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步 IO 的复杂度远远高于同步 IO。
# 文件读写
# 读文件 open()
传入文件名,标示符
参数:'rb' 二进制
encoding='gbk' 字符编码
f = open('/Users/michael/test.txt', 'r') |
# read () 一次读取全部内容
f.read() | |
'Hello, world!' |
# f.close()关闭文件
简化方法
# with open('filepath', 'r') as f: print(f.read())
Python 引入了 with
语句来自动帮我们调用 close()
方法,并且不必调用 f.close()
方法
with open('/path/to/file', 'r') as f: | |
print(f.read()) |
如果文件很小, read()
一次性读取最方便;
如果不能确定文件大小,反复调用 read(size)
比较保险;
如果是配置文件,调用 readlines()
最方便
for line in f.readlines(): | |
print(line.strip()) # 把末尾的 '\n' 删掉 |
file 和缓存时 = 是 file-like Object 对象,不要求从特定类继承,只要写个 read()
方法就行
# f.write () 写文件
f = open('/Users/michael/test.txt', 'w') | |
f.write('Hello, world!') | |
f.close() |
或
with open('/Users/michael/test.txt', 'w') as f: | |
f.write('Hello, world!') |
使用 with
语句操作文件 IO 是个好习惯
# StringIO 和 BytesIO
# StringIO
StringIO 顾名思义就是在内存中读写 str
from io import StringIO | |
f = StringIO() | |
f.write('hello') |
getvalue()
方法用于获得写入后的 str
from io import StringIO | |
>>> f = StringIO('Hello!\nHi!\nGoodbye!') | |
>>> while True: | |
... s = f.readline() | |
... if s == '': | |
... break | |
... print(s.strip()) |
# BytesIO
操作二进制数据,就需要使用 BytesIO
>>> from io import BytesIO | |
>>> f = BytesIO() | |
>>> f.write('中文'.encode('utf-8')) | |
6 | |
>>> print(f.getvalue()) | |
b'\xe4\xb8\xad\xe6\x96\x87' |
# os 模块
# os.name 操作系统类型
# os.uname () 详细系统信息
# os.enciron 环境变量
要获取某个环境变量的值,可以调用 os.environ.get('key')
# 查看当前目录的绝对路径: | |
>>> os.path.abspath('.') | |
'/Users/michael' | |
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来: | |
>>> os.path.join('/Users/michael', 'testdir') | |
'/Users/michael/testdir' | |
# 然后创建一个目录: | |
>>> os.mkdir('/Users/michael/testdir') | |
# 删掉一个目录: | |
>>> os.rmdir('/Users/michael/testdir') |
通过 os.path.join()
函数,这样可以正确处理不同操作系统的路径分隔符
# os.path.join(
) 连接路径
# os.path.split()
拆分路径
# os.path.splitext()
文件扩展名
# 对文件重命名: | |
>>> os.rename('test.txt', 'test.py') | |
# 删掉文件: | |
>>> os.remove('test.py') |
shutil
模块提供了 copyfile()
的函数,它们可以看做是 os
模块的补充
最后看看如何利用 Python 的特性来过滤文件。比如我们要列出当前目录下的所有目录,只需要一行代码:
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
要列出所有的 .py
文件,也只需一行代码:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
# 序列化 pickle 模块
变量从内存中变成可存储或传输的过程称之为序列化,Python 中叫 pickling
变量内容从序列化的对象重新读到内存里称之为反序列化,即 unpickling
# pickle.dumps () 对象 -》字节 [序列化]
pickle.dumps()
方法把任意对象序列化成一个 bytes
pickle.dumps()
方法把任意对象序列化成一个 bytes
, 并写入文件中
import pickle | |
d=dict(name='ruan',age=34,freand='woman') | |
# print(pickle.dumps(d)) | |
f = open('timezone.txt', 'wb') | |
pickle.dump(d, f) | |
f.close() |
# pickle.load () 字节 -》对象【反序列化】
import pickle | |
f=open(r'C:\Users\yangs\PycharmProjects\python_study\fun\timezone.txt','rb') | |
d=pickle.load(f) | |
f.close() | |
print(d) |
# json 模块
json
模块的 dumps()
和 loads()
函数是定义得非常好的接口的典范。
# json.dumps (python 对象) python 对象 -》json 对象
dumps()
方法返回一个 str
,内容就是标准的 JSON
# json.loads (json 对象) json 对象 -》python 对象
json.``dump
(obj, fp, , skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, kw)*
# 类变为字典并序列化
json.dumps(s, default=lambda obj: obj.__dict__) |
# 进程和线程
Python 的标准库提供了两个模块: _thread
和 threading
, _thread
是低级模块, threading
是高级模块
线程是最小的执行单元,而进程由至少一个线程组成
操作系统轮流让各个任务交替执行
真正的并行执行多任务只能在多核 CPU 上实现
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程
Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个 “子任务”,我们把进程内的这些 “子任务” 称为线程(Thread)
- 多进程模式;
- 多线程模式;
- 多进程 + 多线程模式。
# 多进程
Unix/Linux 操作系统提供了一个 fork()
系统调用,普通的函数调用,调用一次,返回一次,但是 fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
创建子进程
from multiprocessing import Process | |
import os | |
def run_pro(name): | |
print('开始运行子进程%s,%s'%(name,os.getpid())) | |
if __name__ == '__main__': | |
print('开始运行进程%s' % (os.getpid())) | |
p=Process(target=run_pro,args=('test',)) | |
p.start() | |
p.join() | |
print('end') |
# 启动大量子进程 pool
进程池
import time, threading | |
# 新线程执行的代码: | |
def loop(): | |
print('thread %s is running...' % threading.current_thread().name) | |
n = 0 | |
while n < 5: | |
n = n + 1 | |
print('thread %s >>> %s' % (threading.current_thread().name, n)) | |
time.sleep(1) | |
print('thread %s ended.' % threading.current_thread().name) | |
print('thread %s is running...' % threading.current_thread().name) | |
t = threading.Thread(target=loop, name='LoopThread') | |
t.start() | |
t.join() | |
print('thread %s ended.' % threading.current_thread().name) |
# 模块总结
# doctest 文档测试
# os.path 文件路径
# pickle 序列化
# json
使用 dict 和 set - 廖雪峰的官方网站 (liaoxuefeng.com)
用于学习记录,后期便于复习,参考链接
python file
调用脚本时会先载入 pyhton 解释器,然后运行脚本
rpm:软件管理包
操作符优先级:
条件判断:
Python 为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容,被形象地称作 “内置电池(batteries included)”。用 Python 开发,许多功能不必从零编写,直接使用现成的即可。
语言定位:
Python 的定位是 “优雅”、“明确”、“简单”
Python 是解释型语言
# python
# 数据类型
# int 整型
long int 长整型
int 整型
# float 浮点型
浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时
双精度浮点型 e
浮点型
# String 字符串
字符串是以单引号 '
或双引号 "
括起来的任意文本
字符串是以 Unicode 编码
对于单个字符的编码,Python 提供了 ord()
函数获取字符的整数表示, chr()
函数把编码转换为对应的字符:
# Bool 布尔
True
Flase
# None 空值
None, None
不能理解为 0
,因为 0
是有意义的,
Null 无意义
.
<iframe src="http://127.0.0.1:6806/widgets/brython-editor" data-src="http://127.0.0.1:6806/widgets/brython-editor" data-subtype="widget" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 1341px; height: 276px;"></iframe>
# 变量
变量的概念基本上和初中代数的方程变量是一致的,
变量不仅可以是数字,还可以是任意数据类型。
变量名必须是大小写英文、数字和 _
的组合,且不能用数字开头,字母或下划线开头
#变量类型 | |
a=1 | |
b='我是变量' | |
c=True | |
d=12.2e3 | |
print(a,b,c,d) | |
print(type(a),type(b),type(c),type(d)) |
int a=1 静态语言 此时已经分配的 int 分区之后不能更改变量类型【不支持,Java】
a=3 动态语言,可以赋值成任意类型
# 动态定义
# 静态定义
a = 'ABC' | |
b = a | |
a = 'XYZ' | |
print(b) |
执行 a = 'ABC'
,解释器创建了字符串 'ABC'
和变量 a
,并把 a
指向 'ABC'
:
执行 b = a
,解释器创建了变量 b
,并把 b
指向 a
指向的字符串 'ABC'
:
执行 a = 'XYZ'
,解释器创建了字符串 'XYZ',并把 a
的指向改为 'XYZ'
,但 b
并没有更改:
# 常量
所谓常量就是不能变的变量,通常用全部大写的变量名表示常量:
PI = 3.14159265359 |
# list 列表
list 是一种有序的集合,可以随时添加和删除其中的元素。
list=['a','b','c'] | |
print(list) | |
print(len(list)) |
# 切分
# append()追加
str.append('a')
# insert 插入指定位置
# pop()删除末尾元素
要删除指定位置的元素,用 pop(i)
方法,其中 i
是索引位置
替换元素直接赋值即可
列表可以嵌套
s = ['python', 'java', ['asp', 'php'], 'scheme'] |
类型可以不同
L = ['Apple', 123, True] |
# 切片
list [:-1] 不包含最后一个元素
list [:] 全部列表
list [::] 全部列表
前 10 个数,每两个取一个
# 列表生成
list(range(1,11))生成 10 个数 1-10
print([m+n for m in '123' for n in 'yza']) |
k=[1,1,23,45,56,[1,12,3,467,[2,4,4,3,22]]] | |
print([x for x in k if x==1 ]) |
# tuple 元组
tuple 一旦初始化就不能修改
但元组初始化后就不能进行更改了
b=(1) | |
#定义的不是 tuple,是 1 这个数! | |
# 这是因为括号 () 既可以表示 tuple,又可以表示数学公式中的小括号, | |
# 这就产生了歧义,因此,Python 规定, | |
# 这种情况下,按小括号进行计算,计算结果自然是 1 | |
print(b) | |
print(type(b)) | |
c=(1,) | |
print(c) | |
print(type(c)) |
# dict 字典
其他语言叫 map,使用键 - 值(key-value)存储,具有极快的查找速度。dict 的 key 必须是不可变对象。key 计算位置的算法称为哈希算法(Hash)。
# 定义
d={'name':'ruanyifen','age':60,'happy':'write'} | |
d['add']='Im add' | |
d['name']='fix' |
# 取 value
# dict['key']
# 'key' in dict
# dict.get('key')
print(type(d)) | |
print(d['name']) | |
print('name' in d)#方法一判断是否有这个主键在字典 d 中 | |
print(d.get('name'))#方法二 取 |
# dict.pop ('key') 删除一个 key
# dict.keys 返回字典中所有 key 列表
# dict.update () 将 a 字典新 key,value 内容加入 b 字典中
dicta={"name":'ruan','age':20} | |
dictb={"name":'ruan2','age':40,'add':'w shi add'} | |
dictb.update(dicta) | |
print(dictb) |
# 内建函数使用
type()
cmp()
len()
hash()
内建 cmp()函数比较两个 dict 时,先比较长度,后比值,输出 1 或 - 1
# dict 特点
dict 有以下几个特点:
- 查找和插入的速度极快,不会随着 key 的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而 list 相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
# set 集合
也是一组 key 的集合,但不存储 value。由于 key 不能重复,所以,在 set 中,没有重复的 key。
重复元素在 set 中自动被过滤
# 定义
# set.add ('key') 添加元素
但重复元素不添加,自动去重
# set.remove ('key') 删除元素
set 可以看成数学意义上的无序和无重复元素的集合,
因此,两个 set 可以做数学意义上的交集、并集等操作:
# & 两个 set 交集
# | 两个 set 并集
# map () 的显示
打印 map 对象可以看到 map 对象返回的是一个地址,不是真实的数据
print(list(map对象)) | |
print([it for it in map对象]) |
# 数据类型转换
# int()
# float()
# str()
# bool()
# 条件判断
# if
if else
a=100 | |
if a>=0: | |
print(a) | |
else: | |
print(-a) |
if
if True: | |
print('True') |
if elif elif else
name='zhangsan' | |
if name=='zhangsan': | |
print('我是',name) | |
elif name=='lisi': | |
print('我是', name) | |
elif name=='wangwu': | |
print('我是', name) | |
else: | |
print('我谁的不是') |
# input()输入输出
input () 返回的数据类型是 str
print()
# 循环 迭代
list,tuple,dict 都可循环
Python 的 for
循环本质上就是通过不断调用 next()
函数实现的,计算是惰性的
dict 循环按照 value 时:for value in dict.values
for value in d.values(): | |
print(value) |
# for in
sum=0 | |
for i in range(1,100): | |
sum=sum+i | |
print(sum) |
# while
sum2=0 | |
k=0 | |
while(k<100): | |
sum2=sum2+k | |
k=k+1 | |
print(sum2) |
# break
如果要提前结束循环,可以用 break
语句
# continue
通过 continue
语句,跳过当前的这次循环,直接开始下一次循环
# 生成器
在 Python 中,这种一边循环一边计算的机制,称为生成器:generator。
包括生成器和带 yield
的 generator function。
g = (x * x for x in range(10)) |
访问大文件
yield
# isinstance()迭代器
直接作用于 for
循环的对象统称为可迭代对象,都是迭代器 Iterable
list
、 tuple
、 dict
、 set
、 str
list
、 dict
、 str
虽然是 Iterable
,却不是 Iterator
。
list
、 dict
、 str
等 Iterable
变成 Iterator
可以使用 **iter ()** 函数
以直接作用于 for
循环的数据类型有以下几种:
一类是集合数据类型,如 list
、 tuple
、 dict
、 set
、 str
等;
一类是 generator
,包括生成器和带 yield
的 generator function。
可以使用 **isinstance ()** 判断一个对象是否是 Iterable
对象__iter__:
迭代对象
判断是不是可以迭代,用 Iterable
from collections import Iterable | |
isinstance({}, Iterable) --> True | |
isinstance((), Iterable) --> True | |
isinstance(100, Iterable) --> False |
判断是不是迭代器,用 Iterator
from collections import Iterator | |
isinstance({}, Iterator) --> False | |
isinstance((), Iterator) --> False | |
isinstance( (x for x in range(10)), Iterator) --> True |
Python 中 list,truple,str,dict 这些都可以被迭代,但他们并不是迭代器,为什么::因为和迭代器相比有一个很大的不同,list/truple/map/dict 这些数据的大小是确定的,也就是说有多少事可知的。但迭代器不是,迭代器不知道要执行多少次,所以可以理解为不知道有多少个元素,每调用一次 next (),就会往下走一步,是惰性的。
# 函数
抽象
将函数抽象成一个函数名称,不看内部结构直接调用方法
返回类型 函数名(输入参数):
函数体
# 调用函数
要调用一个函数,需要知道函数的名称和参数
绝对值 abs
# 定义函数
def myabs(x): | |
if x>0: | |
return x | |
if x<0: | |
return -x | |
print(myabs(-100)) |
# 空函数
def nufun(): | |
pass |
pass
可以用来作为占位符
# 函数 参数检查
def my_init_abs(x): | |
if not isinstance(x,(int,float)): | |
raise TypeError('no no no') | |
else: | |
if x>0: | |
print(x) | |
if x<0: | |
print(-x) | |
my_init_abs(-90) |
# 可返回多个值,函数
def return_much(): | |
a='返回' | |
b='我也返回' | |
c='我也要返回' | |
return a,b,c | |
print(return_much()) | |
print(type(return_much())) |
# 函数参数
*args 是可变参数,args 接收的是一个 tuple;
**kw 是关键字参数,kw 接收的是一个 dict。
power(x)
函数,参数 x
就是一个位置参数,可单个变量,list,set,tuple
power(*x)
函数,可传入单个变量,list,set,tuple,可以传入任意个参数或 0 个参数
power(**kw)
函数,字典 dict
可变参数允许你传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个 tuple。
而关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个 dict.
power(x, n)
,用来计算 xn
power(x, n)
函数有两个参数: x
和 n
默认参数,此时 age 和 city 为默认参数,可传值改变也可不变【不用传值】
power(L=None)
函数有 None 这个不变对象,可用 list
def enroll(name, gender, age=6, city='Beijing'): | |
print('name:', name) | |
print('gender:', gender) | |
print('age:', age) | |
print('city:', city) |
# 必选参数
def a1(x): | |
return x | |
print(a1(2)) | |
print(a1(12.1)) | |
print(a1('ruan')) | |
print(a1(True)) | |
print(a1([1,2,3])) | |
print(a1({1,2,3})) | |
print(a1({"key":"vleaue",'name':'ruan','mun':23})) |
# = 默认参数
def a2(x=9): | |
return x | |
print(a2()) | |
print(a2(2)) | |
print(a2(12.1)) | |
print(a2('ruan')) | |
print(a2(True)) | |
print(a2([1,2,3])) | |
print(a2({1,2,3})) | |
print(a2({"key":"vleaue",'name':'ruan','mun':23})) |
# * 可变参数
def a3(*x): | |
return x | |
print(a3()) | |
print(a3(2)) | |
print(a3(12.1)) | |
print(a3('ruan')) | |
print(a3(True)) | |
print(a3([1,2,3])) | |
print(a3({1,2,3})) | |
print(a3({"key":"vleaue",'name':'ruan','mun':23})) |
# ** 关键字参数
def a3(**kw): | |
return kw | |
print(a3()) | |
print(a3(kw=2)) | |
print(a3(kw=12.1)) | |
print(a3(kw='ruan')) | |
print(a3(kw=True)) | |
print(a3(kw=[1,2,3])) | |
print(type(a3(kw=[1,2,3]))) | |
print(a3(kw={1,2,3})) | |
print(type(a3(kw={1,2,3}))) | |
print(a3(kw={"key":"vleaue",'name':'ruan','mun':23})) |
# 递归函数
在函数内部,可以调用其他函数。
一个函数在内部调用自身本身,这个函数就是递归函数。
使用递归函数需要注意防止栈溢出
#递归函数 | |
def funmyself(x): | |
if x>1: | |
return x+funmyself(x-1) | |
elif x==1: | |
return 1 | |
print(funmyself(3)) |
解决栈溢出方法:
尾递归优化,事实上尾递归和循环的效果是一样的
尾递归是指,在函数返回的时候,调用自身本身,并且,return 语句不能包含表达式
#尾递归 | |
def funmyself2(n): | |
return funmyself2_it(n,1) | |
def funmyself2_it(n,pro): | |
if n==1: | |
return pro | |
else: | |
return funmyself2(n-1)+n | |
print(funmyself2(100)) |
此时 funmyself2 是尾递归函数
# 转义字符 \
转义字符 \
可以转义很多字符,比如
\n
表示换行,
\t
表示制表符,
字符 \
本身也要转义,所以 \\
表示的字符就是 \
Python 还允许用 r''
表示 ''
内部的字符串默认不转义
# 运算符 and、or 和 not
运算优先级:not>or>and
# 除法 ///
print(10/3) | |
print(10//3) |
# 除法一 / 浮点数
# 除法二 // 地板除 整数
/
除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
//
,称为地板除,两个整数的除法仍然是整数:
# 取余 %
print(10%1) | |
print(10%3) | |
print(4%7) | |
print(2%20) | |
#如果 a% b a>b 则结果为 a |
# 字符编码
ASCII
编码
8 个比特(bit)作为一个字节(byte)
一个字节能表示的最大的整数就是 255(二进制 11111111 = 十进制 255)
两个字节可以表示的最大整数是 65535
,4 个字节可以表示的最大整数是 4294967295
大写字母 A
的编码是 65
,二进制的 01000001
,小写字母 z
的编码是 122
Unicode 把所有语言都统一到一套编码里,这样就不会再有乱码问题
ASCII 编码是 1 个字节,而 Unicode 编码通常是 2 个字节
ASCll 出现乱码问题引入 Unicode 编码存储空间多了一倍引入 UTF-8 编码
utf-8:将 Unicode 字符根据不同的数字大小编码成 1-6 个字节,常用的英文字母被编码成 1 个字节,汉字通常是 3 个字节,
字符 | ASCII | Unicode | UTF-8 |
---|---|---|---|
A | 01000001 | 00000000 01000001 | 01000001 |
中 | x | 01001110 00101101 | 11100100 10111000 10101101 |
在计算机内存中,统一使用 Unicode 编码,当需要保存到硬盘或者需要传输的时候,就转换为 UTF-8 编码。
用记事本编辑的时候,从文件读取的 UTF-8 字符被转换为 Unicode 字符到内存里,编辑完成后,保存的时候再把 Unicode 转换为 UTF-8 保存到文件
浏览网页的时候,服务器会把动态生成的 Unicode 内容转换为 UTF-8 再传输到浏览器:
# compile () 字符串编译为字节代码
# 编码转化
# ord ('A') 字母转字符
# chr (65) 字符转字母
a=ord('A') | |
b=chr(65) | |
print(a,b) |
# b'str' 转为字节类型 bytes
bytes
类型的数据用带 b
前缀的单引号或双引号表示
x = b'ABC' |
要注意区分 'ABC'
和 b'ABC'
,前者是 str
,后者虽然内容显示得和前者一样,但 bytes
的每个字符都只占用一个字节。
# str.encode('ascii') str
变为 bytes
ASCII
UTF-8
# str.decode('utf-8') bytes
变为 str
print(type(b'abc')) | |
print(b'abc'.decode('utf-8')) | |
print(type(b'abc'.decode('utf-8'))) |
len(str)计算字符数
函数计算的是 str
的字符数,如果换成 bytes
, len()
函数就计算字节数
print(len('abc')) | |
print(len(b'abc')) | |
print(len('中')) | |
print(len('中'.encode('utf-8'))) |
# 特殊注释
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- |
第一行注释是为了告诉 Linux/OS X 系统,这是一个 Python 可执行程序,Windows 系统会忽略这个注释;
第二行注释是为了告诉 Python 解释器,按照 UTF-8 编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
# 占位符 格式化
# 占位符 % s % d % f
格式化方式和 C 语言是一致
%
运算符就是用来格式化字符串的。
在字符串内部,
%s
表示用字符串替换,
%d
表示用整数替换,
有几个 %?
占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个 %?
,括号可以省略
占位符 | 替换内容 |
---|---|
%d | 整数 |
%f | 浮点数 |
%s | 字符串 |
%x | 十六进制整数 |
转义: %%
来表示一个 %
# format()格式化字符串
# f-string 格式化字符串
{r}
被变量 r
的值替换, {s:.2f}
被变量 s
的值替换,并且 :
后面的 .2f
指定了格式化参数(即保留两位小数),因此, {s:.2f}
的替换结果是 19.62
# 函数式编程
函数是 Python 内建支持的一种封装,通过层层函数进行调用
#面向过程的程序设计 #:把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计
函数式和函数的区别:
对比例子:计算和计算器的区别
编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如 C 语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如 Lisp 语言
Python 不是纯函数式编程语言
函数式编程就是一种抽象程度很高的编程范式,
纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的
# 函数式编程特点:
- 纯函数式编程语言函数没有变量,输入输出确定
- 允许本身作为参数传入另一个函数,允许返回一个函数
# 高阶函数
参数中有函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
# 变量可以指向函数
a = 函数
求绝对值的函数 abs()
为例
print(abs(-10)) | |
print(abs) |
abs(-10)是函数调用,abs 是函数本身
k=abs(-20) | |
print(k) | |
#函数本身也可以赋值给变量 | |
h=abs | |
print(h) | |
print(h(-100)) |
结论:函数本身也可以赋值给变量,即:# 变量可以指向函数。#
# 函数名也是变量
#函数名 #:其实就是指向函数的变量
a () 中 a 是指向函数 a()的变量
# 传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
#高阶函数 #:一个函数就可以接收另一个函数作为参数
b()
a(b)
x=a
def f(x): | |
return abs(int(x)) | |
def a(a,b,f): | |
return f(a)+f(b) | |
if __name__ == '__main__': | |
a1=input('a') | |
a2=input('b') | |
print(a(a1,a2,f)) |
此时函数 a 为高阶函数,需要调用 f 函数作为参数
# map/reduce 内建函数
内建了 map()
和 reduce()
函数 高阶函数
# map()函数处理生成新 Iterator 迭代器
两个参数,函数名【函数本身】,需要处理的编程式 iterator
<br />
创建一个迭代器,使用每个迭代器中的参数计算函数。当最短迭代用尽时停止。
map(func, *iterables) --> map object |
def f(x): | |
return x*x | |
r=map(f,[1,2,3,4,4,4,4,4,4,4,4]) | |
print(r) | |
print(type(r)) | |
print(list(r)) | |
print(type(list(r))) |
运算规则抽象
# reduce()函数作用在序列上
两个参数,函数名【函数本身】,需要处理的 #序列 #: sequence (序列) 是一组有顺序的元素的集合
序列基本样式 [下限:上限:步长]
reduce
把结果继续和序列的下一个元素做累积计算
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) |
from functools import reduce | |
>>> def fn(x, y): | |
... return x * 10 + y | |
... | |
>>> reduce(fn, [1, 3, 5, 7, 9]) | |
13579 |
# filter () 过滤序列
参数和 map()相似
filter()
也接收一个函数和一个序列
# sorted()排序
高阶函数
参数:排序对象,key = 函数
sorted([36, 5, -12, 9, -21], key=abs) |
排序的核心是比较两个元素的大小
print(sorted([1,2,353,6,3,234,43,435])) |
key 指定绝对值大小排序
print(sorted([1,2,353,6,3,234,43,435,-242,-34,34,35],key=abs)) |
# 返回函数
# 函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
# 如果不需要立刻求和,而是在后面的代码中, | |
# 根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数: | |
def zary_sum(a): | |
def sum(): | |
sum1=0 | |
for i in a: | |
sum1=sum1+i | |
return sum1 | |
return sum | |
print(type(zary_sum([1,2,3,4]))) | |
f=zary_sum([1,2,3,4]) | |
print(f()) |
调用返回函数时,每次调用都会新生成一个函数
# 闭包
当一个函数的返回值是另外一个函数,
而返回的那个函数如果调用了其父函数内部的其它变量,如果 返回的这个函数在外部被执行,就产生了闭包 。
返回函数中,返回的函数调用父函数的内部变量
#返回函数 | |
def count(): | |
fs=[] | |
for i in range(1,4): | |
def f(): | |
return i*i | |
fs.append(f) | |
return fs | |
f1,f2,f3=count() | |
print(f1(),f2(),f3()) |
返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。
# lambda()匿名函数
lambda 关键字 函数参数:函数表达式
传入函数时,有些时候,不需要显式地定义函数
Python 对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
lambda x:x*x | |
#等价于 | |
def f(x): | |
return x*x |
关键字 lambda
表示匿名函数,冒号前面的 x
表示函数参数,只能一个表达式
不用写 return
,返回值就是该表达式的结果。
匿名函数也是一个函数对象
f=lamdba x:x*x |
判断奇数函数
原函数:
def is_odd(n): | |
return n % 2 == 1 | |
L = list(filter(is_odd, range(1, 20))) |
采用匿名函数修改
l=list(filter(lambda x:x%2==1,range(1,20))) | |
print(l) |
# 装饰器 Decorator
# 本质上,装饰器就是一个返回函数的高阶函数
@log 等价于
now = log(now) |
由于函数也是一个对象,而且函数对象可以被赋值给变量,
所以,通过变量也能调用该函数
def log(func): | |
def wrapper(*args,**kwargs): | |
print('call %s'% func.__name__) | |
return func(*args,**kwargs) | |
return wrapper() | |
#func 为参数所以是高阶函数 | |
#return 函数所以是返回函数, | |
#没有调用父函数中参数,所以不是闭包 |
场景注意:
无 @装饰器时函数不调用,需要参数才调用
当 @时会直接调用装饰器定义函数然后执行函数,不用调用函数
三层时,传入参数
def log1(text): | |
def decorator(func): | |
def wapper(*args,**kw): | |
print('%s %s'%(text,func.__name__)) | |
return func(*args,**kw) | |
return wapper | |
return decorator | |
@log1('ruan') | |
def now3(): | |
print("hhh") | |
now3() |
相当于在返回高阶函数上还有一个函数,所以返回时应该还要调用一次
# @wraps 常用装饰器
当装饰器是个闭包时,装饰器调用变量会改变增加 @wraps 后装饰器内的变量不变
装饰器在装饰一个函数时,,原函数就成了一个新的函数,也
就是说其属性会发生变化,所以为了 不改变原函数的属性,
我们会调用 functools 中的 wraps 装饰器来保证原函数的属性不变
# 不加 wraps 时
@wraps(func) |
from functools import wraps | |
def wrap(func): | |
def b(): | |
'b' | |
print('decorator:',b.__name__) | |
print('funname',func.__name__) | |
func() | |
return b | |
@wrap | |
def a(): | |
'a' | |
print('name',a.__name__) | |
a() |
加装饰器 wraps 时
from functools import wraps | |
def wrap(func): | |
@wraps(func) | |
def b(): | |
'b' | |
print('decorator:',b.__name__) | |
print('funname',func.__name__) | |
func() | |
return b | |
@wrap | |
def a(): | |
'a' | |
print('name',a.__name__) | |
a() |
闭包的概念:调用父函数中的变量的函数,为了保证数据安全。变量作用域只在函数内部,可在闭包中操作数据。
装饰器返回为什么是函数名(函数内存地址)而不直接执行函数?
当有参数传入时,可直接与调用的函数中的值传入参数执行。
()是运算符 f () 与 f.call () 等价:将 f 对象变成变成可调用的对象
# 偏函数(functools 模块)
属于 functools 模块
# 作用:
通过设定参数的默认值,降低函数调用的参数
int()
函数默认按十进制转换
print(int('100',base=8))
经常调用于是重写一个函数 int2
def int2(x, base=8): | |
print(int(x, base)) | |
return int(x, base) | |
print(int2('2334')) |
采用偏函数
import functools | |
int3=functools.partial(int,base=8) | |
print(int3('46')) | |
print(int()) |
functools.partial 的作用是将函数的特定参数固定住(设定为默认值)
创建偏函数的时候也可以接收,函数对象,*args,**kw
# 模块
python 包:作用区分相同名称的模块
模块相当于一个 py 文件
# 作用域
仅仅在模块内部使用。在 Python 中,是通过 _
前缀来实现的。
# pubilc 公开
正常的函数和变量名是公开的(public)
# private 非公开_,__
_xxx 和__xxx 这样的函数或变量就是非公开的(private)
# 安装第三方模块 pip
pip install 模块名
# 模块搜索路径
import sys | |
print(sys.path) |
两种方式:
- 添加搜索路径
import sys
sys.path.append('/Users/michael/my_py_scripts')
- 设置环境变量
第二种方法是设置环境变量 PYTHONPATH
# 面向对象编程
面向对象编程 ——Object Oriented Programming,简称 OOP,是一种程序设计思想
对象作为程序的基本单元,
一个对象包含了数据和操作数据的函数
数据封装、继承和多态是面向对象的三大特点
# 类和实例
面向对象最重要的概念就是类(Class)和实例(Instance)
类是抽象出来的模板
实例是根据类创建出的对象,每个对象可能有属性和方法
定义类是通过 class
关键字,类名通常是大写开头的单词
class Student(object): | |
pass |
!!!在类中定义函数有一点不同,定义佛如方法第一个参数永远是实例变量本身 self
仍然可以用默认参数、可变参数、关键字参数和命名关键字参数
# 数据封装
class Student(object): | |
def __init__(self, name, score): | |
self.name = name | |
self.score = score | |
def get_grade(self): | |
if self.score >= 90: | |
return 'A' | |
elif self.score >= 60: | |
return 'B' | |
else: | |
return 'C' |
# 访问限制
# 作用:
确保了外部代码不能随意修改对象内部的状态
实例的变量名如果以 __
开头,就变成了一个私有变量(private)
外部无法访问_name
class Student(object): | |
def __init__(self,name,age): | |
self._name=name | |
self.age=age | |
def print_name(self): | |
print(self._name) | |
return self.age,self._name | |
a=Student('ruan',23) | |
h=a.print_name() | |
print(h) |
若是要获取,修改变量增加 get,set 方式即可
class Student(object): | |
def __init__(self,name,age): | |
self._name=name | |
self.age=age | |
def get_name(self): | |
return self.name | |
def set_name(self,name): | |
self._name=name |
Python 本身没有任何机制阻止你干坏事,一切全靠自觉。
类外部无法访问
# 继承和多态
# 继承
# 多态
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。
比如:动物是父类,狗和鱼是子类;鱼是鱼类,鱼是动物都成立。
判断一个变量是否是某个类型可以用 isinstance()
判断
# 鸭子类型
并不要求严格的继承体,一个对象只要 “看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
# 获取对象信息
# type()判断对象类型
# isinstance () 对于继承关系,判断 class 的类型
# dir()获取对象的所有属性和方法
# len()对象长度
# lower()返回小写的字符串
# getattr()获取属性 a
# setattr()设置属性 a
# hasattr(obj,'a')判断是否有属性 a
getattr(obj, 'z', 404) # 获取属性 'z',如果不存在,返回默认值 404 | |
404 |
# 实例属性和类属性
class Student(object): | |
name='ruan' | |
h=Student() | |
h.name='hhh' |
类中的 name 是类属性,
创建 h 类对象即实例后赋值的是实例属性 name,但由于实例对象的优先级比类属性高,会屏蔽类中的 name 属性,即 h.name 的值为 hhh
# 总结:
- 实例属性属于各个实例所有,互不干扰;
- 类属性属于类所有,所有实例共享一个属性;
- 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误
# 面向对象高级编程
数据封装、继承和多态只是面向对象程序设计中最基础的 3 个概念
多重继承、定制类、元类
# _slots_使用
可以给创建的实例绑定属性和方法
给一个实例绑定的方法对另外一个实例对象是不起作用的
class A: | |
def run(self): | |
print("i im ferther runing....") | |
sun1=A() | |
#给实例 sun1 设置 name 属性 | |
sun1.name='i im name' | |
#创建实例对象 2 | |
sun2=A() | |
#实例对象 sun1 的属性和 sun2 无关,即 sun2 没有 name 属性 | |
#给实例 sun1 绑定方法,方法和属性同理 | |
#定义方法 | |
def setAll(self,num): | |
print(num) | |
sun1.newfun=MethodType(setAll, sun1) | |
sun1.newfun(37) | |
#若所有实例都需要绑定方法则给类绑定方法 | |
A.setAll=setAll | |
#给类绑定方法后,所有创建的实例的均可调用 |
def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
# 限制实例属性,定义一个特殊的 __slots__
变量
class Student(object): | |
__slots__ = ('name', 'age') # 用 tuple 定义允许绑定的属性名称 | |
s=Student() | |
s.name='ruan' | |
s.firstname='i im firstname' | |
#输出的时候 firstname 的属性会报错, | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
AttributeError: 'Student' object has no attribute 'firstaname' |
# 注意:
_slots_使用时要注意,定义的属性只在当前的类的实例中,对于继承的子类是不起作用的
class People(object): | |
__slots__ = ('name','age') | |
def run(self): | |
print('i im run people......') | |
class Teacher(People): | |
def say(self): | |
print('i im teacher....') | |
t=Teacher() | |
t.tall='shouhua' | |
print(t.tall) | |
p=People() | |
p.tall('shouhuap') | |
print(p.tall) |
只限制父类 People 的属性,而子类 Teacher 中不限制
# @property
在绑定属性时,如果我们直接把属性暴露出去,导致可以随意更改。通过 get,set 来获取更改属性值。
在 python 中直接调用装饰器将一个方法变成属性调用
class Student(object): | |
@property | |
#使用 get 方法是调用装饰器 @peoperty, | |
# 同时自动创建了另一个装饰器 @属性.setter | |
def score(self): | |
return self.score | |
@score.setter | |
def score(self,value): | |
self._score=value |
# 总结:
- 权限限制只对类对象实际起作用,想要达到方法和属性强制访问权限,需要使用 @property 装饰器进行 get,set 方法
属性名与方法名一定要区分开,不然会进入死循环(self._age,def age ())
实例化的对象使用属性时,不是调用属性(meizi._age),而是用的方法名(meizi.age)
@property 其实就是实现了 getter 功能; @xxx.setter 实现的是 setter 功能;还有一个 @xxx.deleter 实现删除功能
定义方法的时候 @property 必须在 @xxx.setter 之前,且二者修饰的方法名相同(age ())
如果只实现了 @property(而没有实现 @xxx.setter),那么该属性为 只读属性
#请利用 @property 给一个 Screen 对象加上 width 和 height 属性, | |
# 以及一个只读属性 resolution: | |
class Screen(object): | |
__slots__ = ('_width','_height','_resolution') | |
@property | |
def width(self): | |
return self._width | |
# 方法名称和实例变量均为 width: | |
@width.setter | |
def width(self,widthValue): | |
self._width=widthValue | |
@property | |
def height(self): | |
return self._height | |
@width.setter | |
def height(self, height): | |
self._height = height | |
@property | |
def resolution(self): | |
return self._width * self._height | |
s=Screen() | |
s.width=23 | |
s.height=12 | |
print(s.resolution) |
s.score = 60 # OK,实际转化为 s.set_score (60) | |
s.score # OK,实际转化为 s.get_score () |
要特别注意:属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
class Student(object):
# 方法名称和实例变量均为birth:
@property
def birth(self):
return self.birth
出现递归调用错误
之前的例子中 width 和_width 不同所以可以运行
# 多重继承
python 可以支持多继承,即一个子类可以继承多个父类;但 java 是单继承,只能有一个父类
Tercher(Name,study,teach)即 Teacher 可以继承多个父类
# MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如, Teacher
继承自 Name。但是,如果需要 “混入” 额外的功能,通过多重继承就可以实现,比如 Teacher 除了继承自 Name
外,再同时继承 Teach
。这种设计通常称之为 MixIn
Python 自带了 TCPServer
和 UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由 ForkingMixIn
和 ThreadingMixIn
提供。
# 多继承
多重继承这个名词一般用来形容继承链条可以很长,多个层次。
# 多重继承
多继承则指一个类可以有多个基类,相反则是单继承。任何面向对象编程语言都支持多重继承,但像 java 这种只能通过接口实现有限程度的多继承
问:多继承 如果多个类有共同得方法名 怎么区分是调得哪个类🤡
答:调用该方法的时候,会调用第一顺位继承父类的方法
# 总结:
- Python 允许使用多重继承,因此,MixIn 就是一种常见的设计
- 只允许单一继承的语言(如 Java)不能使用 MixIn 的设计
# 定制类
Python 的 class 中还有__xxx__有特殊用途的函数,可以帮助我们定制类
# str () 回用户看到的字符串
将对象 <__main__.Student object at 0x109afb190>
变成易读的数据
只在调用 print 时会调用__str__,交互界面时还是现实上方不易读的对象内容,此时用
# repr () 返回程序开发者看到的字符串
__str__()
返回用户看到的字符串,而 __repr__()
返回程序开发者看到的字符串,
也就是说, __repr__()
是为调试服务的
简写
def __str__(self): | |
return 'xxx object (name=%s)' % self.name | |
__repr__ = __str__ |
# _iter () 返回一个迭代对象
需要用到 for in 迭代,需要转化为迭代对象
该方法返回一个迭代对象,然后,Python 的 for 循环就会不断调用该迭代对象的 __next__()
方法拿到循环的下一个值,直到遇到 StopIteration
错误时退出循环
例子:
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
a=Fib() | |
for i in a: | |
print(i) |
# getitem () 表现得像 list 那样按照下标取出元素
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
def __getitem__(self, item): | |
a,b=1,1 | |
for i in range(item): | |
a,b=b,a+b | |
return a | |
a=Fib() | |
print(a[3]) |
以上是传入 int,切片功能实现,isinstance 判断类型
class Fib(object): | |
def __init__(self): | |
self.a,self.b=0,1 | |
def __iter__(self): | |
return self | |
def __next__(self): | |
self.a,self.b=self.b,self.a+self.b | |
if self.a>1000: | |
raise StopIteration | |
return self.a | |
def __getitem__(self, item): | |
if isinstance(item,int): | |
a, b = 1, 1 | |
for i in range(item): | |
a, b = b, a + b | |
return a | |
if isinstance(item,slice): | |
start=item.start | |
stop=item.stop | |
if start is None: | |
start=0 | |
a,b=1,1 | |
L=[] | |
for x in range(stop): | |
L.append(a) | |
a,b=b,a+b | |
return L | |
a=Fib() | |
print(a[3:12]) |
# getattr () 动态返回一个属性
调用类属性或方法时,先在__init__() 获取后,再从__getattr__() 获取,获取不到才报错
# call () 直接调用实例本身
与直接调用这个函数一样
class People(object): | |
def __init__(self,name): | |
self.name=name | |
def __call__(self, *args, **kwargs): | |
print('i im call %s'% self.name) | |
p=People('ruan') | |
p() |
# 使用枚举类
枚举类:在某些情况下,一个类的 实例对象 的数量是 有限且固定 的,如季节类,它的实例对象只有春、夏、秋、冬。 在 Java 中像这种对象实例有限且固定的类被称为枚举类;这样的枚举类型定义一个 class 类型,然后,每个常量都是 class 的一个唯一实例。Python 提供了 Enum
类来实现这个功能。
from enum import Enum | |
M=Enum('a',('sun1','sun2','sun3','sun4')) | |
print(M.sun1) |
自定义枚举类
from enum import Enum,unique | |
@unique | |
class Week(Enum): | |
sun1=1 | |
sun2=2 | |
sun3=3 | |
day2=Week.sun2 | |
print(day2) |
from enum import Enum,unique | |
@unique | |
class Gender(Enum): | |
Male=0 | |
Female=1 | |
class Student(object): | |
def __init__(self,name,gender): | |
self.name=name | |
self.gender=gender | |
# 测试: | |
bart = Student('Bart', Gender.Male) | |
if bart.gender == Gender.Male: | |
print('测试通过!') | |
else: | |
print('测试失败!') |
# 使用元类 [创建类]
实例对象是类创建
类是元类创建
创建类的方式
# 方式一:type()
type()
函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过 type()
函数创建出 Hello
类
from class1104 import * | |
h=Hello() | |
print(type(h)) | |
print(type(Hello)) |
Hello = type('Hello', (object,), dict(hello=fn)) |
要创建一个 class 对象, type()
函数依次传入 3 个参数:
- class 的名称;
- 继承的父类集合,注意 Python 支持多重继承,如果只有一个父类,别忘了 tuple 的单元素写法;
- class 的方法名称与函数绑定,这里我们把函数
fn
绑定到方法名hello
上
# 方式二:元类 metaclass
先定义 metaclass,然后创建类。
先定义类,然后创建实例。
metaclass 是 Python 面向对象里最难理解,也是最难使用的魔术代码。
按照默认习惯,metaclass 的类名总是以 Metaclass 结尾,以便清楚地表示这是一个 metaclass
# metaclass 采用 type 创建类 ,metaclass 是类的模板,所以必须从 `type` 类型派生 | |
class ListMetaclass(type): | |
def __new__(cls, name,bases,attrs): | |
attrs['add']=lambda self, value:self.append(value) | |
return type.__new__(cls,name,bases,attrs) | |
class MyList(list,metaclass=ListMetaclass): | |
pass | |
mylist=MyList() | |
mylist.add(1) | |
print(mylist) |
__new__()
方法接收到的参数依次是:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合
# 应用场景
ORM 全称 “Object Relational Mapping”,即对象 - 关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作 SQL 语句。
要编写一个 ORM 框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
# 错误处理 try
try: | |
print('try...') | |
r = 10 / 0 | |
print('result:', r) | |
except ValueError as e: | |
print('ValueError:', e) | |
except ZeroDivisionError as e: | |
print('ZeroDivisionError:', e)e) | |
finally: | |
print('finally...') | |
print('END') |
Python 的错误其实也是 class,所有的错误类型都继承自 BaseException
UnicodeError
是 ValueError
的子类🤡
Built-in Exceptions — Python 3.10.0 documentation
# 调用栈
让 Python 解释器来打印出错误堆栈
# 记录错误 logging
可将 logging 生成一个 txt 方便查看
try: | |
xxx | |
except Exception as e: | |
logging.exception(e) |
# 抛出错误 raise
except ValueError as e: | |
print('ValueError!') | |
raise |
在 bar()
函数中,我们明明已经捕获了错误,但是,打印一个 ValueError!
后,又把错误通过 raise
语句抛出去了,这不有病么?
其实这种错误处理方式不但没病,而且相当常见。捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。
# 调试方法
# 1. print()
# 2. 断言 assert
assert n != 0, 'n is zero!' |
assert
的意思是,表达式 n != 0
应该是 True
,否则,根据程序运行的逻辑,后面的代码肯定会出错。
采用断言的好处:
启动 Python 解释器时可以用 -O
参数来关闭 assert
:
$ python -O err.py |
关闭后,你可以把所有的 assert
语句当成 pass
来看
# 3. logging
import logging | |
s = '0' | |
n = int(s) | |
logging.info('n = %d' % n) | |
print(10 / n) |
# 4.pbd 单步执行
启动 Python 的调试器 pdb,让程序以单步方式运行,可以随时查看运行状态。
python -m pdb xxx.py | |
(Pbd) 1#查看第一行代码,单步执行第一行代码 |
# 5. pdb.set_trace()
这个方法也是用 pdb,但是不需要单步执行,我们只需要 import pdb
,然后,在可能出错的地方放一个 pdb.set_trace()
,就可以设置一个断点:
import pdb | |
s = '0' | |
n = int(s) | |
pdb.set_trace() # 运行到这里会自动暂停 | |
print(10 / n) |
可以用命令 p
查看变量,或者用命令 c
继续运行:
# 6.IDE 工具
vscode,pycharm....
# 单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
# 文档测试
doctest 非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含 doctest 的注释提取出来。用户看文档的时候,同时也看到了 doctest。
Python 内置的 “文档测试”(doctest)模块可以直接提取注释中的代码并执行测试.
class Dict(dict): | |
"""" | |
这一段就是文档测试 | |
Simple dict but also support access as x.y style. | |
>>> d1 = Dict() | |
>>> d1['x'] = 100 | |
>>> d1.x | |
100 | |
>>> d1.y = 200 | |
>>> d1['y'] | |
200 | |
>>> d2 = Dict(a=1, b=2, c='3') | |
>>> d2.c | |
'3' | |
>>> d2['empty'] | |
Traceback (most recent call last): | |
... | |
KeyError: 'empty' | |
>>> d2.empty | |
Traceback (most recent call last): | |
... | |
AttributeError: 'Dict' object has no attribute 'empty' | |
""" | |
def __init__(self, **kw): | |
super(Dict, self).__init__(**kw) | |
def __getattr__(self, key): | |
try: | |
return self[key] | |
except KeyError: | |
raise AttributeError(r"'Dict' object has no attribute '%s'" % key) | |
def __setattr__(self, key, value): | |
self[key] = value | |
if __name__ == '__main__': | |
import doctest | |
doctest.testmod() |
将其中一个函数注释,运行让它报错
# IO 编程
程序和运行时的数据在内存中驻留
涉及到数据交换的地方,通常是磁盘、网络等,就需要 IO 接口
通常,程序完成 IO 操作会有 Input 和 Output 两个数据流
Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。
在 IO 编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把 100M 的数据写入磁盘,CPU 输出 100M 的数据只需要 0.01 秒,可是磁盘要接收这 100M 数据可能需要 10 秒,怎么办呢?有两种办法:
# 同步 IO
第一种是 CPU 等着,也就是程序暂停执行后续代码,等 100M 的数据在 10 秒后写入磁盘,再接着往下执行,这种模式称为同步 IO;
# 异步 IO
另一种方法是 CPU 不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步 IO。
如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步 IO 的复杂度远远高于同步 IO。
# 文件读写
# 读文件 open()
传入文件名,标示符
参数:'rb' 二进制
encoding='gbk' 字符编码
f = open('/Users/michael/test.txt', 'r') |
# read () 一次读取全部内容
f.read() | |
'Hello, world!' |
# f.close()关闭文件
简化方法
# with open('filepath', 'r') as f: print(f.read())
Python 引入了 with
语句来自动帮我们调用 close()
方法,并且不必调用 f.close()
方法
with open('/path/to/file', 'r') as f: | |
print(f.read()) |
如果文件很小, read()
一次性读取最方便;
如果不能确定文件大小,反复调用 read(size)
比较保险;
如果是配置文件,调用 readlines()
最方便
for line in f.readlines(): | |
print(line.strip()) # 把末尾的 '\n' 删掉 |
file 和缓存时 = 是 file-like Object 对象,不要求从特定类继承,只要写个 read()
方法就行
# f.write () 写文件
f = open('/Users/michael/test.txt', 'w') | |
f.write('Hello, world!') | |
f.close() |
或
with open('/Users/michael/test.txt', 'w') as f: | |
f.write('Hello, world!') |
使用 with
语句操作文件 IO 是个好习惯
# StringIO 和 BytesIO
# StringIO
StringIO 顾名思义就是在内存中读写 str
from io import StringIO | |
f = StringIO() | |
f.write('hello') |
getvalue()
方法用于获得写入后的 str
from io import StringIO | |
>>> f = StringIO('Hello!\nHi!\nGoodbye!') | |
>>> while True: | |
... s = f.readline() | |
... if s == '': | |
... break | |
... print(s.strip()) |
# BytesIO
操作二进制数据,就需要使用 BytesIO
>>> from io import BytesIO | |
>>> f = BytesIO() | |
>>> f.write('中文'.encode('utf-8')) | |
6 | |
>>> print(f.getvalue()) | |
b'\xe4\xb8\xad\xe6\x96\x87' |
# os 模块
# os.name 操作系统类型
# os.uname () 详细系统信息
# os.enciron 环境变量
要获取某个环境变量的值,可以调用 os.environ.get('key')
# 查看当前目录的绝对路径: | |
>>> os.path.abspath('.') | |
'/Users/michael' | |
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来: | |
>>> os.path.join('/Users/michael', 'testdir') | |
'/Users/michael/testdir' | |
# 然后创建一个目录: | |
>>> os.mkdir('/Users/michael/testdir') | |
# 删掉一个目录: | |
>>> os.rmdir('/Users/michael/testdir') |
通过 os.path.join()
函数,这样可以正确处理不同操作系统的路径分隔符
# os.path.join(
) 连接路径
# os.path.split()
拆分路径
# os.path.splitext()
文件扩展名
# 对文件重命名: | |
>>> os.rename('test.txt', 'test.py') | |
# 删掉文件: | |
>>> os.remove('test.py') |
shutil
模块提供了 copyfile()
的函数,它们可以看做是 os
模块的补充
最后看看如何利用 Python 的特性来过滤文件。比如我们要列出当前目录下的所有目录,只需要一行代码:
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
要列出所有的 .py
文件,也只需一行代码:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
# 序列化 pickle 模块
变量从内存中变成可存储或传输的过程称之为序列化,Python 中叫 pickling
变量内容从序列化的对象重新读到内存里称之为反序列化,即 unpickling
# pickle.dumps () 对象 -》字节 [序列化]
pickle.dumps()
方法把任意对象序列化成一个 bytes
pickle.dumps()
方法把任意对象序列化成一个 bytes
, 并写入文件中
import pickle | |
d=dict(name='ruan',age=34,freand='woman') | |
# print(pickle.dumps(d)) | |
f = open('timezone.txt', 'wb') | |
pickle.dump(d, f) | |
f.close() |
# pickle.load () 字节 -》对象【反序列化】
import pickle | |
f=open(r'C:\Users\yangs\PycharmProjects\python_study\fun\timezone.txt','rb') | |
d=pickle.load(f) | |
f.close() | |
print(d) |
# json 模块
json
模块的 dumps()
和 loads()
函数是定义得非常好的接口的典范。
# json.dumps (python 对象) python 对象 -》json 对象
dumps()
方法返回一个 str
,内容就是标准的 JSON
# json.loads (json 对象) json 对象 -》python 对象
json.``dump
(obj, fp, , skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, kw)*
# 类变为字典并序列化
json.dumps(s, default=lambda obj: obj.__dict__) |
# 进程和线程
Python 的标准库提供了两个模块: _thread
和 threading
, _thread
是低级模块, threading
是高级模块
线程是最小的执行单元,而进程由至少一个线程组成
操作系统轮流让各个任务交替执行
真正的并行执行多任务只能在多核 CPU 上实现
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程
Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个 “子任务”,我们把进程内的这些 “子任务” 称为线程(Thread)
- 多进程模式;
- 多线程模式;
- 多进程 + 多线程模式。
# 多进程
Unix/Linux 操作系统提供了一个 fork()
系统调用,普通的函数调用,调用一次,返回一次,但是 fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
创建子进程
from multiprocessing import Process | |
import os | |
def run_pro(name): | |
print('开始运行子进程%s,%s'%(name,os.getpid())) | |
if __name__ == '__main__': | |
print('开始运行进程%s' % (os.getpid())) | |
p=Process(target=run_pro,args=('test',)) | |
p.start() | |
p.join() | |
print('end') |
# 启动大量子进程 pool
进程池
import time, threading | |
# 新线程执行的代码: | |
def loop(): | |
print('thread %s is running...' % threading.current_thread().name) | |
n = 0 | |
while n < 5: | |
n = n + 1 | |
print('thread %s >>> %s' % (threading.current_thread().name, n)) | |
time.sleep(1) | |
print('thread %s ended.' % threading.current_thread().name) | |
print('thread %s is running...' % threading.current_thread().name) | |
t = threading.Thread(target=loop, name='LoopThread') | |
t.start() | |
t.join() | |
print('thread %s ended.' % threading.current_thread().name) |