0%

python基础知识

1. python的运行过程

1.1 字节码编译

当程序运行时,Python内部会将源代码(.py文件中的程序)编译成所谓的字节码的形式,就是将每一行源代码语句从上到下翻译过来,翻译成一组字节码的指令.这些字节码可以提高执行速度;比起源代码语句,字节码要执行快的多。

它会将字节码保存为一个以pyc为扩展名的文件,Python这样保存字节码是一种作为启动速度的优化.下一次运行程序时,如果你还停留在上一次修改记录的话,就会直接跳过编译直接加载.pyc文件.

1.2 PVM虚拟机

py程序编译成字节码后,字节码文件会发送给PVM,也就是python虚拟机进行出来。PVM是python运行的引擎,从根本上讲,它才算是python解释器。python虚拟机就是去模拟可执行程序在x86机器上的运行

2. python的内置对象

python中没有类型声明,运行的表达式的语法决定了创建和使用对象的类型,正如以下表达式就是这些类型起源的地方。Python有五个标准的数据类型:

  • Numbers(数字)
  • String(字符串)
  • List(列表)
  • Tuple(元组)
  • Dictionary(字典)
column column
数字 1234,3.14159,3+4j
字符串 'spaw',"guidos","python"
列表 [1,[2,'there'],4]
字典 {'food':'spam','taste':'yum'}
元组 (1,'spam',4,'u')
文件 my=open('eggs','r')
集合 set('abs'),{'a','b','s'}

可迭代对象:字符串、列表、字典、元组、集合、文件对象 一些操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//数字整型
s=546;
s=s*4;
//字符串
s='spam';
//s[0]='a' #error,python中的字符串是不可变类型
k=len(s); #k=4
s=s+'xyz'; #s='spamxyz'
//序列操作
s='spam';
q=s[0];
m=s[-1]; #字符串长度-1,即s[4-1]
t=s[0:3] #从下标0开始,输出三个字符,即t='spa'

重要性质:

python的对象内存不可变性:在上面所写的例子中没有通过任何操作对原始字符串即spam变动,字符串在python中具有不可变性,在创建后就不可改变,但比总可以通过建立一个新的字符串并以同一个名字对其进行赋值(如s=s+'xyz'),因为python在运行过程中会清理旧对象(动态类型)。一旦改变便意味是产生了新的对象

2.1 可变类型和不可变类型

python中除了动态类型这个观念,还有一个同样重要的概念:可变类型与不可变类型:

  • 不可变类型:一旦对象被创建,其值就不能被改变。例如,整数、浮点数、字符串和元组都是不可变类型。当你试图改变这些类型的值时,实际上是在创建新的对象,而不是修改原始对象(原始对象更加引用计数是否为0决定是否由垃圾回收器回收)。python的不可变类型有:
    • 整数、 浮点数、复数、字符串、元组
      1
      2
      3
      my_tuple = (1, [2, 3], 4)  
      # my_tuple[0] = 0 (这会引发TypeError)
      my_tuple[1].append(5) # 修改元组中列表的内容是允许的
    • 冻结集合(Frozenset):冻结集合是不可变的集合类型,类似于集合但不可修改。
  • 可变类型:对象创建后,其值可以被改变。例如,列表和字典都是可变类型。你可以修改列表中的元素或添加/删除元素,也可以修改字典中的键值对。python中的可变类型:
    • 列表list、字典dict、集合、以及自定义的类也是可变对象

注意可变类型、不可变类型与动态类型是两种不同的限制概念,动态类型只是这意味着你不需要在声明变量时指定其类型。Python解释器会根据你在代码中对变量赋的值来推断其类型,即一个变量是动态类型的(即其类型可以在运行时改变),但它引用的对象可以是可变的或不可变的

3. 数字类型

python的数据类型完整包括:整数和浮点数、复数、固定精度的十进制整数、有理分数、布尔类型、无穷的整数精度、各种内置函数和模块。

其中值得注意的是,与其他语言的整数有位限制不同,python3.0之后支持了无穷的整数精度(只要内存允许)

3.1 一些内置的数学函数
函数 功能
abs() 取绝对值
pow() 计算任意N次方值
min()\max() 取最小值
divmod() 同时取商和余数
sum() 求和
round() 四舍五入
hex() 十进制转为十六进制
3.2 表达式操作符

与C/C++有些不同:

  • //:表示整除

4. 字符串

4.1 raw字符串抑制转义

转义序列用来处理嵌入在自符串中的特殊字节编码很合适,但有些场合我们希望不要发生转义,如文件路径中myfile=open('C:\new\test.txt','w')。本意想打开这个文件,但是因为转义字符\n的存在。如果字母r出现在字符串第一个引号钱,意味着关闭转义:myfile=open(R'C:\new\test.txt','w')'

4.2 常见操作

对于python的字符串,无论是""还是''都一样,支持串与串的相加,支持串与数的相乘,但不允许串与数的相加。同时支持负偏移和分片操作:

1
2
3
4
5
6
7
str="trluper";	#合法
str='trluper'; #合法
c=str[-1]; #str[len(str)-1]=str[6]
str=str+'github';
str*=4;
str+=4; #error

输出:
1
2
3
r
trlupergithub
trlupergithubtrlupergithubtrlupergithubtrlupergithub

分片:Str[i:j]i表示从下标为i的开始,小标j结束(不包括j)(i未输入时默认为0,j未说明默认为到最后一个元素)。即str[1:-1],表示从r开始到e结束(len(str)-1=6),分片会产生新对象。

1
2
3
str='trluper';
s=str[1:-1];
print(s);
输出:
1
rlupe

扩展分片:分片表达式有可选的第三个索引,用作步进str[i:j:k],未声明时k默认为1,表示隔几个元素取

1
2
3
str='trluper';
s=str[1:-1:2];
print(s);
输出:
1
rue

4.3 数据类型转换

上面讲到字符串和数字之间无法相加,会发生错误,为解决这方面问题,python提供了字符串和数字间的转换函数:

函数名称 功能
int(string s) 将字符串转换为数字
str(int i) 将数字转换为字符串
float(string s) 将字符串转换为浮点数
repr(object) 将对象转换为字符串并返回一个字符串对象
ord(char c) 将单个字符转为ascii码
chr(int i) 将数字转换为对于的ascii码
tuple(s) 将序列 s 转换为一个元组
list(s) 将序列 s 转换为一个列表
set(s) 转换为可变集合
dict(d) 创建一个字典。d 必须是一个序列 (key,value)元组。
4.4 修改/查询字符串

对于不可变性质,如果我们要改变原有字符串的一些值,则需要利用合并、分片这样的工具来重新建立并赋值一个新字符串或者replace,若有必要(程序的方便和易读),需要将结果重新赋值给原变量名。查找可以使用find()函数或者判断“Trl" in str

1
2
3
4
5
6
7
8
9
str='Trluper!';
str[0]='t'; #error
//分片合并
str=str[:3]+"user"+str[-1];
//replace
str=str.replace("uper","user");
//查找
str.find("trl"); #找到返回第一个下标,未找到则返回-1
“trl” in str; #return True or false

4.5 字符串格式化

字符串格式化提供了一种组合字符串处理任务的处理思想。在python中有两种形式的字符串格式化表达式

  1. 基于C的printf模型:在%左侧放置字符串,字符串中带有一个或多个嵌入目标(即%d、%s、%c这些),%右侧放置一个元组或字典 。
  2. python的字符串的format函数:通过花括号的位置或者关键字指出替换目标
1
2
3
4
5
6
7
#基于元组的字符串格式化:
str="my name is %s,and I am %d years old" %("trluper",24);
#基于字典
str="my name is %(name)s,and I am %(age)d years old" % {"name":"trluper","age":24};
#format函数
template="my name is {0},and i am {age} years old" ;
str=template.format("trluper",age=24);

输出:

1
2
3
my name is trluper,and I am 24 years old
my name is trluper,and I am 24 years old
my name is trluper,and i am 24 years old

5. 字符串的函数接口

5.1 字符串查询

建议使用find,因为如果没有找到匹配的字符串,index方法会报异常。

方法名称 功能
find(str, beg=0, end=len(string)) 查找子串str第一次出现的位置,如果找到则返回相应的索引,否则返回-1
rfind(str, beg=0,end=len(string)) 类似于find()函数,不过是从右边开始查找
index(str, beg=0, end=len(string)) 类似于find,只不过如果没找到会报异常。
rindex(str, beg=0 end=len(string)) 类似于rfind,如果没有匹配的字符串会报异常
1
2
3
4
5
6
7
str1 = "my name is qlee,what your name?"
str2 = "name"

print(str1.find(str2))#全部查找
print(str1.find(str2,5))#从第5个元素开始查找
print(str1.find(str2,35))# 从第35个元素开始查找,超过元素索引或者没找到,不会报错

5.2 字符串大小写转换操作(upper、lower、swapcase、capitalize和title)
方法名称 功能
upper 将字符串中所有元素都转为大写
lower 将字符串中所有元素都转为小写
swapcase 交换大小写。大写转为小写,小写转为大写
capitalize 第一个大写,其余小写
title 每个单词的第一次字符大写,其余均为小写
1
2
3
4
5
6
txt = "my name is Qlee"
print(txt.upper())
print(txt.lower())
print(txt.swapcase())
print(txt.capitalize())
print(txt.title())
5.3 字符串对齐(center,just和zfill)

1
2
3
4
5
str = "hello world!"
print (str.center(30, '*'))
print (str.ljust(30, '*'))
print (str.rjust(30, '*'))
print (str.zfill(30))

输出:

1
2
3
4
*********hello world!*********
hello world!******************
******************hello world!
000000000000000000hello world!
5.4 分割字符串(split、splitlines和partition)

1
2
3
4
5
6
7
8
9
10
11
str = "my name is qlee, what is your name"
print(str.split()) # 以空格为分隔符
print(str.split('i',1)) # 以 i 为分隔符
print(str.split('b')) # 以b为分隔符,没找到不会报错
print(str.partition("name"))#找到第一个name,分割为三部分
print(str.rpartition("name"))#反向找到第一个name,分割为三部分

str = """my name is qlee
what is your name"""
print(str.splitlines())

输出:

1
2
3
4
5
6
7
['my', 'name', 'is', 'qlee,', 'what', 'is', 'your', 'name']
['my name ', 's qlee, what is your name']
['my name is qlee, what is your name']
('my ', 'name', ' is qlee, what is your name')
('my name is qlee, what is your ', 'name', '')
['my name is qlee', ' what is your name']

5.5 合并与替换(join,replace)
方法名称 功能
join(seq) 以指定字符串作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的字符串
replace(old,new [,max]) 把 将字符串中的 old 替换成 new,如果 max指定,则替换不超过max
1
2
3
4
5
6
7
8
9
10
11
12
print("----------join-----------")
seq1 = ("h", "e", "l", "l", "o") #元组
seq2 = ["h", "e", "l", "l", "o"] #列表
seq3 = "hello" #字符串
print ("".join(seq1)) #无分隔符
print (" ".join(seq1))#空格
print (",".join(seq1))#","

print("----------replace-----------")
str = "my name is qlee"
print (str.replace("qlee", "lq"))

输出:

1
2
3
4
5
6
----------join-----------
hello
h e l l o
h,e,l,l,o
----------replace-----------
my name is lq

5.6 字符串的比较(<,>,max,min等)

字符串的比较操作:

  • 运算符:> , >=, <, <=, ==, !=
  • 比较规则:从第一个以此往下比较。
  • 比较原理:比较的是oridinal value(原始值,即ascii码值)
方法名称 功能
max(str) 返回字符串str中最大的字母
min(str) 返回字符串str中最小的字母
ord 将指定字符转换为原始值
chr 将原始值转换为对应的字符
1
2
3
4
5
str = "mynameisqlee"
print("max(str): ", max(str),"min(str): ", min(str))
print("hello" < "Hello")
print(ord("c"))
print(chr(98))

输出:

1
2
3
4
max(str):  y min(str):  a
False
99
b

5.7 判断字符串(isidentifier、isspace、isalpha、isdecimal、isnumeric和isalnum等)
方法名称 功能
isidentifier 判断字符串是不是合法标识符(字符、数字、下划线)
isspace 判断字符是否只有空白字符(回车、换行和水平制表符)
isalpha 判断字符串是否全部由字母组成
isdecimal 判断字符是否全部由十进制的数字组成,不包括中文、罗马字符
isdigit 判断字符串只包含数字,不包括中文数字
isnumeric 判断字符串是否全部由数字组成,中文数字也算
isalnum 判断字符串是否由字母和数字组成
islower 判断字符串中的字符是否全部为小写,字符串至少有一个字符
isupper 判断字符串中的字符是否全部为大写,字符串至少有一个字符
istitle 判断字符串是否标题话,见titile
isascii 如果字符串为空或字符串中的所有字符都是 ASCII,则返回 True,否则返回False
isprintable 如果所有字符都是可打印的,则isprintable()方法返回 True,否则返回 False
1
2
3
4
5
6
7
8
9
10
11
12
print("hello&".isidentifier())#False,&为非法标识符
print(" t".isspace())#False,"t"为非空
print("aldflafd你好".isalpha())#ture,中文也可以
print("123四".isdecimal())#False,中文不属于十进制
print("123四".isnumeric())#True,中文、罗马字符的数字也算
print("123abc".isalnum())#True,只能字母和数字
print("123四".isdigit())#False,不能包括中文
print("".islower())# False,不能为空字符
print("TLUHBH".isupper())#True
print("My Name Is Qlee".istitle())#True,只有第一个字符为大写
print("我是中国人".isascii())#False,中文不属于ascii
print("Hello!\nAre you ?".isprintable()) #False,\n不可打印
5.8 其他
  • s.rstrip():移除字符串s中的空格
  • s.endswith('span');字符串是否以span结尾
  • s.startswith('span'):字符串是否以span开头

6. 模式匹配

字符串对象的方法能够支持基于模式的文本出处理,文本的模式匹配是一个高级工具,需要导入一个re模块,这个模块包含了搜索、分割和替换等调用。

1
2
3
4
5
import re;
str="Hello se es ra df world";
match=re.match('Hello[\t]*(.*)world',str);
gr=match.group(1);
print(gr);
上面这个例子是在以Hello未开始,后面接这几个或零个制表符或空格的,接着将任意字符保存至组中,后以world结尾的str, 输出:
1
se es ra df

7. 列表(list)

python的列表对象是一个任意类型对象的位置相关的有序集合,没有固定大小,不像C++数组那样,它没有固定类型的约束,同时python的核心数据类型一个优秀特性就是能支持任意类型的嵌套。同字符串不同,列表是可变对象,支持在原处修改,总结性质如下:

  • 列表是保持从左到右的序列,列表内存储的元素没有类型限制,也支持任意嵌套
  • 列表可以像字符串一样索引、分片和合并
  • 与字符串不同的是,列表是可变的,支持在原处修改。同时列表的长度可以改变
  • 在python解释器内部,列表就是C数组,而不是链结构
  • 存储的是对象引用,而不是拷贝,因此从这来看很像C/C++的指针数组
1
2
3
4
5
6
7
8
9
10
11
#包含了整型、字符串、浮点数
L=[123,"spam",1.23];

#一些接口
append(object, /) #将任意object在列表尾部插入
pop(); #移除给定偏移项
insert(); #插入元素
remove(); #按照值移除
sort(); #安照默认的升序排序
reverse(); #翻转
del L[i] #删除指定位置元素

8. 列表解析

处理序列和列表的方法中,python还包括了一个更高级的的操作,称为列表解析表达式.列表解析最常应用迭代协议的环境之一,列表解析把任意一个表达式应用于一个迭代对象中的元素。它可提供一种处理矩阵这样的数据结构。假如现在要从矩阵中提取第二列,使用列表解析可以很简单提取出来:row[1]for row in AOI_world,这种表达式有许多玩法,如row[1]for row in AOI_world if row[1]%2==0

  • 列表解析编写代码比for循环更精简
  • 执行速度更快
1
2
3
4
5
6
7
AOI_world=[[1,2,3],[4,5,6],[7,8,9]];
col2=[row[1]for row in AOI_world];
print(col2);

#更爽的用法,rstrip函数去除右边的空白,使用if滤除开头不为p的字符串
lines=[line.rstrip() for line in open('test.txt','r') if line[0]=='p']

输出:

1
[2, 5, 8]
当然其他相关函数内容如mapfilter还未介绍,但列表解析这里可以涉及一些。列表解析说白了,就是把任意一个表达式应用于一个可迭代对象中的元素,并将结束收集到一个新列表中。列表解析能够成为比filter,map更有用的函数工具:
1
2
3
4
5
6
7
8
#使用map
res=list(map(ord,'trluper'));
#用列表解析
res=[ord(x) for x in 'trluper'];
#使用if的列表解析能有与filter一样的过滤功能
res=[x**2 for x in [1,2,3,4] if x%2==0]
#嵌套for循环
res=[x+y for x in [1,2,3,4] for y in [5,6,7,8]];

注意:列表解析会产生一个新列表对象。

8.1 列表常用函数
  • append(x):列表尾部插入新元素x
  • extend():在列尾插入多个元素,参数为列表[]
  • insert(i,x):在位置i插入新元素x(原位置i的元素会在后面)
  • sort():排序
  • reverse():翻转
  • pop():在末尾弹出一个元素
  • index():查找某个元素的索引位置
  • remove():移除某个元素

上面是成员函数,非成员函数比如len()也用的多。

9. 字典(Dictionary)

字典是一种映射,是一个键值对集合,通过键值获取相应的值,字典具有可变性,可以随需求增大或减小,像列表那样。字典页可以嵌套,性质总结:

  • 字典其实就是散列表也叫哈希表,通过键对生成哈希码而找到值,通过键值对映射类型
  • 与列表不同,字典是无序,保存在字典中的项没有特定的顺序,因此不支持序列操作,如下标访问,是通过键值访问,键值是不可重复的
  • 与列表一样,可变长,存储的元素可为任意类型,支持任意嵌套,可以在原处修改;存储的是对象引用,而不是拷贝
    1
    2
    keyValue_test={'food':'fish','quantity':4,'price':50};
    fod_name=keyValue_test['food'];

字典常用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
"fish" in keyValue;		#键存在判断
D.keys(); #返回所有键,可迭代对象
D.values(); #返回所有值,可迭代对象
D.items(); #键+值,可迭代对象
D.copy(); #拷贝
D.get(key,\); #返回key对应的值,相当于D[key]
D.update(D1); #合并字典
D.pop(key); #删除键值对
D['eggs'] #通过键查找值

len(D); #长度
del D[key];
list(D.keys()) #生成键序列

名称 作用
clear() 删除字典内所有元素
copy() 返回一个字典的浅复制
fromkeys(seq[, val]) 创建一个新字典,以序列 seq 中元素做字典的键,val 为字典所有键对应的初始值
get(key, default=None) 返回指定键的值,如果值不在字典中返回default值
has_key(key) 如果键在字典dict里返回true,否则返回false(python3.0舍弃,都使用in)
items() 以列表返回可遍历的(键, 值) 元组数组
keys() 以列表返回一个字典所有的键
setdefault(key, default=None) 和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default
update(dict2) 把字典dict2的键/值对更新到dict里
values() 以列表返回字典中的所有值
pop(key[,default]) 删除字典给定键 key 所对应的值,返回值为被删除的值。key值必须给出。 否则,返回default值。
popitem() 返回并删除字典中的最后一对键和值。
9.1 键的排序
  • 因为字典不是序列,不包含任何可靠的从左到右的顺序排序方法,当我们需要强调某种顺序时,一个常用方法就是通过字典的keys方法收集一个键的列表,使用列表的sort方法进行排序:

    1
    2
    3
    4
    key_list=list(d.keys());
    print(key_list);
    key_list.sort();
    print(key_list);
    输出:
    1
    2
    ['food', 'price', 'quantity', 'buyer']
    ['buyer', 'food', 'price', 'quantity']

  • 第二种方法是通过使用最新的sorted内置函数可以一步完成,sorted调用后会返回排序后的新列表对象

    1
    2
    3
    s=sorted(d);
    for key in s:
    print(key,"==>",d[key]);
    输出:
    1
    2
    3
    4
    buyer ==> TOM
    food ==> fish
    price ==> 50
    quantity ==> 4

10. set集合

set集合是无序的,它既不是映射也不是序列,可以说它是存储无值的键,而且键值唯一不可变

创建集合对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#python2.6版
x=set('abcde');
y=set('bdxyz');

z=x&y
print(z);

i=x.intersection(y);
print(i);
x.add('trluper');
print(x);
x.update(set(['q','y']));
print(x);
x.remove('b');
print(x);

#python3.0
set([1,2,3,4]);

输出:
1
2
3
4
5
{'b', 'd'}
{'b', 'd'}
{'b', 'c', 'trluper', 'd', 'a', 'e'}
{'b', 'q', 'c', 'trluper', 'y', 'd', 'a', 'e'}
{'q', 'c', 'trluper', 'y', 'd', 'a', 'e'}

11. 元组(tuple)

不同与列表和字典,元组对象是一个不可改变的python数据结构,他有列表的有序性(即支持随机索引下标访问),但没有列表的可变性,和字符串类似。它们编写在圆括号()而不是[]{}内,()时可选的,不是必须,。它们支持任意类型和任意嵌套。元组提供了不可变这一项约束是使用它的原因,否则使用列表。性质总结:

  • 元组是位置有序的对象集合,因此支持下标访问,支持分片。
  • 与字符串一样,元组是不可比变的,不能在原处进行修改。因此元组也就是长度不可变的,在不生产一个新对象下不能增长或缩短
  • 元组内的元素可为任意类型且为引用,而不是拷贝,支持任意嵌套
  • 元组不支持任何方法调用,但是支持字符串和列表的一般序列操作:合并、倍乘、分块

一般操作:

1
2
3
4
5
6
7
8
9
10
T=(elements);   #创建元组
T=i,j,k,l #省略()创建元组
T[j]; #访问
T[i:j]; #分块
len(T); #长度
T1+T2; #合并
T*3; #内容倍乘
for x in T: #迭代
“trluper" in T; #元组内是否有该元素

在python3.0以上,有元组的专有调用方法

1
2
index(element);		#查询元素下标
count(element); #统计元素出现过几次

元组除了不可变性,与列表极其相似,那为什么有了列表还要元组?

因为元组的不可变性提供了某种完整性,这样就能确保元组在程序中不会被另外一个引用修改,而列表就没有这种限制,对列表的修改总是会影响所有列表引用。因此,列表更类似于其他语言的“常量”

12. 文件

文件对象提供了python编程对外部文件操作的接口,要创建一个文件对象,就必须调用内置的open函数打开问文件:

1
file_object=open("*.txt",'r')
除了open这个函数,python还有额外的类文件工具:管道、FIFO、套接字、通过键访问文件、对象吃久、基于描述符的文件、关系数据库等.

文件常用函数接口:

1
2
3
file_name=open("test.txt",'r');
lines=file_name.readlines(); #读取整个文件到列表

注意:在写入时,我们必须将对象转换成字符串,同样在读出时,我们也必须用转换工具将文本文件中的字符串转换乘python对象

13. python的动态类型

如果学习了C++/java这种静态编译的语言,你会感到困惑,为什么python不需要显示声明变量类型就能知道该变量实际的类型?其实,这就涉及到python动态类型知识了,这也是为什么pyhon称为动态编译语言

13.1 动态类型是什么

python的动态类型是为什么在python不必声明变量的存在和类型的缘由。python使用动态类型和他提供的多态性来提供python语言的简洁灵活的基础。在python中我们是不会声明所使用对象的确切类型的。所谓的python动态类型,就是将变量与类型分离开来,在程序运行的过程中自动决定对象的类型。

  • 与C/C++明显不同的是,在python中,变量只是一个名字,它永远不会有任何类型约束,变量是通用的,在python中类型的概念只存在于对象中。
  • 变量在赋值的时候才创建,即没有声明,当变量出现在表达式中,它会被当前引用的对象所替代,无论该对象是什么类型
1
2
3
4
5
6
7
a = 3
a = 'span'

就如a=3这个简单的赋值语句,python分步:
1.创建一个对象代表3
  2.如果程序中没有变量a,则创建他。
  3.将变量与对象3连接起来。
  • 变量和对象保存在内存中的不同部分,变量只是对对象的引用,这样来说变量更像是C/C++中的void*指针(记住一句话:类型属于对象,而不是变量
      1. 变量是系统表的元素,他指向对象存放的地址空间
      1. 对象是分配的一块内存,地址可被连接,有足够大空间代表对象的值,每个对象都有两个标准头部信息:一是类型标识符(标识对象类型),二是引用计数器(对象垃圾回收)。
      • 2.1 类型标识符:对象知道自己的类型,一旦与变量连接,程序执行中也就知道变量是引用了哪种类型的对象。
      • 2.2 引用计数:每当对象被引用到一个变量是,其引用计数会+1,反之则-1,当引用计数为0是,该对象的内存空间被自动回收,这种性质很想C++的智能指针shared_ptr,意味着我们在使用过程中不需要考虑对象内存释放的问题.
      • 2.3 引用的过程自动完成变量指向对象地址的过程,即从变量到对象的指针

在上述例子中,变量a赋值成为不同类型对象的引用,但是重新给变量a赋新值时,前一个引用的对象会发生什么变化?

答案是:如果之前对象的引用计数因为此次重新赋值变为0的话,之前对象占用的内存空间会被python的垃圾回收器回收

垃圾回收的好处:不同与c++,python没有支持手动的申请和释放内存的操作,而是把这些东西交给垃圾回收器,这样使用对象就不要考虑申请内存,也不要担心内存泄漏问题,省去了大量的基础代码。

13.2. 共享引用
  • 之前我们就提到过,在python中对象是不可变性的,一旦发生改变便意味这新对象的产生。共享引用也是如此:

    1
    2
    3
    a=3; #a指向对象,对象值为3,对象引用为1
    b=a; #b也指向该对象,对象引用为2
    b=b+2; #产生新对象5,引用计数为1,a所引用的对象仍为3,引用计数为1

  • 但有一些对象和操作也确实会在原处修改对象,如列表、字典以及一些通过class语句定义的对象,对列表的一个位置进行赋值会改变这个列表对象,而不是生成一个新列表(想想也是,如果每次都有生成一个新列表,那么对于数量极大的列表来说,其消耗的时间是极大的),如果不想这样,使用copy函数进行拷贝成一个新对象,指向内容相同但内存不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #同上面一样创建了新对象42,没有影响原对象
    L1=[1,2,3];
    L1=L2;
    L2=42;
    #原处修改,L1和L2均变为[42,2,3]
    L1=[1,2,3];
    L1=L2;
    L1[0]=42;
    #copy函数防止原处修改,
    import copy;
    L2=copy.copy(L1);

  • 共享引用和相等:==操作符,测试两个引用对象值是否相同;is操作符,检查对象的同一性,是否内存地址一样

13.3.引用和拷贝

到这里,应该知道了引用和拷贝的区别:

  • 引用是指变量名指向对象,多个引用都是指向同意内存地址
  • 而拷贝是对原对象的一份拷贝,会在PVM中开辟一份自己的内存地址存储

在python中赋值操作总是存储对象的引用,而不是拷贝。如果有些时候我们不希望因多个对象的引用中的改变而造成影响(如上面所举列表例子),那么就应该用拷贝:

  • 没有限制条件的分片语句会产生一份拷贝,比如(L[:]能够赋值一份序列)
  • 字典copy方法(X.copy())能够产生一份拷贝
  • 一些内置函数,如list(L)产生一份列表拷贝
  • copy标准库模板能够完整拷贝
1
2
3
4
5
6
7
8
import copy
L = [1,2,[2,3]]
D = {'A':1,2:'D'}

#产生一份拷贝
A = L[:]
B = D.copy()
C = COPY.deepcopy(L)

14. python语句

14.1 与C/C++完全不同点
  • python中所有的嵌套语句的首行以:结尾
  • python中不像C/C++,可省略嵌套语句的(),如if x>y:,如果单条if,则必须加()
  • 每个语句最后的;不是必须的,可省略,但是当一行有多条语句用;
  • 缩进的结束就是代码块的结束,python没有用{}来标识代码块,而是看缩进
14.2 赋值和打印
14.2.1 赋值
1
2
3
4
5
str="trluper"
str,s="trl","uper"
a,b,c,d='trlu" #a='t',b='r',c='l',d='u'
a,*b="trluper" #a='t' b=['r','l','u','p','e','r']

在上面的表达式可以看到,*b并不是C/C++中的指针,而是告诉赋值表达式变量b匹配剩下的内容成列表

14.2.2 打印

python中print的语法:print([object,...][,sep=' '][,end='\n'][,file=sys.stdout]);在表示中,方括号的的项是可选的,且这些参数跟在要打印的对象后面,以name=value形式出现。

  • object指要打印的对象
  • sep表示没打印一个对象,都有输出一个空格。(没有显式传递,则默认` `)
  • end表示在打印末尾输出换行符(没有传递就默认\n
  • file表示要将这些对象输出给文件、标准流还是其他文件对象。(没有显示说明,则默认sys.stdout)
1
2
3
log=open(r'D:\Python_WorkSpace\test.txt','a');
print("唱","跳","rap","篮球",sep='*',end='\n',file=log);

14.3 python的布尔值

对于python的bool类型来说,其truefalse的判断:

情况 True/False 情况 True/False
none F map{} F
数值0 F False F
空序列 F 对象为空 F
14.4 判断循环语句
14.4.1 if/elif/else

python中没有C/C++中的switch case语句块,要做判断选择只有用if语句,在python中没有{}作为语句块边界标识,只能靠左侧的缩进来判断语句属于哪一部分:

1
2
3
4
5
6
7
8
9
if (a==b and c==d and d==e):
print("相等")
elif c==d and d==e:
print("cde相等")
else:
print("不相等“)

#三目运算
A=Y if X else Z; #X为真时A=Y,false时为A=Z
14.4.2 while语句

while语句书写格式时:首行以及测试表达式,内部有一行或多行缩进语句主题执行:

1
2
3
4
5
6
a=0;b=10;
while a<b:
print(a)
a+=1
else:
print("a不小于b")
14.4.3 for语句

for循环不同与c/c++,在python中,它通常是用作序列迭代器,可以遍历任何有序的序列对象内的元素,可用于字符串、列表、元组、其他内置可迭代对象;

1
2
3
4
5
for <target> in <object>:
<Options>
else:
<Options>

当运行for循环时,PVM会将序列对象中的元素赋值給target,然后在循环主题中对其进行操作。for循环的循环变量target可以是任何赋值目标(反正执行完一次后就会重新赋值,引用)。因此for对序列的任意嵌套都能解包。

14.5 其他循环
14.5.1 range函数跳遍历

range函数常用在for循环中用来产生索引,但也可以用在任何需要整数列表的地方。range是一个跌代器,会根据需要产生元素:

1
2
3
4
5
6
range(5);	#生成0,1,2,3,4
range(2,5); #生成2,3,4
range(0,10,2); #生成0,2,4,6,8
#应用,遍历X跳一个输出,其实就是相当与while循环,条件+2
for i in range(0,len(X),2):
print(X[i],end=' ');
根据上面的解释可知:

  • 传一个参数时,会产生从0算起的整数,不包括参数值
  • 传两个参数时,第一个为起始值,第二个为最大不包括值
  • 传三个参数时,第三个为步进值
14.5.2 并行遍历zip和map
  • zip函数:传而对于zip(),原型是zip(*list)list是一个列表,zip(*list)返回的是一个元组串,如果要转为元组列表,必须使用list()函数(zip不仅仅支持List,它允许任何可迭代对象)

    1
    2
    3
    4
    5
    6
    7
    8
    L1=[1,2,3,4];
    L2=[5,6,7,8];
    L3=zip(L1,L2); #L3=(1,5),(2,6),(3,7),(4,8)
    L4=list(zip(L1,L2)) #L4=[(1,5),(2,6),(3,7),(4,8)]
    #并行遍历
    for (x,y) in L3:
    print(x,y,"x+y=",x+y);
    a2,b2=zip(*zip(L1,L2)) #a2=(1,2,3,4) b2=(5,6,7,8)
    事实上,zip()可以接受任何类型的序列(就是任何可迭代对象)。此外,若传入zip的容器大小长度不一,则只会以长度最短的容器作为标准生成元组

  • map函数: 对于map()它的原型是map(function,sequence),就是对序列sequence中每个元素都执行函数function操作.比如之前的a,b,c = map(int,raw_input().split()),意思就是说把输入的a,b,c转化为整数

    1
    map(ord,'spam');	#ord为ascii转换为数字,结果为115,112,97,109

14.5.3 filter和reduce

filterreduce都会返回可迭代对象。在python3.0,需要用list调用来显示其所有结果。filter的作用是基于某一函数的过滤器。reduce是没对元素都应用函数得到最后结果

1
2
list(filter((lambda x:x>0),range(-5,5)));	#[1,2,3,4]
reduce((lambda x,y:x+y),[1,2,3,4]); #10

15. 迭代器和解析

在上面提到的for循环和while循环能够处理重复性任务,但是序列迭代在程序中非常常见,因此python提供了额外工具来使得这项工作变得简单高效。

15.1. 文件迭代器
15.1.1 __next()__

在文件类中,有一个方法__next__(),该方法每次调用时,就会返回文件的下一行,到达文件末尾时,会抛出StopIteration异常,而不是空字符串。像这样的接口就是python中所说的迭代协议.事实上,任何这类对象都认为时可迭代的,因为它们也能以for循环或其他迭代工具遍历,因为所有迭代工具内部工作原理都是调用__next__(),并且捕捉StopIteration异常来确定何时离开

迭代协议:Python 迭代协议由__iter__方法与__next__方法构成,若对象具有__iter__方法,称该对象为“可迭代对象(iterable object)”。若对象具有__next__方法,称该对象为“迭代器(iterator)”。__iter__方法必须返回一个迭代器对象,__next__方法不断的返回下一元素,或者抛出StopIteration。__next__方法是 Python 迭代协议的核心.__iter__方法是迭代协议的辅助——将可迭代对象转换成迭代器

1
2
3
4
5
6
7
file=open("test.txt",'r');
while True:
try:
s=file.__next__();
except StopIteration:
break;
print(s,end=' ');
15.1.2 用for循环(推荐)

读取文本文件的最佳方式就是根本不要去读,其代替的方法就是让for循环每轮自动调用__next__()从而前进到下一行:

1
2
for line in open('test.txt'):
print(line,end=' ');
不建议使用readlines函数,因为readlines时一次性把文件加载到内存,且运行速度不如for循环.

15.1.3 手动迭代

为了支持手动迭代,python提供了一内置函数next(),他会自动调用一个对象的__next__()函数。即调用next(X)等价于X.__next__()

1
2
3
4
5
6
while True:
try:
s=next(file);
except StopIteration:
break;
print(s,end='');
另一方面,再深一点看,当for循环开始时,通过iter内置函数获得一个迭代器,返回的迭代器对象含有需要的next或__next__()方法
1
2
3
4
5
6
7
8
9
it=iter(file);	//获取迭代器
print(file is it);
while True:
try:
s= it.__next__();
except StopIteration:
break;
print(s,end='');

15.2. 字典迭代
15.2.1 获取键值列表,再由遍历列表遍历字典
1
2
for key in D.keys():
print(key,D[key]);
15.2.2 有字典的迭代器遍历

字典有一个迭代器,在迭代的环境下(即for循环/while循环)会自动一次返回一个键,这样我们就不必生成键值序列来遍历:

1
2
3
4
5
6
7
8
9
it=iter(D);	#获得字典的迭代器
while True:
try:
#s=it.__next__();
l=next(it);
except StopIteration:
break;
print(l,D[l]);

因此可简写for循环:(推荐)

1
2
3
for key in D:
print(key,D[key]);

15.3 其他迭代环境

上面介绍的迭代环境是在for循环看的以及之前讲到过的列表解析也有迭代协议。其实,in成员测试map内置函数以及像sorted(调用后会返回排序后的列表对象)和zip调用这样的内置函数也使用了迭代协议当应用于文件时,文件对象的迭代器都自动扫描:

1
2
3
4

list(map(str.upper,open("test.txt",'r'))); #["ADC","DADA","SSDA"]
sorted(open("text.txt",'r')); #['A','B','C']
.....

16. 函数

作为一位C/C++、java程序员,需要了解python函数体系下的函数相关语句和表达式,因为它们有很大的不同点:

  • def是可执行代码,python的函数由def语句编写,函数只有当def运行后才存在,这就意味着不能在函数未运行时就去调用
  • def创建了一个对象并将其赋值给某一个变量,即函数名只是函数对象的引用。
  • lambda创建一个对象但将其作为结果返回。使用lambada表达式创建函数,运行将函数定义内联到语法上一条def语句不能工作的地方。
  • yield向调用者发回结果对象,但会记住它离开的地方
  • global声明了一个模块的变量并被赋值。默认情况下所有在一个函数总被赋值的对象,仅在函数作用域中有效。如要使得它在整个模块中都可使用,函数需要global语句声明
  • nonlocal声明了将要赋值的一个封闭函数变量
16.1 def

def语句将创建一个函数对象并将其赋值给一个变量名,格式如下:

1
2
3
def <name>(arg1,arg2,arg3,...):
functions-body;

python中所有语句都是实时运行的,没有像独立编译时间这样的流程。python函数在程序运行之前并不需要全部定义,因为def在运行时评估,而def里面的函数体调用时才评估(与C/C++不同) 。一个def语句可以出现在任何地方,比如if,while甚至是def嵌套也是合法的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if test:
def _func():
....
else:
def _func1():
....

#嵌套
def f1():
x=99;
def f2():
def f3():
print(x);
f3(); #调用
f2(); #调用

f1();
注意:当我def嵌套时,必须在上一层函数调用嵌套函数

16.2 python中的多态

对于python中函数的作用,会根据传入参数的不同而有些许不同:

1
2
3
4
5
6
def times(x,y):
return x*y;
#调用
times(2,4); #return 8
times("tr",4); #return trtrtrtr

可以看到times实现的功能的意义完全依赖于参数xy的类型,这种依赖类型的行为称为多态。可以说python的动态类型是实现python多态的前提条件。在python这种多头机制下,编写函数接口更多是为对象编写,而不是特定为一个类型,这样可以达到函数的复用,提升代码简易和高效性。

16.3 作用域

在代码编写过程中,变量可以在三个不同地方辅助,其对应的作用域:

  • 变量在一个def赋值,其作用域只在该函数有效
  • 变量在def之外赋值,从赋值地方开始到文件结尾都有效
  • 变量在嵌套def内,只在内层def内有效
16.3.1 作用域法则
  1. 全局作用域的作用范围仅限单个文件。要是有其他模块的变量名,必须导入模块
  2. 导入模块的作用域是全局的
  3. 每次函数调用都创建一个新的本地作用域
  4. 在函数赋值的变量名除非声明为global或者nonlocal变量,否则默认为本地变量。global将位于函数内的变量变为全局(位于模块文件内部的顶部),nonlocal将嵌套函数内的变量声明为非本地
  5. python执行过程变量搜索路径:本地作用域-->上一层结构def或lambda的本地作用域-->全局作用域-->内置作用域
16.3.2 global

global不是一个类型或者大小的声明,而是命名空间的声明,它告诉编译器python函数打算生成一个或多个全局变量名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#用法1
def _func(x,y):
global z;
z=x+y;
func(2,4);
print(z); #输出6

#用法2,告诉函数x不是本地的,而是全局的(主要用法:将在函数内修改的全局变量保存)
x=22
def f1():
global x;
x=99
f1();
print(x); #输出99

16.3.3 nonlocal

global一样,nonlocal也是将变量在嵌套作用中的修改保存下来。与global不同之处在于:global是对def之外的全局变量作用,而nonlocal更严格,是对嵌套的的变量,这样达到能够使内层嵌套对变量的修改能够保存。

1
2
3
4
5
6
7
8
9
10
def _func1(a,str):
x=a;
def _f(str):
nonlocal x
x+=20
print(str,x); #这是一个nonlocal测试 21
_f(str);
print(x); #21

_func1(1,"这是一个nonlocal测试")

17. 参数

17.1 引用传递

要努力区别python的传参机制和C++引用参数,对于python来说:

  • 不可变参数通过“值”传递,虽然像整数、字符串这些是通过对象的引用进行传递的,但是它们的不可变性导致我们不可能在原处改变,一旦执行给变量赋新值,意味这新对象产生,变量只是新对象的一份引用。(这样来看它的效果其实就是C++中的按值传递,即发生了一份拷贝)
  • 可变对象时通过“指针”进行传递的。虽然列表、字典这样的对象实际上也是通过引用来传递,但效果却和不可变参数完全不同,因为它们支持原处修改,因此函数内发生的改变能够保存。(这就像C++中传递数组很像)
1
2
3
4
5
6
7
8
9
10
11
12
13
a=2;
b=[1,2,3,4,5];

def test(x):
if isinstance(x,list):
x[0]="20";
elif isinstance(x,numbers.Number):
x=20;

test(a);
print(a); #2
test(b);
print(b) #[20,2,3,4,5]

python通过引用来传参,意味着我们不需要进行多余的拷贝操作,能够节约内存和运行时间,同时也就支持可传递很大对象。如果我们要抑制因传入可变参数而带来的修改,可以在传入时拷贝一份作为参数test(copy(b));

17.2 返回值

return语句能够返回任意种类对象,所有也能够返回多个值(指的是封装进元组或列表这样的集合里):

1
2
3
4
5
6
def func():
x=2;
y=[3,4];
return x,y;

l=func(); #(2,[3,4])
上面的函数返回了元组(2,[3,4])。(因为元组的()不是必须的)

17.3 参数匹配模型

这些匹配模型的底层传参机制仍有是引用赋值:

  • 位置参数:默认模式,通过从左到右参数匹配(位置:调用者)
  • 关键字参数;设置传参默认值,name=value形式,意思是当我们执行从左到右匹配,发现某参数没有传入,则调用时使用默认值(位置:被调函数)
  • 关键字参数:name=value还可以指定我们调用时参数传递给哪一个参数变量,破环了默认模式,即不必遵循从左到右匹配,而是按名字匹配,如print("trluper",sep=' ',end=' ',file=sys,stdout);(位置:调用者)
  • 可变参数列表,在函数的参数中有*号,意味着该函数支持可变参数列表,即支持传递任意个参数。单个*指对元组,**是对字典,即指在调用的时候我们传递给函数的参数会被打包参数位置信息的元组序列或者字典对象,对函数来说它会解包

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#位置参数
def f(a,b,c):
print(a,b,c);
#关键字参数name=value形式调用
f(a=2,c=3,b=6);

#关键字参数,设置默认值
def f(name='Bob',age='',jov='engineer'):
....

#*
def f(*args):
print(args);
#调用时打包成元组
f(1,2,3,4) #(1,2,3,4)

#**只对关键字参数有效,将关键字参数打包成字典
def f(**args):
print(args);
f(a=1,b=2); #{'a'=1,'b'=2}
注意:如要函数要混用这些模式,则顺序必须是位置参数->关键字参数->*->**

18. 函数的高级话题

学习了函数,我们就必须要了解如何使函数聚合性更强,解耦合性能更好,设计函数的功能性。

18.1 递归函数

较循环结构而言,对任意深度的嵌套,循环较难处理,而递归能够容易处理该类嵌套:

1
2
3
4
5
6
7
8
9
10
11
12
13
#能够处理任意嵌套
def sumtree(L:"可以是列表也可是元组")->int:
tot=0;
for x in L:
if isinstance(x,list):
tot+=sumtree(x);
elif isinstance(x,Tuple):
tot+=sumtree(x);
else:
tot+=x;
return tot;

print(sumtree([1,[2,3],(3,4),5,[7,8,[2,3]]])); #38

18.2 属性存储和注解

由于python函数是对象,函数对象可以赋值给其他的变量名、传递给其他函数、嵌入到数据结构、从一个函数返回给另一个函数等等。

18.2.1 属性

函数除了调用以外,我们还可以检查它的属性

1
2
print(sumtree.__name__)
print(dir(sumtree));
输出:
1
2
sumtree
['__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

18.2.2 注解

python3.0以后,可以给函数对象附加注解信息---就是与函数的参数和结果相关的任意用户定义的数据。注解有它专有的语法,但它自身不做任何事情,注解是可选的,并且出现的时候只是直接附加在函数对象的__annotations__属性以供其他用户使用。语法:

  • 函数注解编写在def行
  • 对于参数,它们出现在紧随参数名冒号的后面
  • 对于返回值,它们编写于参数列表之后的->
1
2
3
4
def sumtree(L:"可以是列表也可是元组")->int:
....

print(sumtree.__annotations__); #{'L': '可以是列表也可是元组', 'return': <class 'int'>}
18.3. lambda表达式

除了def之外,python提供了另一种函数对象的表达式,叫lambada也称匿名函数。这个表达式创建了一个之后能够调用的函数,但是它返回一个函数而不是将这个函数赋值给一个变量名,这也是为啥lambda叫匿名函数。

18.3.1 lambda表达式格式

关键字lambda后面接参数列表,:后面为函数体

1
2
lambda arg1,rag2,...:
function_body;
注意:

  • lambda是一个表达式,而不是语句。因为是一个表达式,所有它能出现在def不能出现的地方,如列表中、或者函数参数中等。同时它会返回一个新函数对象,我们可以选择性的将对象赋值给一个变量,方便操作。
  • lambda的主体是一个单个的表达式,而不是一个代码块。因为lambda是为编写简单函数而设计的
1
2
3
4
5
6
7
8
9
def min_test(L,cmp=lambda x,y:x>y):
a=L[0];
for e in L[1:]:
if cmp(a,e):
a=e;
else:
continue;

min_test([3,2,1,0,4]) #0

19深入理解迭代和解析

19.1 重访迭代器:生成器

python对延迟提供了更多的支持,有两种方法尽可能延迟结果创建:

  • 生成器函数:编写为常规的def,但是使用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态
  • 生成器表达式:类似于列表解析,但是它们的返回是按需产生结果对象,而不是像列表解析一样构建一新列表

由于二者都不会创建列表,因此节省了内存空间,并且允许计算时间分散到各个结果请求。

19.1.1 生成器函数

一个送回一个值并随后从退出的地方继续的函数就叫生成器函数生成器函数与常规函数的不同点主要在于:

  • 生成器函数在调用时不会立即执行其函数体中的代码,而是返回一个生成器对象,后续调用__next__才在yield会返回一个值
  • 生成器函数自动在生成值的时刻(yield)挂起,在挂起时要保存函数的状态。
  • 生成器函数代码不同是生成器yield一个值,而不是return,从该角度来看,允许生成器函数随时间产生一系列值。实际来看就是在生成器函数的yield语句处挂起并向调用者返回一个值,但会保留足够的状态以使得生成器函数能在它离开的地方继续执行

1 迭代协议

要理解生成器,必须了解迭代协议。可迭代的对象定义了一个__next__方法,要么返回迭代中的下一项,要么抛出StopIteration异常终止迭代。一个对象的迭代器用iter内置函数接受。如果支持该协议,则for循环和其他迭代语句,使用这种迭代协议来遍历一个序列或者生成器;若不支持,迭代返回去重复序列。

2 生成器:

生成器函数要支持该协议,就必须得包含一条yiled语句,该语句被编译为生成器,一旦调用这条语句就会返回一个迭代对象,该对象支持用一个__next__()函数来继续执行该生成器函数接口。要终止生成器函数,可以在生成器函数末尾有一条return语句,终止生成,也可以引发一个StopIteration异常终止。简而言之,编写了包含yiled的函数称为生成器函数,yield语句为生成器,自动的支持迭代协议

1
2
3
4
5
6
7
def func(L):
for i in L:
yield i**2; #每次yield返回一个值,控制权都会交给主函数main

def main:
for i in func([1,2,3,4]): #1,4,9,16
print(i);

从上面程序可以看到,调用一个生成器函数会返回一个生成器对象,它支持迭代协议,即生成器对象有一个__next__()方法,它可以开始这个函数,或者从它上一次yield值后的地方恢复,并且得到一系列的值,到最后一个时,产生StopIteration异常。

3 扩展生成器函数协议send和next

生成器函数协议增加了一个send()函数,send()函数生成一系列结果的下一个元素,这一点像__next__(),但是它提供了一种调用者与生成器之间的通信。当使用这一额外协议时,值可以通过调用G.send(value)发生给一个value给生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
def simple_generator():  
print("Start of the function")
x = yield 10
print(f"Received: {x}")
y = yield 20
print(f"Received: {y}")
return "End of the function"

gen = simple_generator()

print(next(gen)) # 输出: Start of the function 和 10
print(gen.send("Hello")) # 输出: Received: Hello 和 20
print(gen.send("World")) # 输出: Received: World
  • gen = simple_generator():创建生成器对象 gen,但此时函数体内的代码并未执行。
  • print(next(gen))
    • 执行 simple_generator() 函数体。
    • 打印 "Start of the function"
    • 遇到 yield 10,生成器产生值 10 并暂停。
    • print(next(gen)) 打印出生成器返回的10。
  • print(gen.send("Hello"))
    • send("Hello") 唤醒生成器,并将 "Hello" 作为 x 的值传入(赋值给 x)。
    • 继续执行函数体,打印 "Received: Hello"。
    • 遇到 yield 20,生成器产生值 20 并再次暂停。
    • print(gen.send("Hello")) 打印出生成器返回的20。
  • print(gen.send("World"))
    • send("World") 再次唤醒生成器,并将 "World" 作为 y 的值传入(赋值给 y)。
    • 打印 "Received: World"。
    • 由于此时函数体内没有更多的 yield 语句,函数继续执行到 return "End of the function",但 return 语句在生成器中的效果是触发 StopIteration 异常,并附带返回值 "End of the function"(这个返回值可以通过捕获 StopIteration 异常的 value 属性来获取)。

但在这个例子中,你没有捕获 StopIteration 异常,所以只是简单地打印了 "Received: World",然后生成器就结束了。

19.1.2 生成器表达式

生成器表达式其实跟列表解析相似,区别就是列表解析是在[]内,而生成器表达式是在()内:

1
2
3
4
5
6
7
8
#列表解析
[x**2 for x in range(5)]; #[0,1,4,9]
#生成器表达式
G=(x**2 for x in range(5)) #generator
G.__next__();
#for循环自动迭代
for num in(x**2 for x in range(5)):
print(num);

从上面可知道,生成器表达式很不同,不是在内存种构建结果,而是返回一个生成器对象,这个对象将会支持迭代协议并在任意迭代语境的操作。

1 生成器是单迭代对象

无论是生成器函数还是生成器表达式,都只支持一次活跃迭代,即不能在不同位置的有自己的迭代器:

1
2
3
4
5
G=(x**2 for x in range(5))
iter_1=iter(G);
iter_1.__next__(); #0
iter_2=iter(G);
iter_2.__next_(); #1
如上,两个迭代器总是在同一位置,即只能有一个活跃的迭代器

2. 编写自己的map和zip

mymap:

1
2
3
def mymap(func,*seqs):
return [func(*args) for args in zip(*seqs)]
print(mymap(pow,[1,2,3,4],(3,4,5,6)));
输出:
1
[1, 16, 243, 4096]

myzip:

1
2
3
4
5
6
7
def myzip(func,*seqs):
seqs=[list(s) for s in seqs];
res=[];
while all(seqs):
res.append(tuple(s.pop(0)for s in seqs));
return res;

19.2. 解析语法概况

除了列表解析以外,python3.0还增加了集合解析和字典解析:

1
2
3
4
5
6
7
8
#列表解析
[f(x) for x in S]
#集合解析
{f(x) for x in S}
#字典解析
{key:val for (key,val) in zip(keys,vals)}
#生成器表达式
(x*x for x in range(n))

20. 模块

模块是将程序代码和数据封装起来以方便重用,其实模块就相当于是一个命名空间。模块由两个语句和一个终于的内置函数进行处理:

  • import:让用户导入一个模块
  • from:允许用户从以模块文件获取特定变量名
  • imp.reload:在不终止python程序下,提供重新载入模块文件代码的方法.
20.1 import工作机制

乍一看,我们都会认为python的import很像C/C++的#include<>,但是并不是完全一样,首先一点就是在导入时模块就已经运行了,即一旦import a模块,a模块里面的代码就会执行,如果a模块内有print语句的话就会输出。而C会在预处理阶段将头文件展开,同内部代码一样,执行预处理编译-->汇编-->链接过程

import的三个过程:

1. 找到模块文件

在执行钱必须找到模块文件所在的位置,导入是简单的import a,没有路径,也没有加后缀.py,因为python使用==标准模块搜索路径==

2. 编译成字节码(如果需要)

python会检查模块文件的时间戳,当发现字节码即后缀为.pyc的文件比源代码.py旧,就会重新编译成.pyc文件,覆盖原字节码文件,反之,则不。若在搜索时只有.pyc文件,没有.py文件,直接加载.pyc文件。通过这些算法方式来提高程序启动速度。(注意:只有被导入的文件才会有.pyc文件) 注意:因此多次在一个顶层模块导入模块,导入操作只会执行一次,因为已经生成了最新的.pyc文件

3. 执行模块代码来创建其所定义的对象

最后一步执行,不多说

20.2.标准模块搜索路径

大多数情况下,可以说依赖导入模块的搜索路径的自动特性,完全不要要配置路径。要配置你自己的路径,必须了解模块搜索路径的四个组件:

  • 程序主目录
  • pythonPATH(如果已经设置)
  • 标准链接库目录
  • 任何.pth文件内容

这四个组件组合起来就变成了sys.path。第一个和第三个是自动定义,以及固定了,第二和第四个就可以用来扩展路径,从而包含你自己的外部源代码。

20.3 import和from

格式:

1
2
3
import simple
from simple import x,y
x=42;
在交互的模式下对x赋值运输,只会修改作用域内的变量x,而不是这个文件内的x,以from复制而来的变量名和其来源的文件之间没有联系,如果要修改,应该用import
1
2
3
import simple
simple.x=42

20.4 命名空间

在模块的文件顶层(也就是不在函数或类的主体内)每一个赋值的变量名都会变成该模块的属性。我么们可以在模块外以simple.x对其属性进行引用。

20.4.1 模块加载和作用域
  • 模块语句会在首次导入时执行:模块第一次导入无论在什么地方,都会建立空的模块对象,并逐一执行模块文件内的语句
  • 顶层的赋值语句创建模块属性(如def,=)
  • 模块的命名空间能够通过属性__dict__dir(simple)获得
  • 模块是一个独立的作用域(本地变量就是全局变量):模板自己成立一个作用域,但是它的变量可看作全局变量,文件内都可使用
20.4.2 进行导入操作的文件的与被导入的文件的关系

导入操作不会赋予被导入文件种的代码对上层代码的可见度:即被导入文件无法看见进行导入操作的文件内的变量名

1
2
3
4
5
6
7
8
9
10
11
#modeA.py
x=88
def f():
gloabal x;
x=99;

#modeB.py
import modeA
x=11;
modeA.f()
print(x,modeA.x); #11 99
总结来说:

  • 函数绝对无法看见其他函数内的变量名,除非它们是嵌套的
  • 模块程序代码绝对无法看见其他模块的变量名,除非明确进行了变量导入
20.4.3 模块重载

我们之前提到,模块导入只会执行一次,要强制模块代码重新载入并执行,得要调用reload内置函数:

1
2
import simple
reload(simple); #重载模块
重载模块有几个注意点:

  • reload会在模块当前命名空间内执行模块文件的新代码
  • 重载会影响所有使用import读取的模块属性
  • 重载只会以后对以后使用from的造成影响

21 内置函数

python内部内置了丰富的函数供开发者调用,下面进行简单的介绍。

21.1 数学函数
名称 功能
abs(x) 返回数字的绝对值,如abs(-10)返回 10
ceil(x) 返回数字的上入整数,如math.ceil(4.1) 返回 5
cmp(x, y) 如果 x < y 返回 -1, 如果 x == y 返回 0, 如果 x > y 返回 1
exp(x) 返回e的x次幂(ex),如math.exp(1) 返回2.718281828459045
fabs(x) 返回数字的绝对值,如math.fabs(-10) 返回10.0
floor(x) 返回数字的下舍整数,如math.floor(4.9)返回 4
log(x) math.log(math.e)返回1.0,math.log(100,10)返回2.0
log10(x) 返回以10为基数的x的对数,如math.log10(100)返回 2.0
max(x1, x2,...) 返回给定参数的最大值,参数可以为序列。
min(x1, x2,...) 返回给定参数的最小值,参数可以为序列。
modf(x) 返回x的整数部分与小数部分,两部分的数值符号与x相同,整数部分以浮点型表示。
pow(x, y) x**y 运算后的值。
round(x [,n]) 返回浮点数x的四舍五入值,如给出n值,则代表舍入到小数点后的位数。
sqrt(x) 返回数字x的平方根
21.2 内置函数

这里列举一些简单内置函数列表,不做例子解释:

名称 功能
abs() abs()函数返回数字的绝对值
all() all()函数用于判断给定的可迭代参数iterable中的所有元素是否都为TRUE,如果是返回 True,否则返回False.元素除了0、空、None、False外都算 True
any() any()函数用于判断给定的可迭代参数iterable是否全部为False,则返回False,如果有一个为True,则返回True。元素除了是0、空、FALSE 外都算 TRUE
bin() bin()返回一个整数 int 或者长整数 long int 的二进制表示
bool() bool() 函数用于将给定参数转换为布尔类型,如果没有参数,返回 False。
bytearray() bytearray()方法返回一个新字节数组。这个数组里的元素是可变的,并且每个元素的值范围
callable() 函数用于检查一个对象是否是可调用的。如果返回Trueobject仍然可能调用失败;但如果返回False,调用对象object绝对不会成功。
chr() chr()用一个范围在 range(256)内的(就是0~255)整数作参数,返回一个对应的字符
classmethod修饰符 classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
cmp() cmp(x,y) 函数用于比较2个对象,如果 x < y 返回 -1, 如果x == y返回 0, 如果 x > y返回 1
delattr() 删除属性,delattr(x, 'foobar') 相等于 del x.foobar
dir() dir()函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表
divmod() 把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a / b, a % b)
execfile() 函数可以用来执行一个文件
file() file()函数用于创建一个 file 对象,它有一个别名叫open(),更形象一些,它们是内置函数。参数是以字符串的形式传递的
float() float()函数用于将整数和字符串转换成浮点数。
frozenset() 返回一个冻结的集合,冻结后集合不能再添加或删除任何元素,即不可变
getattr() 返回一个对象的属性值
globals() globals()函数会以字典类型返回当前位置的全部全局变量
hasattr() hasattr()函数用于判断对象是否包含对应的属性
hash() hash()用于获取取一个对象(字符串或者数值等)的哈希值
help() help() 函数用于查看函数或模块用途的详细说明
hex() hex() 函数用于将10进制整数转换成16进制,以字符串形式表示
id() id()函数返回对象的唯一标识符,标识符是一个整数
input() 标准输入流,接受一个标准输入数据,返回为 string 类型
int() 将一个字符串或数字转换为整型
iter() iter()函数用来生成迭代器
len len()方法返回对象(字符、列表、元组等)长度或项目个数
locals() locals()函数会以字典类型返回当前位置的全部局部变量
next() next()返回迭代器的下一个项目,该函数于iter()配套使用
open() open()函数用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出 OSError
ord() 该函数是chr()函数(对于 8 位的 ASCII 字符串)的配对函数,它以一个字符串(Unicode 字符)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值。
pow(x,y) 指数计算,返回\[$x^y\]$
round() 返回浮点数 x 的四舍五入值,准确的说保留值将保留到离上一位更近的一端(四舍六入)
sorted() 对所有可迭代的对象进行排序操作
str() 返回一个对象的string格式,将对象转化为适于人阅读的形式
21.2.1 compile()函数

compile() 函数将一个字符串编译为字节代码,格式如下:

1
compile(source, filename, mode[, flags[, dont_inherit]])
- source:字符串或者AST(Abstract Syntax Trees)对象。。 - filename:代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。 - mode :指定编译代码的种类。可以指定为 exec, eval, single。 - flags :变量作用域,局部命名空间,如果被提供,可以是任何映射对象。。 - flags和dont_inherit是用来控制编译源码时的标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>>str = "for i in range(0,10): print(i)" 
>>> c = compile(str,'','exec') # 编译为字节代码对象
>>> c
<code object <module> at 0x10141e0b0, file "", line 1>
>>> exec(c)
0
1
2
3
4
5
6
7
8
9
21.2.2 complex()函数

complex()函数用于创建一个值为real + imag * j的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数

1
2
complex(1)    # 数字,结果为(1 + 0j)
complex("1") # 当做字符串处理,结果为(1 + 0j)

21.2.3 dict()函数

dict() 函数用于创建一个字典。有四种接口来创建字典

1
2
3
4
dict()                        # 创建空字典
dict(a='a', b='b', t='t') # 传入关键字
dict(zip(['one', 'two', 'three'], [1, 2, 3])) # 映射函数方式来构造字典
dict([('one', 1), ('two', 2), ('three', 3)]) # 可迭代对象方式来构造字典

21.2.4 enumerate()函数

enumerate()函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。

1
2
3
4
5
6
7
#形式
enumerate(sequence, [start=0])

#举例
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
list(enumerate(seasons)) #[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
list(enumerate(seasons, start=1)) # 小标从 1 开始[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

21.2.5 eval()函数

eval()函数用来执行一个字符串表达式,并返回表达式的值。

1
2
x=5
eval( '3 * x' ) #15

21.2.6 filter()函数

filter()函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 TrueFalse,最后将返回 True 的元素放到新列表中。

注意: Python2.7 返回列表,Python3.x 返回迭代器对象,具体内容可以查看:Python3 filter() 函数

1
2
3
4
5
def is_odd(n):
return n % 2 == 1

newlist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(newlist) #[1, 3, 5, 7, 9]
21.2.7 format 格式化函数

Python2.6 开始,新增了一种格式化字符串的函数str.format(),它增强了字符串格式化的功能。基本语法是通过{}:来代替以前的 %format 函数可以接受不限个参数,位置可以不按顺序

1
2
3
4
5
6
7
8
>>>"{} {}".format("hello", "world")    # 不设置指定位置,按默认顺序
'hello world'

>>> "{0} {1}".format("hello", "world") # 设置指定位置
'hello world'

>>> "{1} {0} {1}".format("hello", "world") # 设置指定位置
'world hello world'

21.2.8 isinstance()函数

isinstance()判断一个对象是否是一个已知的类型,类似type()isinstance()type() 区别:

  • type()不会认为子类是一种父类类型,不考虑继承关系。
  • isinstance()会认为子类是一种父类类型,考虑继承关系。
  • 因此如果要判断两个类型是否相同推荐使用 isinstance()
    1
    2
    3
    4
    5
    #语法
    isinstance(object, classinfo)
    #举例
    a = 2
    isinstance (a,int) #True
21.2.9 issubclass()函数

方法用于判断参数 class 是否是类型参数 classinfo 的子类:

1
issubclass(class, classinfo)

21.2.10 list()函数

list()方法用于将元组或字符串转换为列表。

1
2
3
4
5
aTuple = (123, 'Google', 'Runoob', 'Taobao')
list1 = list(aTuple)

str="Hello World"
list2=list(str)
注:元组与列表是非常类似的,区别在于元组的元素值不能修改,元组是放在括号中,列表是放于方括号中。

21.2.11 range()函数

Python3 range()函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表。 Python3 list()函数是对象迭代器,可以把range()返回的可迭代对象转为一个列表,返回的变量类型为列表。

1
2
3
4
5
6
7
8
9
10
11
12
#格式
range(stop)
range(start, stop[, step])
#举例
for i in range(5):
print(i)
#输出
0
1
2
3
4

21.2.12 reversed()函数

reversed 函数返回一个反转的迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 字符串
seqString = 'Runoob'
print(list(reversed(seqString)))

# 元组
seqTuple = ('R', 'u', 'n', 'o', 'o', 'b')
print(list(reversed(seqTuple)))

# range
seqRange = range(5, 9)
print(list(reversed(seqRange)))

# 列表
seqList = [1, 2, 4, 3, 5]
print(list(reversed(seqList)))
结果:
1
2
3
4
['b', 'o', 'o', 'n', 'u', 'R']
['b', 'o', 'o', 'n', 'u', 'R']
[8, 7, 6, 5]
[5, 3, 4, 2, 1]

21.2.13 set()函数

set()函数创建一个无序不重复元素集,可进行关系测试,删除重复数据,还可以计算交集、差集、并集等

1
2
3
4
5
6
7
8
9
10
11
x = set('runoob')
y = set('google')
>>> x, y
(set(['b', 'r', 'u', 'o', 'n']), set(['e', 'o', 'g', 'l'])) # 重复的被删除
>>> x & y # 交集
set(['o'])
>>> x | y # 并集
set(['b', 'e', 'g', 'l', 'o', 'n', 'r', 'u'])
>>> x - y # 差集
set(['r', 'b', 'u', 'n'])
>>>

21.2.14 sorted()函数

sorted()函数对所有可迭代的对象进行排序操作

1
2
3
4
5
6
7
#格式
sorted(iterable, key=None, reverse=False)

example_list = [5, 0, 6, 1, 2, 7, 3, 4]
sorted(example_list, reverse=True)
#结果
[7, 6, 5, 4, 3, 2, 1, 0]

sort 与 sorted 区别:

  • sort是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
  • listsort 方法返回的是对已经存在的列表进行操作,而内建函数sorted方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
21.2.15 hash()函数

hash() 用于获取取一个对象(字符串或者数值等)的哈希值。

1
2
3
hash('test')            # 字符串
#哈希值
2314058222102390712

21.2.16 vars()函数

vars()函数返回对象object的属性和属性值的字典对象。

1
2
3
4
5
6
7
8
#格式
vars([object])
#举例
class Runoob:
a = 1
print(vars(Runoob))
#输出:
{'a': 1, '__module__': '__main__', '__doc__': None}

22. 命令行参数add_argument()用法解析

22.1 argparse介绍

argparse模块是 Python 内置的一个用于命令项选项与参数解析的模块,argparse 模块可以让人轻松编写用户友好的命令行接口。通过在程序中定义好我们需要的参数,然后 argparse 将会从 sys.argv 解析出这些参数。argparse 模块还会自动生成帮助和使用手册,并在用户给程序传入无效参数时报出错误信息。

三个步骤:

  • 1、创建一个解析器——创建 ArgumentParser() 对象
  • 2、添加参数——调用 add_argument() 方法添加参数
  • 3、解析参数——使用 parse_args() 解析添加的参数
22.2 创建一个解析器——创建 ArgumentParser() 对象

使用 argparse 的第一步是创建一个 ArgumentParser 对象:

1
parser = argparse.ArgumentParser(description='test')
ArgumentParser 对象包含将命令行解析成 Python 数据类型所需的全部信息。

  • description:大多数对 ArgumentParser 构造方法的调用都会使用 description= 关键字参数。这个参数简要描述这个程度做什么以及怎么做。在帮助消息中,这个描述会显示在命令行用法字符串和各种参数的帮助消息之间。
22.3 添加参数——调用 add_argument() 方法添加参数

给一个 ArgumentParser 添加程序参数信息是通过调用 add_argument() 方法完成的。通常,这些调用指定 ArgumentParser 如何获取命令行字符串并将其转换为对象。这些信息在 parse_args() 调用时被存储和使用。例如

1
2
3
parser.add_argument('--sparse', action='store_true', default=False, help='GAT with sparse version or not.')
parser.add_argument('--seed', type=int, default=72, help='Random seed.')
parser.add_argument('--epochs', type=int, default=10000, help='Number of epochs to train.')
add_argument() 方法定义如何解析命令行参数:
1
ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])

每个参数解释如下:

  • name or flags: 选项字符串的名字或者列表,例如 foo 或者 -f, --foo。
  • action : 命令行遇到参数时的动作,默认值是 store。
  • store_const:表示赋值为const;
  • append:将遇到的值存储成列表,也就是如果参数重复则会保存多个值;
  • append_const:将参数规范中定义的一个值保存到一个列表;
  • count:存储遇到的次数;此外,也可以继承 argparse.Action 自定义参数解析;
  • nargs: 应该读取的命令行参数个数,可以是具体的数字,或者是?号,当不指定值时对于 Positional argument 使用 default,对于 Optional argument 使用 const;或者是 * 号,表示 0 或多个参数;或者是 + 号表示 1 或多个参数。
  • const: action 和 nargs 所需要的常量值。
  • default : 不指定参数时的默认值。
  • type: 命令行参数应该被转换成的类型。
  • choices : 参数可允许的值的一个容器。
  • required: 可选参数是否可以省略 (仅针对可选参数)。
  • help : 参数的帮助信息,当指定为 argparse.SUPPRESS 时表示不显示该参数的帮助信息.
  • metavar:在 usage 说明中的参数名称,对于必选参数默认就是参数名称,对于可选参数默认是全大写的参数名称.
  • dest: 解析后的参数名称,默认情况下,对于可选参数选取最长的名称,中划线转换为下划线.
22.4 解析参数——使用 parse_args() 解析添加的参数

rgumentParser 通过 parse_args() 方法解析参数。它将检查命令行,把每个参数转换为适当的类型然后调用相应的操作。在大多数情况下,这意味着一个简单的 Namespace 对象将从命令行解析出的属性构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
args = parser.parse_args()
````

##### 22.5 实例
```python
import argparse

parser = argparse.ArgumentParser(description='test')

parser.add_argument('--sparse', action='store_true', default=False, help='GAT with sparse version or not.')
parser.add_argument('--seed', type=int, default=72, help='Random seed.')
parser.add_argument('--epochs', type=int, default=10000, help='Number of epochs to train.')

args = parser.parse_args()

print(args.sparse)
print(args.seed)
print(args.epochs)