Python 个人学习笔记【简要归纳】
目录
Python交互模式和直接运行py文件
Python交互模式
在命令行模式下敲命令python,就看到一堆文本输出,然后就进入到Python交互模式,它的提示符是>>>
直接运行py文件
在Mac和linux上可直接运行.py文件 方法是在.py文件的第一行加上一个特殊的注释: #!/usr/bin/env python3
print('hello, world') 然后,通过命令给hello.py以执行权限:
$ chmod a+x hello.py
区别
直接输入python进入交互模式,相当于启动了Python解释器,但是等待你一行一行地输入源代码,每输入一行就执行一行。
直接运行.py文件相当于启动了Python解释器,然后一次性把.py文件的源代码给执行了,你是没有机会以交互的方式输入源代码的。
变量和常量
变量名必须是大小写英文、数字和_
的组合,且不能用数字开头
在Python中,等号=
是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量
在Python中,通常用全部大写的变量名表示常量
Python的整数没有大小限制,Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)。
Python中的两种除法
在Python中,有两种除法,一种除法是/
:
/
除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数。还有一种除法是//
,称为地板除,两个整数的除法仍然是整数。
字符串
对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符
如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。
Python对bytes类型的数据用带b前缀的单引号或双引号表示:
x = b'ABC'
以Unicode表示的str通过encode()方法可以编码为指定的bytes 反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法
如果bytes中包含无法解码的字节,decode()方法会报错 如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节 要计算str包含多少个字符,可以用len()函数 len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数
格式化
在Python中,采用的格式化方式和C语言是一致的,用%实现,举例如下:
'Hello, %s' % 'world'
'Hello, world'
'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
%运算符就是用来格式化字符串的。在字符串内部,%s表示用字符串替换,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略。
另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……
'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'
列表list
用索引来访问list中每一个位置的元素,记得索引是从0开始的 用-1做索引,直接获取最后一个元素 classmates.append('Adam')
追加元素到末尾 classmates.insert(1, 'Jack')
在指定位置插入元素 classmates.pop()
删除末尾元素 classmates.pop(i)
删除指定索引位置的元素
元组tuple
tuple一旦初始化就不能修改,但获取元素可正常使用。 因为tuple不可变,所以代码更安全。 只有1个元素的tuple定义时必须加一个逗号,用于消除歧义 tuple所谓的“不变”是说,tuple的每个元素,指向永远不变,比如指向某个list,list中的内容可变。
循环
① for...in循环,依次把list或tuple中的每个元素迭代出来
② while循环,只要条件满足,就不断循环,条件不满足时退出循环。
dict和set
dict的key必须是不可变对象。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
set可以看成数学意义上的无序和无重复元素的集合
函数
如果想定义一个什么事也不做的空函数,可以用pass语句,pass可以用来作为占位符
Python的函数返回多值其实就是返回一个tuple
函数的参数
默认参数
设置默认参数注意事项:
一是必选参数在前,默认参数在后,否则Python的解释器会报错 二是如何设置默认参数。
当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
定义默认参数要牢记一点:默认参数必须指向不变对象!
可变参数
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数。
关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
关键字参数可扩展函数功能
命名关键字参数
和关键字参数*kw不同,命名关键字参数需要一个特殊分隔符,*后面的参数被视为命名关键字参数。
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。
参数组合
对于任意函数,都可以通过类似func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
递归函数
解决递归调用栈溢出的方法是通过尾递归优化 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。 遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把函数改成尾递归方式,也会导致栈溢出。
切片
前10个数,每两个取一个: L[:10:2]
所有数,每5个取一个: L[::5]
只写[:]
就可以原样复制一个list
tuple也可以用切片操作,只是操作的结果仍是tuple 字符串也可以用切片操作,只是操作结果仍是字符串
迭代
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
在Python中,迭代是通过for ... in
来完成的。只要是可迭代对象,无论有无下标,都可以迭代,比如dict。默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values()
,如果要同时迭代key和value,可以用for k, v in d.items()
。
判断是否为可迭代对象:
from collections import Iterable
isinstance('abc', Iterable) # str是否可迭代
True
Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
for i, value in enumerate(['A', 'B', 'C']):
print(i, value)
0 A
1 B
2 C
列表生成式
写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来。
for循环后面还可以加上if判断。
还可以使用两层循环。
lower()方法可以将一个list中的所有字符串变成小写
生成器
在Python中,一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。
第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator。 如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值。正确的方法是使用for循环,因为generator也是可迭代对象。
如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。要把函数变成generator,只需要把print()改为yield()就可以了。
变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
迭代器Iterator
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数
Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。
函数式编程
高阶函数
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
map()、reduce()
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
filter()
Python内建的filter()函数用于过滤序列。
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
sorted()
Python内置的sorted()函数可以对list进行排序。
sorted()函数可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序。
sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
注意:每次调用都会返回一个新的函数,即使传入相同的参数
f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
f1==f2
False
闭包
能够读取其他函数内部变量的函数。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。
匿名函数
关键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数的限制:只能有一个表达式,不用写return,返回值就是该表达式的结果。
因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。
装饰器
函数对象有一个__name__
属性,可以拿到函数的名字
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
偏函数
当函数的参数个数太多,需要简化时,使用
functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
面向对象编程
访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
继承
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
获取对象信息
type()
isinstance()
dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list
配合getattr()、setattr()
以及hasattr()
,我们可以直接操作一个对象的状态
如果试图获取不存在的属性,会抛出AttributeError的错误
可以传入一个default参数,如果属性不存在,就返回默认值
getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
使用__slots__
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性。
__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。
子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。
调试
print()
assert
凡是用print()
来辅助查看的地方,都可以用断言(assert)来替代
如果断言失败,assert
语句本身就会抛出AssertionError
启动Python解释器时可以用-O
参数来关闭assert
,关闭后,你可以把所有的assert
语句当成pass
来看。
logging
把print()
替换为logging
是第3种方式,和assert
比,logging
不会抛出错误,而且可以输出到文件。
import logging
logging.basicConfig(level=logging.INFO)
它允许你指定记录信息的级别,有debug
,info
,warning
,error
等几个级别,当我们指定level=INFO
时,logging.debug
就不起作用了。同理,指定level=WARNING
后,debug
和info
就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
pdb
第4种方式是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。
pdb.set_trace() 这个方法也是用pdb,但是不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点.
单元测试
为了编写单元测试,我们需要引入Python自带的unittest
模块
编写单元测试时,我们需要编写一个测试类,从unittest.TestCase
继承。
以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。
可以在单元测试中编写两个特殊的setUp()
和tearDown()
方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
设想你的测试需要启动一个数据库,这时,就可以在setUp()
方法中连接数据库,在tearDown()
方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码。
进程和线程
多进程
在Unix/Linux下,可以使用fork()
调用实现多进程。
要实现跨平台的多进程,可以使用multiprocessing
模块。
multiprocessing
模块提供了一个Process类来代表一个进程对象。
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process
实例,用start()
方法启动,这样创建进程比fork()
还要简单。
join()
方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
进程间通信是通过Queue、Pipes等实现的。
以Queue
为例,在父进程中创建两个子进程,一个往Queue
里写数据,一个从Queue
里读数据。
多线程
多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又要小心死锁的发生。
由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()
来实现:
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
当多个线程同时执行lock.acquire()
时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally
来确保锁一定会被释放。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。
注:Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。
GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。