python3入门-廖雪峰python3教程笔记

Author Avatar
KING Aug 02, 2017 Aug 02, 2017 UPDATED

主要记录下自己学习过程中的一些小笔记和疑问,以备复习巩固,主要学习资料为:Python3入门-by廖雪峰

函数返回值

参考:定义函数

一个函数可以返回多个值,用逗号分隔开即可,其实是被隐式的转换为一个tuple元组了,然后在调用函数后使用多个变量去接收这个tuple,而能够依次对应tuple中的每一个值,这种方式与js6中的解构赋值的新特性非常相似。

1
2
3
4
5
6
import math

def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny

使用:

1
2
3
x, y = move(100, 100, 60, math.pi / 6)
print(x, y)
151.96152422706632 70.0

函数的参数

参考:函数的参数

一个函数,传入一个list,添加一个END再返回:

1
2
3
def add_end(L=[]):
L.append('END')
return L

普通传入数组的情况下是能正常使用的,

1
2
3
4
add_end([1, 2, 3])
[1, 2, 3, 'END']
add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

但是若连续多次使用默认参数则会出现如下问题:

1
2
3
4
add_end()
['END', 'END']
add_end()
['END', 'END', 'END']

似乎提到了这样的问题:关于多次调用同一个函数且不提供参数覆盖默认参数时会出现默认参数不销毁的问题(从其他高级语言如java,c++等类比,理论上函数调用后所有的局部变量都会被销毁)

博主的解释是python中函数定义的时候即将参数值初始化(而不是运行时再初始化)导致的,而解决的方法就是将默认参数指向不变对象,比如改为None,然后在函数内首先判断是否为None即可解决此问题:

1
2
3
4
5
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L

初一看发现没看懂为什么,但是琢磨一下,发现其实此解释也可以这样理解:因为一个数组[]是变量,即我们看到的变量名L其实是指向内存中的一个地址,该地址在默认情况下在定义的时候已经确定了,若不传入新的数组变量名,则变量名L地址不会被改变,而传入变量名的地址会覆盖原定义的L变量名,所以若不是连续调用,则不会出现这个问题。

同时,python中的函数参数定义非常非常灵活,与其他语言有较大差别,需要多用多记。

参数类型共有5种:必选参数、默认参数、可变参数、命名关键字参数和关键字参数

参数组合

同时由于tuple和dict的特色结构,即:

对于任意的函数,都可以通过类似func(args, *kw)的形式调用它,无论它的参数是如何定义的。

关于args和*kw

很多python函数的参数都是类似这样有前缀*或者**的,那么这里到底是什么意思呢?

总的而言*参数名是可变参数,用于接收一个不确定传入参数数量情况,**参数名是关键字参数,用于接收一个字典,

而python又规定,可变参数和关键字参数的传入的参数可以是任意个数,所以,不管传入的是什么参数,多少参数,都可以通过args接收到,然后若又传入了配置字典参数,则通过**kw获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def f1(a, b, c=0, *args, **kw):
print('f1:','a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
print('f2:','a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

def f3(*args, **kw): #对f3来说,无论传入什么都可以接收到
print('f3:', 'args = ', args, 'kw =', kw)

args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw) # f1: a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}

args = (1, 2, 3)
kw = {'d': 88, 'x': '#'}
f2(*args, **kw) # f2: a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

f3(*args, **kw) # f3: args = (1, 2, 3) kw = {'d': 88, 'x': '#'}
f3(1, 2, 3) # f3: args = (1, 2, 3) kw = {}
f3(*args) # f3: args = (1, 2, 3) kw = {}
f3(**kw) # f3: args = () kw = {'d': 88, 'x': '#'}

尾递归

参考:递归函数

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

由filter学到的求素数的方法

用filter求素数

在很多面试或者OJ中都有这样的求素数的题目,而用埃氏筛法可以非常简单而快速的求出指定区间内的所有素数。思想如下:从小开始,将每一个该素数的倍数都筛选掉即可。

首先,列出从2开始的所有自然数,构造一个序列:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取新序列的第一个数5,然后用5把序列的5的倍数筛掉:
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
不断筛下去,就可以得到所有的素数。

关于IO密集/计算密集型任务与语言的选择问题

进程 vs. 线程这节谈到计算密集型 vs. IO密集型的问题,说到:

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

看到这里想到了php,node这样如python一样的解释型语言,然后联想到C++,C这样的编译语言,就能完美解释为什么C++和C在Web应用的不适合问题了。

关于散列算法(Hash)在登陆上的应用

  1. hash密文存储用户密码,防止数据库直接查看密码
  2. 使用salt(盐值)解决简单口令的hash值相同的问题,(salt值需要保密)
  3. 若用户名不可更改,则可将用户名作为hash的一部分,解决相同密码hash值一样