Python学习笔记第三弹
一级目录
二级目录
三级目录
3.1高级特性
3.1.1切片
List在python语言中很常用,取出list中的元素也是频繁的操作。如何取出list中元素,学习过循环后,自然地想到了使用循环结构:
#如何取一个List的前三个元素
grade=[43,24,66,3,7,79]
#直接想法使用for循环,利用下标取出
#注意range(x)得到的是0到小于x的一个序列,序列是不包括x的
r=[]
for i in range(3):
r.append(grade[i])
print(r)
但是频繁取出元素就需要频繁地写循环结构,为了化简这种操作,python提供了切片(Slice)语法:
#若要频繁地从List中取元素,写循环会变得比较繁琐,
# python语言提供了切片Slice的语法:
x=grade[0:3]
print(x)
其中grade[0:3]
表示从索引0
开始取,直到索引3
为止,但不包括索引3
。即索引0
,1
,2
,正好是3个元素。如果索引是0的话,那么可以省略,即上面的语句可以直接写成:grade[:3]
当然也可以从1开始,取到3:grade[1:3],
,一共取出两个元素
类似的,既然Python支持L[-1]
取倒数第一个元素,那么它同样支持倒数切片:
y=grade[-2:-1]
print(y)
y=grade[-2:]
print(y)
上述两次输出结果是不一样的:
[7]
[7, 79]
切记最后一个元素的索引为-1
下面经过实例展示切片的方便之处:
#举例说明切片的用法:
#先产生一个0-100的序列:
myList=list(range(101))
print(myList)
#取出前十个数:
print(myList[:10])
#取出倒数十个数:
print(myList[-10:])
#取出前11-20个数
print(myList[10:20])
#前10个数,每两个一取
print(myList[:10:2])#这里和matlab的语法区分一下
#所有的数:
print(myList[:])
#所有的数,每5个一取
print(myList[::5])
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:
>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)
字符串'xxx'
也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[::2]
'ACEG'
很多语言,比如java,c语言等对于字符串都提供了求子串subString()
类似的函数或者方法,但是python没有,在python中只要使用切片就能简单完成这项操作。
#tuple也可以切片,只不过切片的结果还是tuple
myTuple=(0,1,44,2,4)
print(myTuple[1:3])
#字符串也可以切片
string="pythonyyds"
print(string[-4:])#yyds
3.1.2迭代
迭代:通过for循环来遍历一个list或者一个tuple
在python中迭代是通过for...in
来进行的。(其他的语言,比如C语言是通过下标来实现的)python的for不仅可以作用在list或者tuple这种有下标的类型上,可以作用在一切可迭代对象上,比如dict或者set,没有下标但是是可迭代的。
myDict={"Mike":43,"Judy":22,"Thomas":23}
#默认情况下for in迭代的是dict中的key
for key in myDict:
print(key)
#迭代value
for value in myDict.values():
print(value)
#key-value都迭代:
for k,v in myDict.items():
print(k,v)
当我们使用for
循环时,只要作用于一个可迭代对象,for
循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。
那么,如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:
语句如下:
# 如何判断一个对象是可迭代的?
# 使用 isinstance(x,iterable)语句,
# 返回True或者False
from collections.abc import Iterable
print(isinstance("ABC",Iterable))
print(isinstance(123,Iterable))
最后一个小问题,如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate
函数可以把一个list变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身:
#使用enumerate函数使list变成索引-元素对
#python中可以在一个循环中同时引用两个变量
for x,y in enumerate(["A","B","C"]):
print(x,y)
3.1.3列表生成器
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式
直接看下面的代码,使用list()
生成一个列表
#使用列表生成器生成一个0-10的列表
print(list(range(11)))
#生成[1×1,2×2,3×3...]
#方法一:循环
L=[]
for i in range(11):
L.append(i**2)
print(L)
#方法二:直接使用一行语句替代
print([x*x for x in range(11)])
#注意把x*x这个结果写在前面,后面跟上正常的for循环语句即可
print([x**3 for x in range(11)])
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方
# 列表生成器后面还能加上if条件
print([x**2 for x in range(11) if x%2==0])
#使用两层循环生成全排列
print([x+y for x in "ABCD" for y in "EFGH"])
#注意,三层以及三层以上的for循环语句就很少会使用了
还能够加上函数调用等操作:
myDict={"Mike":44,"Judy":23,"Potter":6}
#前面讲述过使用for循环来进行key和value的迭代:
#同时迭代:
for x,y in myDict.items():
print("(",x,y,")")
#只迭代value:
for x in myDict.values():
print(x)
myDict2={'xyz': 'A', 'yxz': 'B', 'zxy': 'C'}
#现在使用列表生成器来完成这项功能:
print([k+'='+v for k,v in myDict2.items()])
#将一个list中的首字母全部大写
print([k.title() for k in myDict2])
特别的,列表生成器的if...else
语句该如何写:记住if…else要放在for之前即可,即:
#if...else结构:
print([x if x%2==0 else -x for x in range(11)])
3.1.4生成器
通过列表生成式,我们可以直接创建一个列表。
但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素空间都浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器:generator
对比列表生成器,生成器只是将列表生成器的[ ]表示的列表换成了( )。有下面的代码:
#产生0-10的列表
l=[x for x in range(11)]
print(l)
#产生0-10的generator
g=(x for x in range(11))
print(g)
#上一行代码的结果是:<generator object <genexpr> at 0x0000014042360740>
#generator保存的是算法,而不是结果
最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
3.1.5迭代器
总结一下之前的知识,可以用于for循环的数据类型有以下几种:
-
集合数据类型
- list
- tuple
- dict
- set
- str
-
generator
这些可以用for循环迭代的对象统称为可迭代对象。
要判断一个对象是否为可迭代对象可以使用isinstance(x,Iterable)
语句。具体见3.1.2迭代。
其中generator是Iterator
对象,但是list,tuple,dict不是Iterator
。
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
那么如何判断可迭代,和是否是Iterator
呢?
-
可迭代:可以用for去遍历
-
是
Iterator
:可以用next()
函数去获取下一个 -
集合数据类型如
list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。Python的
for
循环本质上就是通过不断调用next()
函数实现的。
3.2函数式编程
3.2.1高阶函数
3.2.1.1map和reduce
1.map
功能:map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用于序列的每个元素,并把结果作为新的Iterator
返回。
举例说明,将函数
f
(
x
)
=
x
2
f(x)=x^2
f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]
上,使用map()
实现如下:
#讲述map函数
L=[1,2,3,4,5]
#不使用map函数的情况下,将list中的所有的元素变为他的平方
#法1:for循环迭代:
R1=[]
for i in L:
R1.append(i**2)
print("for循环结果:",R1)
#法2:列表生成器:
R2=[x*x for x in L]
print("列表生成器结果:",R2)
#使用map函数
def f(x):
return x**2
R3=map(f,L)
print("map函数结果:",R3)
运行结果如下:
for循环结果: [1, 4, 9, 16, 25]
列表生成器结果: [1, 4, 9, 16, 25]
map函数结果: <map object at 0x000001C768173160>
可以看出for循环和列表生成器的结果都是一个列表,但是map
不同,返回的是一个Iterator
对象。
#要想将结果进一步转化成List而不是一个Iterator可以使用list函数:
R4=list(map(f,L))
print("map函数转化:",R4)
列表生成器可以和map函数完成相似的功能,甚至更多
2.reduce
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
下面给出几个例子,不做具体说明
①序列求和的reduce实现:
特别注意,使用reduce
函数要加上一句from functools import reduce
。
from functools import reduce #python3要求
#reduce函数的作用是对上一次产生的结果继续和序列的下一个元素做运算。
#例1:使用reduce实现累加运算:
def toSum(x,y):
return x+y
L=[1,2,3,4,5]
print("使用reduce函数求和结果:",reduce(toSum,L))
#其实直接使用sum函数也可以解决:
print("使用sum函数求和结果:",sum(L))
②把序列[1, 3, 5, 7, 9]
变换成整数13579
from functools import reduce
#reduce实例二:
# 把序列[1, 3, 5, 7, 9]变换成整数13579
L=[1,3,5,7,9]
def toInt(x,y):
return x*10+y
print(reduce(toInt,L))
③考虑到字符串str
也是一个序列,对上面的例子稍加改动,配合map()
,我们就可以写出把str
转换为int
的函数:
from functools import reduce
#使用reduce将str转换为int
string="983537"
def charToNum(s):
digits={'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
return digits[s]
def fn(x,y):
return x*10+y
print(reduce(fn,map(charToNum,string)))
可以看出reduce
函数可以作用于一个Iterator
。
3.2.1.2 filter
filter
和map
相似,也接收一个函数和一个序列
和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。(有点像列表生成器里面的if…else语句的作用了)
最后要注意filter
返回的也是一个Iterator
给出下面的例子,在一个序列中保留奇数,删除偶数
#使用filter保留一个序列中的奇数,删除偶数
L=[1,2,3,4,5,6]
def is_odd(n):
if n%2==0:
return True
return False
print(list(filter(is_odd,L)))
把一个序列中的空字符串删掉,可以这么写:
#把字符串中的空字符删掉
def not_empty(s):
return s and s.strip()
L="A B D DDD"
print(list(filter(not_empty,L)))
- 使用
filter
求素数
3.2.1.3 sorted
函数
排序的核心是比较两个数的大小,而对于dict或者字符串来说,直观上是无法比较他们的大小的,因此比较的过程必须使用函数进行抽象。
Python内置的sorted
函数可以对list
进行排序:
#使用sorted对列表进行排列
L=[1,4,6,22,5,3,55,9,10,-1,32]
print(sorted(L))
特别的是sort()
可以接受一个key
来实现自定义的排序。
原理是key
所规定的函数将作用在list
的每一个元素上,这样有了一个已经处理过的list
,sorted
函数再对这个已经处理过的list
进行排序
举例如下:
①按照绝对值大小进行排序
#sorted可以接收一个key,用来实现自定义排序
L=[1,4,6,22,5,3,55,9,10,-1,32,-97]
#实现按照绝对值大小进行排序:
print(sorted(L,key=abs))
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs
处理过的list:
list = [36, 5, -12, 9, -21]
keys = [36, 5, 12, 9, 21]
然后sorted()
函数按照keys进行排序,并按照对应关系返回list相应的元素:
keys排序结果 => [5, 9, 12, 21, 36]
| | | | |
最终结果 => [5, 9, -12, -21, 36]
②对于字符串的忽略大小写排序
忽略大小写排序可以将接收的key
设置为函数将原有的列表变为全大写或者全小写,再利用sorted函数进行排序即可。
#实现对字符串的忽略大小写排序:
L=['bob', 'about', 'Zoo', 'Credit']
print(sorted(L,key=str.lower))
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。
3.3模块
python中本身就内置了很多有用的模块,只要安装完毕就可以使用。
以内置的sys
模块为例,编写一个hello
的模块。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__=='__main__':
test()
第1行和第2行是标准注释,第1行注释可以让这个hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
后面开始就是真正的代码部分。
你可能注意到了,使用sys
模块的第一步,就是导入该模块:
import sys
导入sys
模块后,我们就有了变量sys
指向该模块,利用sys
这个变量,就可以访问sys
模块的所有功能。
sys
模块有一个argv
变量,用list存储了命令行的所有参数。argv
至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:
运行python3 hello.py
获得的sys.argv
就是['hello.py']
;
运行python3 hello.py Michael
获得的sys.argv
就是['hello.py', 'Michael']
。
最后,注意到这两行代码:
if __name__=='__main__':
test()
当我们在命令行运行hello
模块文件时,Python解释器把一个特殊变量__name__
置为__main__
,而如果在其他地方导入该hello
模块时,if
判断将失败,因此,这种if
测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
我们可以用命令行运行hello.py
看看效果:
$ python3 hello.py
Hello, world!
$ python hello.py Michael
Hello, Michael!
如果启动Python交互环境,再导入hello
模块:
$ python3
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 23 2015, 02:52:03)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>>
导入时,没有打印Hello, word!
,因为没有执行test()
函数。
调用hello.test()
时,才能打印出Hello, word!
:
>>> hello.test()
Hello, world!
作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_
前缀来实现的。
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc
,x123
,PI
等;
类似__xxx__
这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__
,__name__
就是特殊变量,hello
模块定义的文档注释也可以用特殊变量__doc__
访问,我们自己的变量一般不要用这种变量名;
类似_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;
之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。
private函数或变量不应该被别人引用,那它们有什么用呢?请看例子:
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
我们在模块里公开greeting()
函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()
函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:
外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。