Python3 学习笔记 - 钢钢更新
@(我的笔记本)[Python]
[toc]
List
定义list
1 | list=['a','b','c'] |
获取list长度
1 | len(list) |
3
获取list指定位置的值
1 | list[0] |
‘a’
1 | list[2] |
‘c’
1 | list[3] |
Traceback (most recent call last):
File ““, line 1, in
IndexError: list index out of range
1 | list[-1] |
‘c’
往list中插入新值(默认插入到最后)
1 | list.append('d') |
[‘a’, ‘b’, ‘c’, ‘d’]
往list中特定位置插入值
1 | list.insert(1,'e') |
[‘a’, ‘e’, ‘b’, ‘c’, ‘d’]
从list中删除值(默认从最后删除)
1 | list.pop() |
‘d’
[‘a’, ‘e’, ‘b’, ‘c’]
往list中特定位置删除值
1 | list.pop(2) |
‘b’
[‘a’, ‘e’, ‘c’]
覆盖list中的特定位置的值
1 | list[1]='f' |
[‘a’, ‘f’, ‘c’]
Tuple
定义tuple
1 | l=(1) |
1
1 | l=(1,) |
(1,)
tuple里嵌套list
1 | l=('a','b',['A','B'],'c') |
4
可以把tuple当做多维数组使用
1 | l[2][1] |
‘B’
1 | l |
(‘a’, ‘b’, [‘A’, ‘B’], ‘c’)
1 | l[2][1]='X' |
(‘a’, ‘b’, [‘A’, ‘X’], ‘c’)
if 条件判断(别忘了写冒号)
1 | height=1.75 |
分隔字符串
1 | words = text.split() |
循环
for循环
1 | l=['a','b','c'] |
range用法
1 | range(101) |
1 | for i in range(5,0,-1): |
5
4
3
2
1
while循环(break提前退出)
1 | n=1 |
while循环(continue跳过此次循环)
1 | n=0 |
Dict
key-value mapping表:空间换时间1
2d={'Kenny': 34, 'Bob': 40, 'Steven': 39}
d['Kenny']
34
1 | d['Steven']=40 |
{‘Steven’: 40, ‘Kenny’: 34, ‘Bob’: 40}
1 | 'kenny' in d |
False
1 | 'Kenny' in d |
True
获取dict元素值
1 | d.get('Kenny') |
39
删除dict元素值
1 | d.pop('Steven') |
40
{‘Kenny’: 34, ‘Bob’: 40}
更新 (插入) dict元素值
1 | d['Tao']=33 |
{‘Kenny’: 34, ‘Bob’: 40, ’Tao’: 33}
Set集合(无序,不重复)
1 | s=set([1,1,2,2,3,3,3,4]) |
{1, 2, 3, 4}
新增set值
1 | s.add(5) |
{1, 2, 3, 4, 5}
删除set值
1 | s.remove(3) |
{1, 2, 4, 5}
函数
1 | def my_abs(x): |
10
函数返回多个值
1 | import math |
151.96152422706632 70.0
在函数内修改全局变量
1 | count = 5 |
13
1 | count |
13
lambda匿名函数
1 | def ds(x): |
11
1 | lambda x:2*x+1 |
11
1 | g = lambda x, y : x + y |
7
定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上面的例子,我们可以用None这个不变对象来实现:
1 | def add_end(L=None): |
[‘END’]
我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。
要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:1
2
3
4
5def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
但是调用的时候,需要先组装出一个list或tuple:1
calc([1, 2, 3])
14
1 | calc((1, 3, 5, 7)) |
84
如果利用可变参数,调用函数的方式可以简化成这样:1
calc(1, 2, 3)
14
1 | calc(1, 3, 5, 7) |
84
所以,我们把函数的参数改为可变参数:1
2
3
4
5def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*
号。在函数内部,参数numbers接收到的是一个tuple。因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:
1 | calc(1, 2) |
5
1 | calc() |
0
如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
1 | nums = [1, 2, 3] |
14
这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*
号,把list或tuple的元素变成可变参数传进去。1
2nums = [1, 2, 3]
calc(*nums)
14
*nums
表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
小结
- Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
- 默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
- 要注意定义可变参数和关键字参数的语法;
- *args是可变参数,args接收的是一个tuple;
- 可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过args传入:func((1, 2, 3));
- **kw是关键字参数,kw接收的是一个dict。
- 关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过kw传入:func({‘a’: 1, ‘b’: 2})。
- 使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
- 命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
- 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符
*
,否则定义的将是位置参数。
递归函数
1 | def fact(n): |
1
1 | fact(5) |
120
上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:1
2
3
4
5
6def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
小结
- 使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
- 针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
- Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
用循环代替上面的递归
1 | def fact(n): |
list切片功能(数字代表的是索引位置)
1 | L=['kenny','bob','steven','tao','lawrence'] |
[‘kenny’, ‘bob’]
1 | L[:2] |
[‘kenny’, ‘bob’]
1 | L[2:2] |
[]
1 | L[2:4] |
[‘steven’, ‘tao’]
1 | L[-1:] |
[‘lawrence’]
1 | L[-1:-2] |
[]
1 | L[-2:-1] |
[‘tao’]
1 | L=list(range(100)) |
可以通过切片轻松取出某一段数列,比如:
前10个数
1 | L[:10] |
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
后10个数
1 | L[-10:] |
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
前11-20个数
1 | L[10:20] |
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
前10个数,每两个取一个
1 | L[:10:2] |
[0, 2, 4, 6, 8]
所有数,每5个取一个
1 | L[::5] |
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:1
(0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)
字符串’xxx’也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:1
'ABCDEFG'[:3]
‘ABC’
1 | 'ABCDEFG'[::2] |
‘ACEG’
在很多编程语言中,针对字符串提供了很多各种截取函数(例如,substring),其实目的就是对字符串切片。Python没有针对字符串的截取函数,只需要切片一个操作就可以完成,非常简单。
迭代(for循环)
1 | d = {'a': 1, 'b': 2, 'c': 3} |
a
c
b
1 | for value in d.values(): |
1
3
2
1 | for k,v in d.items(): |
(‘a’, 1)
(‘c’, 3)
(‘b’, 2)
列表生成式
1 | list(range(1, 11)) |
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
但如果要生成[1x1, 2x2, 3x3, …, 10x10]怎么做?方法一是循环:1
2
3
4
5L = []
for x in range(1, 11):
L.append(x * x)
L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:1
[x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:1
[x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
还可以使用两层循环,可以生成全排列:1
[m + n for m in 'ABC' for n in 'XYZ']
[‘AX’, ‘AY’, ‘AZ’, ‘BX’, ‘BY’, ‘BZ’, ‘CX’, ‘CY’, ‘CZ’]
把一个list中所有的字符串变成小写:1
2L = ['Hello', 'World', 'IBM', 'Apple']
[s.lower() for s in L]
[‘hello’, ‘world’, ‘ibm’, ‘apple’]
生成器generator1
2
3g=(x*x for x in range(10))
for n in g:
print (n)
0
1
4
9
16
25
36
49
64
81
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:1
2
3
4
5
6
7def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
map/reduce
map用法
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。1
2
3
4
5
6def f(x):
return x*x
r=map(f,[1,2,3,4,5,6,7,8,9])
r
[1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce的用法
reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算1
2
3
4
5from functools import reduce
def add(x,y):
return x+y
reduce(add, [1,3,5,7,9])
25
体会filter和map的区别,filter是过滤结果为真的数据,而map则是迭代调用1
list(filter(lambda x:x*2,range(10)))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
1 | list(map(lambda x:x*2,range(10))) |
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
filter过滤
Python内建的filter()函数用于过滤序列。
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。1
2
3
4def is_odd(n):
return n%2==1
list(filter(is_odd, [1,2,4,5,6,9,10,15]))
[1, 5, 9, 15]
sorted排序
Python内置的sorted()函数就可以对list进行排序1
sorted([36,5,-12,9,-21])
[-21, -12, 5, 9, 36]
1 | sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) |
[‘Zoo’, ‘Credit’, ‘bob’, ‘about’]
函数作为返回值1
2
3
4
5
6
7def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
1 | f = lazy_sum(1, 3, 5, 7, 9) |
<function lazy_sum.
.sum at 0x101c6ed90>
调用函数f时,才真正计算求和的结果:1
f()
25
装饰器decorator
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。1
2
3
4
5def now():
print('2015-3-25')
f = now
f()
2015-3-25
函数对象有一个name属性,可以拿到函数的名字:1
now.__name__
‘now’
1 | f.__name__ |
‘now’
现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:1
2
3
4
5def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:1
2
3
def now():
print('2015-3-25')
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:1
now()
call now():
2015-3-25
functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:1
2
3import functools
int2 = functools.partial(int, base=2)
int2('1000000')
64
1 | int2('1010101') |
85
使用模块
Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。
我们以内建的sys模块为例,编写一个hello的模块:
1 | #!/usr/bin/env python3 |
- 第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
- 第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
- 第6行使用author变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
后面开始就是真正的代码部分。
你可能注意到了,使用sys模块的第一步,就是导入该模块:1
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]
。
最后,注意到这两行代码:1
2if __name__=='__main__':
test()
当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__
置为__main__
,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
我们可以用命令行运行hello.py
看看效果:1
$ python3 hello.py
Hello, world!
1 | $ python hello.py Michael |
Hello, Michael!
如果启动Python交互环境,再导入hello模块:1
$ 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.
1 | import hello |
导入时,没有打印Hello, word!,因为没有执行test()函数。
调用hello.test()时,才能打印出Hello, word!:1
hello.test()
Hello, world!
try … except 捕获错误
使用try…except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用foo(),foo()调用bar(),结果bar()出错了,这时,只要main()捕获到了,就可以处理:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')
try:
sum = 1 + 'a'
f = open('xyz.txt')
print(f.read())
f.close()
except OSError as reason:
print("文件打开出错。\n", str(reason))
except TypeError as reason:
print("类型出错。", str(reason))
finally:
f.close()
抛出错误
因为错误是class,捕获一个错误就是捕获到该class的一个实例。因此,错误并不是凭空产生的,而是有意创建并抛出的。Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。
如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例:1
2
3
4
5
6
7
8
9# err_raise.py
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
日期和时间
获取当前日期和时间:
1 | from datetime import datetime |
2015-05-18 16:28:07.198690
1 | print(type(now)) |
获取指定日期和时间
要指定某个日期和时间,我们直接用参数构造一个datetime:1
2
3from datetime import datetime
dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
print(dt)
2015-04-19 12:20:00
datetime转换为timestamp
把一个datetime类型转换为timestamp只需要简单调用timestamp()方法:1
2
3from datetime import datetime
dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
dt.timestamp() # 把datetime转换为timestamp
1429417200.0
timestamp转换为datetime
要把timestamp转换为datetime,使用datetime提供的fromtimestamp()方法:1
2
3from datetime import datetime
t = 1429417200.0
print(datetime.fromtimestamp(t))
2015-04-19 12:20:00
str转换为datetime
很多时候,用户输入的日期和时间是字符串,要处理日期和时间,首先必须把str转换为datetime。转换方法是通过datetime.strptime()实现,需要一个日期和时间的格式化字符串:1
2
3from datetime import datetime
cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')
print(cday)
2015-06-01 18:19:59
datetime转换为str
如果已经有了datetime对象,要把它格式化为字符串显示给用户,就需要转换为str,转换方法是通过strftime()实现的,同样需要一个日期和时间的格式化字符串:1
2
3from datetime import datetime
now = datetime.now()
print(now.strftime('%a, %b %d %H:%M'))
Mon, May 05 16:28
base64
Python内置的base64可以直接进行base64的编解码:1
2import base64
base64.b64encode(b'binary\x00string')
b’YmluYXJ5AHN0cmluZw==’
1 | base64.b64decode(b'YmluYXJ5AHN0cmluZw==') |
b’binary\x00string’
由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种”url safe”的base64编码,其实就是把字符+和/分别变成-和_:
1 | base64.b64encode(b'i\xb7\x1d\xfb\xef\xff') |
b’abcd++//‘
1 | base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff') |
b’abcd–__
1 | base64.urlsafe_b64decode('abcd--__') |
b’i\xb7\x1d\xfb\xef\xff’
文件
打开文件
1 | f = open('/u01/1.txt') |
重置游标
1 | f.seek(0,0) |
打印文件内容
1 | for line in f: |
数据库
连接MySQL
1 | import pymysql |