Python笔记

Python基础

字符串和编码

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件;浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器。

一共有三种编码方式:ASCIIUTF-8UnicodeASCII只能表示英语,不能表示中文。Unicode全世界都在用,不会存在乱码问题,但是占得地方比较大。UTF-8可以兼容ASCII码,也可以表示其他语言。

字符串与正则表达式

常用的关于字符串的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
str1 = 'hello, world!'

# 通过len函数计算字符串的长度
print(len(str1)) # 13

# 获得字符串首字母大写的拷贝
print(str1.capitalize()) # Hello, world!

# 获得字符串变大写后的拷贝
print(str1.upper()) # HELLO, WORLD!

# 从字符串中查找子串所在位置
print(str1.find('or')) # 8
# 与find类似但找不到子串时会引发异常
# print(str1.index('shit'))

# 检查字符串是否以指定的字符串开头
print(str1.startswith('He')) # False
# 检查字符串是否以指定的字符串结尾
print(str1.endswith('!')) # True

# 将字符串以指定的宽度居中并在两侧填充指定的字符
print(str1.center(50, '*'))
# 将字符串以指定的宽度靠右放置左侧填充指定的字符
print(str1.rjust(50, ' '))
---------
str2 = 'abc123456'

# 检查字符串是否由数字构成
print(str2.isdigit()) # False
# 检查字符串是否以字母构成
print(str2.isalpha()) # False
# 检查字符串是否以数字和字母构成
print(str2.isalnum()) # True
---------
str3 = ' jackfrued@126.com '

# 获得字符串修剪左右两侧空格的拷贝
print(str3.strip())

关于正则表达式,这里有一篇正则表达式30分钟入门教程,大家可以看一下。

函数的参数

实参与形参

在自己写程序的时候,遇到过这样一个问题:

1
2
3
4
5
6
7
8
def mean_plus(lst):
for i in lst:
if i % 2 == 0:
i = i + 100
return lst

list1 = [1, 2, 3, 4, 5]
print(mean_plus(list1))

本来以为可以输出[1, 102, 3, 104, 5],可实际上却只输出了[1, 2, 3, 4, 5]

先看这一段代码:

1
2
3
4
5
6
7
8
9
10
def swap(x, y):
t = x
x = y
y = t
return x, y

a = 15
b = 50
swap(a, b)
print(a, b) # 15, 50

本来想用这段代码来交换ab的值,可是却还是输出了他们之前的值。要想解答这个问题,先得知道实参与形参的概念。下面一幅图来解释参与形参,以及他们的关系。

比如这个例子中ab就是实参,他们有着对应的值1550。而swap(x, y)中的xy形参,他们只是一个记号,并没有指向任何实际的值。

在执行swap(a, b)时,形参(x, y)把a和b的值(15, 50)拿过来使用,并没有改变a对15,b对50的指向。所以像图上看到的一样,在执行print()语句时,结果并没有调换, 还仍然是15, 50。但是,当你执行print(swap(a, b))时,你得到的结果将是50, 15,这两个值时由经过函数运算值之后的形参得来的,与实参没有什么关系。

以上就是在编写以及运行函数时需要注意的点,回到最开始的问题,其实都是大同小异的:

执行for i in list1: 时,判断条件在数组中找到了复数2,并且将i赋予了新的值。如果你在这时执行print(i),将会得到i = 102,但是i的值对整个数组毫无影响,当i++时,i又指向了3。所以更改i并不能改变原数组的任何一个元素。如果需要类似功能可以使用map函数。

位置参数

1
2
3
4
5
6
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

(x, n)就是位置参数。

默认参数

1
2
3
4
5
6
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

n就是默认参数。

可变参数

1
2
3
4
5
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

这个函数可以传入多个参数,比如calc(1, 2, 3)或者calc(1, 2, 3, 4, 5),那传入已有的list或者是tuple不是更方便🐎?要是想将元组numbers = (1, 2, 3, 4, 5)中的各个元素传入calc()的参数怎么办呢?所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

1
2
3
4
5
6
numbers = (1, 2, 3, 4, 5)
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

关键字参数

这个函数:

1
2
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

这个kw是一个dict,你可以为此函数传入其他key以及他的value。它可以扩展函数的功能:比如,在person函数里,我们保证能接收到nameage这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。

当然,关键字参数也可以像可变参数*numbers一样传入已有的dict:

1
2
3
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)
# name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

命名关键字参数

命名关键字参数就像是关键字参数的限制版本,如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数:

1
2
def person(name, age, *, city, job):
print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。命名关键字参数必须传入参数名,没有传入时调用将报错。所以命名关键字参数可以有缺省值,从而简化调用。

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

生成器与杨辉三角

与list不同,生成器储存的不是一个个的元素,而是生成这个元素的算法。在generator执行过程中,遇到yield就中断,下次执行时就会执行yield之后的代码。

杨辉三角

这个也太难了,答案思路还都不太一样。我在网上搜到了一个挺好理解的答案

1
2
3
4
5
6
def triangle():
N = [1]
while True:
yield N
N.append(0)
N = [N[i]+N[i-1] for i in range(len(N))]

这个答案的重点在与N.append(0),就是将每一排的最右面一个1的右边加了一个0

1
2
3
[1]
[1, 1, 0]
[1, 2, 1]

以第三排[1, 2, 1]为例:第一个元素1 = L[0] + L[-1] ; 第三个元素1 = L[2] + L[1],这样就将看似特例的最两边的1给普遍化了起来。

其实这样还是有点难想,还可以用insert()方法在前一行list首位插入一个0:

1
2
3
4
5
6
7
def triangle():
N = [1]
while True:
yield N
N.append(0)
N.insert(0, 0)
N = [N[i]+N[i+1] for i in range(len(N)-1)]

再以第三排为例:第一个元素1 = L[0] + L[1]; 第三个元素1 = L[2] + L[3],这样就好看多了。

1
2
3
 [0, 1, 0]
[0, 1, 1, 0]
[1, 2, 1]

斐波那契数列

回过头再看一下斐波那契数列(0, 1, 1, 2, 3, 5, 8, 13…):

1
2
3
4
5
def fib():
a, b = 0, 1
while True:
yield b
a, b = b, a + b

斐波那契数列中的第3个数是前两个元素之和,所以说一个循环涉及到的变量是一定是只有3个元素的,而循环结束时,第2, 3个元素要在下个循环开始前又变成了下个循环的第1, 2个元素(a, b = b, a + b)。

Python进阶

map&reduce函数

1
2
3
4
5
6
def f(x):
return x * x

r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])

list(r) #[1, 4, 9, 16, 25, 36, 49, 64, 81]

r的数据类型类型type(r)是map,如果想要list结果需要加上list(r).

1
2
3
4
5
from functools import reduce
def fn(x, y):
return x * 10 + y

reduce(fn, [1, 3, 5, 7, 9]) #13579

reduce()使用时需要加载functools库,而且reduce使用的函数一定(?)要有两个位置参数。

将字符串转换为浮点数

作业叭算是,弄了好久才弄明白

1
2
3
4
5
6
7
8
9
10
11
12
from functools import reduce
def str2float(s):
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
i = 0
while s[i] != '.':
i = i + 1
new_s = s[:i] + s[i+1:]
def s2i(str):
return DIGITS[str]
def fn(x, y):
return x * 10 + y
return reduce(fn, list(map(s2i, new_s))) * (10 ** -(len(s)-i-1))

下面看到了更短的答案:

1
2
3
4
5
def str2float(s):
a = s.split('.') # a = ['123', '456']
n = reduce(lambda x,y:x*10+y,map(lambda x:ord(x)-48, a[1]))/10**len(a[1])
n += reduce(lambda x,y:x*10+y,map(lambda x:ord(x)-48, a[0]))
return n

我的结果思路是把.去掉在乘以小数确定小数点的位置,而这个答案用了str.split()函数来将字符串分成了在list中的两个部分,还用了ord(x)-48替换掉了我的s2i(str)函数。

关于split()

1
2
3
4
5
6
7
str = "Line1-abcdef \nLine2-abc \nLine4-abcd";
print str.split( ); # 以空格为分隔符,包含 \n
print str.split(' ', 1 ); # 以空格为分隔符,分隔成两个

结果:
['Line1-abcdef', 'Line2-abc', 'Line4-abcd']
['Line1-abcdef', '\nLine2-abc \nLine4-abcd']

正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:

1
'a b   c'.split(' ')      # ['a', 'b', '', '', 'c']

嗯,无法识别连续的空格,用正则表达式试试:

1
2
re.split(r'\s+', 'a b   c')         # ['a', 'b', 'c']
re.split(r'[\s\,]+', 'a, b, c d') # ['a', 'b', 'c', 'd']

filter()函数

Python内建的filter()函数用于过滤序列。filter()接收一个函数和一个序列,返回一个迭代器对象。这个函数接收的序列是一个可迭代对象:可以是list,也可以是生成器。

下面是一道求素数的练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def _odd_iter():
n = 1
while True:
n = n + 2
yield n

def _not_divisible(n):
return lambda x: x % n > 0

def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it) # 返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) # filter函数接受了一个生成器_odd_iter()

这个练习用的计算素数的方法是埃氏筛法,大致原理就是_odd_iter()函数生成的第一个数,然后判断并扣除这列数中是否有可以被它整除的数,如此往复循环(而不是只到7就结束了)。

求素数

在网上搜到的求素数还有其他的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
from math import sqrt
def prime(number):
prime_nums = list()
for num in range(2, number + 1):
is_prime = True
n = int(sqrt(num))
for i in range(2, n + 1):
if num % i == 0:
is_prime = False
break
if is_prime:
prime_nums.append(num)
return prime_nums

判断一个整数N是否是素数,其实不需要对区间$[2, N-1]$所有的元素进行判断,只需要判断区
间$[2,\sqrt{N}]$里是否存在元素能够整除N即可。

反证法证明如下:

设:$i * p = N$ , 且$i, p$均大于$\sqrt{N}$,那么不妨设:$\sqrt{N}<p<i<N$

则:

与$p<i$矛盾。

今天是九月十号,我又看到了一种更简单的方法,感觉只有老油条才能做出来:

1
2
3
4
5
6
a = [i for i in range(2,101)]
b=[]
while a:
b.append(str(a[0]))
a = list(filter(lambda x: x % a[0], a))
print(' '.join(b))

闭包&装饰器

闭包

先从闭包的作用说起:

var变量写在了函数外面,是全局变量,可以被函数引用,运行不会报错:

1
2
3
4
5
var = 100    # var全局变量
def function():
res = var + 100
print(res)
function() # res = 200

var可以在函数内部被重新赋值,但是函数内部的var变量是另起炉灶,跟第一行的var = 100没有一丁点关系。当函数完成他的工作时,函数内部的var变量会被删除掉。也就是说,在这个程序中,没有变量等于200了。所以print(var)的结果是100。

1
2
3
4
5
6
var = 100    # var全局变量
def function():
var = 200
print(var)
function() # 200
print(var) # 100

在下面的例子中,会抛出错误。原因其实跟上面的是一样的,函数模块中的var = var + 100跟上面的全局变量没有一点关系。function()函数根本够不到var = 100,也不知道自己肚子里这个var已经有值了。如果把函数模块中的var = var + 100代码修改为大sb = 大sb + 100同样也会报一样错误。

1
2
3
4
5
6
7
var = 100    # var全局变量
def function():
var = var + 100
print(var)
function()
#----------
**UnboundLocalError**: local variable 'var' referenced before assignment

那我就想用一下全局变量var,那怎么办呢?可以用global var语句,让function()函数知道自己肚子里的这个var到底是谁:

1
2
3
4
5
6
var = 100    # var全局变量
def function():
global var
var = var + 100
print(var)
function()

这个例子结束了,再来看看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def maker(step):   #外部函数
num = 1 # num外部临时变量
#-------------------------
def fun(): #内部函数
nonlocal num
num = num + step
print(num)
#-------------------------
return fun

func = maker(3)
func() # 4
func() # 7
func() # 10
func() # 13

前面说到了,当函数完成他的工作时,函数内部的变量会被删除掉。但是我就是想留着他以后用,怎么办呢?闭包的作用就出现了:保存函数的状态信息,使函数的局部变量信息依然可以保存下来。

分析一下这个例子:maker(step)是外部函数,num是外部函数的临时变量。nonlocal num的作用是让内部函数认识num变量。maker(step)函数的返回值是fun,而fun只是一个函数而已,需要将maker(step)赋值给一个变量func,再通过调用func来调用fun函数。

我通过调用了四次func()来调用fun函数,得到的答案不同:1+3=4、4+3=7、7+3=10、10+3=13。你的每次调用,num都被记录了下来,而不是像之前例子中的var= 200一样,调用完就不见了。

练习:廖雪峰老师的练习再这样看来就很简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
def createCounter():
num = 0
def counter():
nonlocal num
num = num + 1
return num
return counter

count = createCounter()
print(count()) # 1
print(count()) # 2
print(count()) # 3
print(count()) # 4

如果还有不会的地方,可以参考这个博客,讲的很详细。

装饰器

理解了闭包,装饰器就很简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
def func(fun):
#-------------------------
def fun1():
print('你是', end = '')
return fun()
#-------------------------
return fun1

@func
def myprint():
print('小可爱')

myprint() # 你是小可爱

装饰器主要是用来为现有函数增加功能,而不破坏现有函数。其中@func被称作装饰器。两层的装饰器很好观察与理解,注意的是层函数返回的是函数,而内层函数返回的是函数调用:@func相当于func(myprint)()

如果装饰器需要传入参数,可就麻烦了,可以看看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def func(sex):
#-----------------------------
def fun1(fun):
#-------------------------
def fun2():
if sex =='man':
print('他是', end = '')
else:
print('她是', end = '')
return fun()
#-------------------------
return fun2
#-----------------------------
return fun1

@func(sex = 'man')
def man():
print('男的')

@func(sex = 'woman')
def woman():
print('女的')

如果需要装饰器根据装饰的函数不同进行不同的判断,就需要三层的闭包结构。看起来很复杂,其实就是多一层外面的函数来进行参数接收而已。

如果还有不会的地方,可以参考这个视频教程,讲的很详细。

致谢