Python Flask 入门
Flask
- Flask
特点
123
3
123%10=3
整数
123//100=1
‘123’+‘123’=‘123123’ str
123+123=246 int
123//10=12
12%10=2
12//10=1
range(10)
0-9
轻量级的框架 Micro Framework https://blog.igevin.info/posts/flask-startup-guideline/
python 的字符串格式化
https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896
list
list 是有序集合
- len() 获取 list 的元素个数
- append() 追加元素到末尾
- insert() 将元素插入到指定地方
- pop() 删除末尾的元素
- 可以直接赋值改某个元素的值
- 元素的数据类型可以不同
- list 的元素可以是 list
example
classmates = ['Luokey','Bob','John'] # index
len(classmates) #3
classmates[0] #'Luokey'
classmates[3] #list index out of range
#负的就是倒数第几个
classmates[-1] #'John'
classmates[-3] #'Luokey'
classmates[-4] #list index out of range
#向末尾增加元素
classmates.append('Helen')
#classmates = ['Luokey','Bob','John','Helen']
#插入到某指定位置
classmates = ['Luokey','Bob','John']
classmates.insert(1,'Linda')
#classmates = ['Luokey','Linda','Bob','John']
#删除末尾的元素
classmates = ['Luokey','Bob','John']
classmates.pop()
#classmates = ['Luokey','Bob']
#删掉指定位置的元素
classmates = ['Luokey','Bob','John']
classmates.pop(1) #根据索引删
#classmates = ['Luokey','John']
#直接赋值改元素
classmates[1] = 'hhhh'
#classmates = ['Luokey','hhhh','John']
classmates[1] = 3 #类型可以不同
#classmates = ['Luokey',3,'John']
classmates[1] = [1,2,3]
#classmates = ['Luokey',[1,2,3],'John']
classmates = ['Luokey',[1,2,3],'John']
classmates[1][1] #3 相当于二维数组
L = []
len(L) #0
tuple(元组)
tuple 有序,且初始化后不能修改
因为不可修改所以更加安全
- 不可修改
- 安全
- 定义时直接确定元素
#定义一个空 tuple
t = ()
#定义一个二元素 tuple
t = (1,2)
#定于一元素的 tuple有坑
t = (1,) # 正常的一元素 tuple
t = (1) # 括号可以表示数学公式中的小括号,有歧义
“可变的” tuple
t = ('a','b',['A','B'])
t[2][0] = 'X'
t[2][1] = 'Y'
# t = ('a','b',['X','Y'])
tuple 的内容却是变了,不变的是tuple 指向的是 list ,list 还是那个 list,只是 list 里的元素变了
tips
a = input()
# a是str
# 如果希望得到的是int
b = int(a) #前提是a可以是数字
循环
# for x in ...
# 把每个元素带入 x 然后执行语句
# 高斯心算题 1 + 2 + cdots + 100
sum = 0
for x in range(101): #要记得这边有冒号啊!
sum += x
# sum 5050
names = ['Luokey','Bob','Helen']
for x in names:
print(x)
# 输出的结果:
# Luokey
# Bob
# Helen
range()
range(5)
可以生成0-4的整数序列
continue
结束这次循环,进入下次循环
example
# 输出奇数
n = 0
while n < 10: # 记得要写冒号!!!
# python 没有 n++ 这种写法
n += 1
if n%2 ==0: # 记得要写冒号!!!
continue
print(n)
break
break
是直接结束循环
dict
字典,全称:dictionary 在其他语言叫 map ,使用 键-值(key-value)储存
- 查找速度快
- 在放进去的时候,必须根据key算出value的存放位置
- 一个key只能对应一个value
- 占用内存大,浪费内存多
- 无序
- 空间换时间
- key 不可变
- get()
- pop(key)
d = {'Luokey' : 95,'Bob' : 60}
d['Luokey'] # 95
d['Luokey'] = 100 # 覆盖
d['Helen'] # 不存在,报错
'Helen' in d # False
d.get('Helen') # None
d.get('Helen',-1) #相当于有了就覆盖,没有就创造并赋值,
# 试了下,错了回去问问
d['Helen'] # -1
# pop
d = {'Luokey' : 95,'Bob' : 60}
d.pop('Luokey') # 返回该key的value,并删除此key
set
- key 的集合,不储存 value
- 无序
- 不重复
- 可理解为高中的集合
- 交集
- 并集
- add(key)
- remove(key)
# 创建一个 set
s = set([1,2,3])
s # {1,2,3},显示的顺序不表示set有序
s.add(4) # {1,2,3,4}
s.add(4) # {1,2,3,4}重复添加无效果
s1 = set([1,2,3,3,4,2,1,1])
s1 # {1,2,3,4} 自动过滤重复的
# 交集
s1 = set{[1,2,3]}
s2 = set{[2,3,4]}
s1 & s2 # {2,3}
s1 | s2 # {1,2,3,4}
常用函数
- abs()
- max()
- 查看函数作用的help()
- 例:help(abs)
- int()
- hex() # 转成16进制
定义函数
def my_abs(x): # 记得写冒号!!!
if x >= 0: # 记得写冒号!!!
return x
else: # 记得写冒号!!!
return -x
max
tips
如果没有return
语句,函数执行完毕也返回结果,只是结果为None
return None
可写成return
空函数
# 定义一个什么时也不做的空函数
def nop():
pass
pass
可以作为占位符,如果现在没有想好怎么写函数的代码,可以先放一个pass
,让代码运行起来
- 缺少了
pass
会有语法错误
参数检查
- 参数类型
- 类型有误不会帮忙检查
- 参数个数
- 有问题会返回
TypeError
,会自动检查
- 有问题会返回
def my_abs(x): # 记得写冒号!!!
if not isinstance(x,(int, float)):
raise TypeError('bad operand type')
if x >= 0: # 记得写冒号!!!
return x
else: # 记得写冒号!!!
return -x
isinstance()
是一个内置的数据类型检查函数
多个返回值
def move(x,y,z):
return x*z, y*z
r = move(1,2,3)
print(r)
>>> (3,6)
# 返回值实际上是一个tuple
默认参数
def power(x,city='shangdong',age=19,gender='man'):
s = 1
while n > 0:
s = s*x
n -=1
return s
# power(5) 等价于 power(5,2)
# 参数按顺序填入
- 必填参数在前,默认参数在后
- 变化大的参数放前,变化小的放后,可设置为默认参数
- 当有多个默认参数时,前面有默认参数为默认状态时,要加参数名
# 前面有默认参数为默认状态时
def enroll(name, gender, gender=0, city='Shandong'):
print('name:',name)
print('gender:',gender)
print('age',age)
print('city:'city)
enroll('Luokey','A',17) # 此时默认Luokey在龙岩,年纪改为17
enroll('Bob','B',city='Beijing') # 此时默认Bob是6岁,城市改为Beijing
# 前面有默认参数为默认状态时
坑
def add_end(L=[]):
L.append('end')
return L
print(add_end())
print(add_end())
print(add_end())
>>>
['end']
['end', 'end']
['end', 'end', 'end']
Python 函数在定义的时候,默认参数L
的值就被计算出来了,因此默认参数L
也是一个变量,他指向对象[]
,每次调用该函数,如果改变了L
的内容,下次调用时,默认参数的内容就改变了,不再是定义时的[]
定义默认参数时,默认参数必须指向不变对象
修改后的代码:
def add_end(L=None):
if L is None:
L = []
L.append('end')
return L
# 这样每次得到的都是 ['end']
可变参数
可变参数就是说,传入的参数个数是可变的 (可以是0)
- 把参数作为一个 list 或 tuple 传进来
# 计算一组数的和
def calc(numbers):
sum = 0
for x in numbers:
sum += x
return sum
# 调用时要先组装出一个list 或tuple
calc([1,2,3]) # 6
calc((1,2,3)) # 6
定义可变参数,只需要在参数前面加一个*
,在函数内部,参数接收到的是一个tuple
def calc(*numbers):
sum = 0
for x in numbers: # 这里的numbers 已经是tuple了
sum += x
return sum
calc(1,2,3,4,54,5) # 69
# 在 list 前面加一个 * 可以把 list 的所有元素当可变参数传进函数
nums = [1,2,3,4,5]
calc(*nums) # 15,这里加 *
关键字参数
关键字参数允许你传入任意个含参数名的参数 (包括0个) ,这些关键字参数在函数内部自动组装成一个dict
def person(name, age, *kw):
print('name:',name,'age:',age,'other':kw)
return
# kw 就变成一个一个dict
person('Luokey',19)
# name: Luokey age: 19 other {}
person('Luokey',19,city='Longyan')
# name: Luokey age: 19 other {'city': 'Longyan'}
person('Luokey',19,city='Longyan',job='student')
# name: Luokey age: 19 other {'city': 'Longyan', 'job': 'student'}
**extra
表示把extra
这个dict的所有key-value用关键词参数传入到函数的**kw
参数,kw
获得的dict时extra
的一个拷贝,对kw
的改动不会影响函数外的extra
命名关键词参数
# 检查是否有 city 和 job 参数
def person(name, age, **kw):
if 'city' in kw:
pass
if 'job' in kw:
pass
print('name:'name,',age:'age,',other:'kw)
限制关键字参数的名字,可以用命名关键字
# 只接收 city 和 job 作为关键字参数
# 用 * 作为特殊分隔符
# 如果没有分隔符,会被识别为位置参数
def person(name, age, *, city, job):
print(name,age,city,job)
return
# 命名参数必须传入参数名
# 不传入参数名会报错
person('Luokey',19,city='Longyan',job='student')
# 命名参数可以不按顺序传入
person('Luokey',19,job='student',city='Longyan')
# 明明关键字可以有默认值,从而简化调用
def person(name, age, *, city='Longyan', job):
print(name,age,city,job)
return
# 由于命名关键字有默认值,调用时可不传入city参数
person('Luokey',19,job='student')
# 如果函数定义中已经有了一个可变参数
# 后面跟着的命名关键字参数就不需要一个特殊分隔符了
def person(name, age, *arg, city, job):
print(name,age,arg,city,job)
return
参数组合还没看…..
递归函数
# 计算阶乘
def fact(n):
if n==1:
return 1
return n*fact(n-1)
tips
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
尾递归
利用尾递归优化可以解决stack 溢出
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
# 阶乘的尾递归优化
def fact(n):
return fact_iter(n,1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num-1, num*product)
切片
L = ['Luokey', 'Helen', 'Tracy', 'Bob', 'Jack']
# 取前3个元素
# 用index索引
[L[0],L[1],L[2]] # ['Luokey', 'Helen', 'Tracy']
# 用for循环
r = []
n = 3
for i in range(n)
r.append(L[i])
# r = ['Luokey', 'Helen', 'Tracy']
# 切片(Slice)操作符
L[0:3] # ['Luokey', 'Helen', 'Tracy']
# 从索引 0 开始,直到索引 3 为止,不包括索引 3
# 第一个索引时 0 时可省略
L[:3] # ['Luokey', 'Helen', 'Tracy']
# 倒数切片
L[-2:] # ['Bob', 'Jack'] 最后两个
L[-2:-1] # ['Bob']
Slice 切片
L = list(range(100))
# L = [0,1,2,3,..., 99]
# 取前10位
L[:10] # [0,1,2,3,4,5,6,7,8,9]
# 前11-20个数
L[10:20] # [10,11,12,13,14,15,16,17,18,19]
# 前10个数,每两个取一个
L[:10:2] # [0,2,4,6,8]
# 所有数,每5个取一个
L[::5]
# [0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95]
# 复制一个 list
L[:] # [0,1,2,3,...,99]
# tuple 也是一种 list
# tuple 也可以用 Slice 操作
# 切片结果还是 tuple
(0,1,2,3,4,5,6,7)[:3] # (0,1,2)
# 字符串 'xxx' 也可一个看成是一种 list
# 字符串也可以进行切片操作
'ASDFGH'[:3] # 'ASD'
用slice
实现除去头尾空格
def trim(s):
if len(s) == 0:
print("请输入有效内容")
return s
else:
if s[:1] == ' ':
s = s[1:len(s)]
s = trim(s)
elif s[-1:] == ' ':
s = s[: len(s) - 1]
s = trim(s)
return s
Iteration 迭代
给定一个 list 或 tuple ,我们可以通过for
循环来遍历这个 list 或 tuple,这种遍历叫迭代 (Iteration)
迭代通过for ... in
来完成
# 迭代 dict
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
print(key)
# a
# b
# c
# 默认迭代的是key
# 迭代 value
d = {'a': 1, 'b': 2, 'c': 3}
for value in d.values():
print(value)
# 1
# 2
# 3
# 同时迭代 key 和 value
d = {'a': 1, 'b': 2, 'c': 3}
for k,v in d.items():
print(k,v)
# a 1
# b 2
# c 3
如何判断一个对象是否可迭代
通过collections
模块的Iterable
类型判断
from collections import Iterable
isinstance('abc', Iterable) # 判断 str 是否可迭代
# Ture
如何同时迭代索引和元素
使用Python内置的enumerate
函数,可以把list变成 索引-元素对
for i, value in enumerate(['A','B','C']):
print(i, value)
# 0 A
# 1 B
# 2 C
# for 循环中引用两个变量
for x,y in [(1,1), (2,4), (3,9)]:
print(x,y)
# 1 1
# 2 4
# 3 9
列表生成式
List Comprehensions
# 生成[1,2,3,4,5,6,7,8,9]
# 可以用 list(range(1,10))
list(range(1,9)) # [1,2,3,4,5,6,7,8,9]
# 生成 [1,4,9,16,25,36,49,64,81,100]
# 用 for 循环
L = []
for x in range(1,11)
L.append(x*x)
# L = [1,4,9,16,25,36,49,64,81,100]
# 用列表生成法
[x*x for x in range(1,11)]
# [1,4,9,16,25,36,49,64,81,100]
# 筛出偶数的平方
[x*x for x in range(1,11) if x%2 == 0]
# [4,16,36,64,100]
用两层循环生成全排列
[m+n for m in 'ABC' for n in 'XYZ']
# ['AX','AY','AZ','BX','BY','BZ','CX','CY','CZ']
generator 生成器
一边循环一边计算的机制,称为生成器(generator)
L = [x*x for x in range(10)] # list
g = (x*x for x in range(10)) # generator
# next()可以获得genertor的下一个返回值
next(g) # 0
next(g) # 1
# 当generator没有值可以抛出时
# 会有StopIteration错误
# 打印 fibonacci 数列
def fib(max):
n, a, b = 0, 0, 1
while n< max:
print(b)
a, b = b, a+b
n = n + 1
return
fib(6)
# >>>
# 1
# 1
# 2
# 3
# 5
# 8
改进为一个generator
如果一个函数定义中包含yield
关键字,这个函数就不再是一个普通的函数,而是一个generator
# generator
def fib(max):
n, a, b =0,0,1
while n<max:
yeild b
a, b =b, a+b
n = n + 1
return 'done'
- 普通函数 遇到
return
返回 - generator 遇到
yeild
返回
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
o = odd()
next(o)
# step 1
# 1
next(o)
# step 2
# 3
next(o)
# step 3
# 5
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
g = fib(6)
while True:
try:
x = next(g)
print('g=',x)
except StopIteration as e:
print("Generator return value:",e.value)
break
# g= 1
# g= 1
# g= 2
# g= 3
# g= 5
# g= 8
# Generator return value: done
迭代器
可以直接用于for
循环的对象统称为可迭代对象:Iterable
list
/tuple
/dict
/set
/dict
/str
/generator function
# 用isinstance() 判断对象是否为 Iterable 对象
from collections import Iterable
isinstance([],Iterable) # True
可以被next()
函数调用并不断返回下一个值的对象成为迭代器:Iterator
- 凡是可作用于for循环的对象都是Iterable类型;
- 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
iter()
可把list
/dict
/str
变成Iterator
isinstance([],Iterator) # False
isinstance(iter([]),Itertor) # True
Iterator
可表示成一个无限大的数据流,例如全体自然数
for
循环的本质就是不断调用next()
函数实现的
for x in [1, 2, 3, 4, 5]
pass
# 等价于
it = iter([1, 2, 3, 4, 5])
while Ture:
try:
# 获取下一个值
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
高阶函数
abs(-10) # 10
abs # <built-in function abs>
# abs(-10) 是函数调用
# abs 是函数本身
x = abs(-10) # 可以把结果赋值给变量
f = abs # 也可以把函数赋值给变量
# 变量指向这个函数
f(-10) # 10
# 函数名也是个变量
abs = 10
print(abs) # 10 此时abs指向的是10这个整数
- 变量能指向函数
- 函数的参数能接受变量
一个函数可以接收另一个函数作为参数,这种函数被称之为高阶函数
def add(x,y,f):
return f(x)+f(y)
add(5,-6,abs) # 11
map/reduce
# 有一个函数f(x),把这个函数作用在一个list上
# 假设函数f(x)=x^2
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 # map object
# 用循环实现
L = []
for n in [1,2,3,4,5,6,7,8,9]:
L.append(f(n))
print(L)
# 不能一眼看出,把f(x)作用在list的每一个元素上,并把结果生成一个新的list
map()
作为高阶函数,把运算规则抽象化
# 把所有数字变成字符串
list(map(str,[1,2,3,4,5,6,7,8,9]))
# ['1','2','3','4','5','6','7','8','9']
reduce()
把一个函数作用在一个序列[x1,x2,x3,...]
上,这个函数必须接受两个参数,reduce
把结果继续和序列的下一个元素做累计计算
from functools import reduce
# example
reduce(f,[x1,x2,x3,x4]) # 等价f(f(f(x1,x2),x3),x4)
# 利用reduce写阶乘
def multiple(a,b):
return a*b
L = list(range(6))[1:] # [1,2,3,4,5]
reduce(multiple,L) # 5!=120
# 将 [1,3,5,7] 变成整数 1357
def add(x,y):
return x*10+y
reduce(add,[1,3,5,7]) # 1357
# 写成匿名函数的形式
reduce(lambda x,y :x*10+y,[1,3,5,7])
将 map()
和recude()
结合
# str2int
from functools import reduce
DIGITS = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
def str2int(s):
def fn(x,y):
return x*10+y
def char2num(s):
return DIGTIS[s]
return reduce(fn,map(char2num,s))
# lambda 函数简化
# def char2num(s):
# return DIHTIS[s]
# def str2int(s):
# return reduce(lambda x,y:x*10+y,map(char2num,s))
filter
filter()
函数用于过滤序列
filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素
# 保留偶数
def is_even(n):
return n % 2 == 0
list(filter(is_even,[1,2,4,5,6,8,10,15]))
# [2,4,6,8,10]
删去空字符串
def not_empty(s):
return s and s.strip()
list(filter(not_empty,['A','','B',None,'C',' ']))
# ['A','B','C']
if '':
print('True')
else:
print('False')
# False
if ' ':
print('Ture')
else:
print('False')
# True
# strip()可以去除头尾的所有空格
filter()
函数返回的是一个Itertor
,也就是一个惰性序列
# 埃氏筛法
# 先构造一个从3开始的奇数序列
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) # 返回序列第一个数
# 其实我不是很理解这里为啥第一个n=2
yield n
it = filter(_not_divisible(n), it) # 构造新序列
# 打印1000以内的素数
for n in primes():
if n < 1000:
print(n)
else:
break
# 因为peimes()是无限序列,所以一定要设置退出条件
sorted
sorted([36,5,-12,9,-12])
# [-21,-12,5,9,36]
# sorted()函数也是一个高阶函数,可以接受一个key函数
sorted([36,5,-12,9,-12],key=abs)
# [5,9,-12,-21,36]
# 先对list里的元素进行key函数的处理,然后排序后再转换回处理前
# 👆就是先取绝对值,排完序后变回取绝对值之前
# 反向排序
sorted([36,5,-12,9,-12], reverse=True)
# [36, 9, 5, -12, -12]
返回函数
高阶函数除了可以接受函数作为参数,还可以把函数作为结果值返回 (其实也很好理解,函数名就是个变量
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum()
f # <function lazy_sum.<locals>.sum at 0x00000000036FCB70>
# f 是 sum()函数
f() # 25
# 调用函数 f 时,才真正计算求和的值
我们在函数lazy_sum
中又定义了函数sum
,并且内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
,相关参数和变量都保存在返回的函数中,这种称为"闭包(Closure)“的程序结构拥有极大的威力。
当我们调用lazy_sum
时,每次调用都会返回一个新的函数,即使传入相同的参数:
f1 = lazy_sum(1,3,5,7)
f2 = lazy_sum(1,3,5,7)
f1 == f2 # False
f1()
和f2()
的调用结果互不影响
闭包(Closure)
闭包返回的函数没有立刻执行,而是等到调用了f()
才执行:
def count():
fs = []
for i in range(1,4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 =count() # 此时没有调用f(),但是i已经等于了3
f1() # 9
f2() # 9
f3() # 9
匿名函数lambda
lambda x: x*x
等价于:
def f(x):
return x*x
lambda
表示匿名函数,冒号前面的x
表示函数参数
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果
匿名函数没有名字,不用担心函数名冲突,可以把匿名函数赋值给一个变量,再用此变量调用该函数:
f = lambda x: x*x
f # <function <lambda> at 0x00000000036CD2F0>
f(5) # 25
lambda
函数也可以作为返回值返回
def build(x ,y):
return lambda : x*x+y*y
f = build(1,5)
f # <function build.<locals>.<lambda> at 0x000000000379CB70>
f() # 26
build(3,4)() # 25
装饰器(Decorator)
函数对象有一个___name__
属性,可以获得函数的名字
def now():
pass
f = now
now.__name__ # 'now'
f.__name__ # 'now'
本质上decoratoe
是一个返回函数的高阶函数
# 打印日志的log函数
def log(func):
def weapper(*args,**kw):
print('call %s():'%func.__name__)
return func(*args,**kw)
return wrapper
log
是一个decorator
,接受一个函数作为参数,并返回一个函数,需要借助Python
的@
语法,把decorator
置于函数的定义处:
@log
def now():
print('2019-8-12')
now()
# call now():
# 2019-8-12
把@log()
放到now()
函数的定义处,相当于执行了语句
now = log(now)
如果decorator
本身需要传入参数,会更加麻烦:
def log(text):
def decorator(func):
def wrapper(*args,**kw):
print('%s %s():' %(text,func.__name__))
return func(*args,**kw)
return wrapper
return decorator
@log('execute')
def now():
print('2019-8-12')
now.__name__ # 'wrapper'
经过decorator
装饰之后的函数__name__
会变
有些通过签名函数来调用的代码会出错
所以需要把原始函数的__name__
等属性复制到wrapper()
函数中
不需要编写wrapper.__name__ = func.__name__
这样的代码,
Python
内置的了functools.wraps
解决这个问题
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args,**kw):
print('call %s():' % func.__name__)
return func(*args,**kw)
return wrapper
偏函数 Partial function
当函数的参数个数太多,需要简化事时,使用functools.partial
可以创建一个新的函数,这个新函数可以固定原函数的部分参数,从而调用的时候更加简单
# int() 把字符串转成整数,默认10转10
# 提供额外参数base,表示字符串内容的进制
int('123') # 123
int('12345', base=8) # 5349
int('12345', 16) # 74565
# 当有大量的二进制字符串需要转换,每次都用int(x, base=2)很麻烦
# 可以定义一个int2()函数,把默认base=2传入
def int2(x,base=2):
return int(x, base)
int2('1000000') # 64
我们可以用functools.partial
创建一个偏函数,不需要自己定义int2()
,可以用下面的代码创建一个新函数int2()
:
import functools.partial
int2 = functools.partial(int, base=2)
int2('1000000') # 64
模块 Module
- 可以避免函数名和变量名冲突
- 使用包(Package)避免模块名冲突
- 包里必须含有
__init__.py
,他的模块名和包名一致
- 包里必须含有
! 自己创建的模块名字不能与Python
自带的模块名小冲突
#!/usr/bin/env python3
可以让.py
文件直接在Unix/Linux/Mac上运行
# -*- coding: utf-8 -*-
注释表示.py
文件本身使用标准UTF-8
编码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Luo'
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()
if __name__ == '__main__'
的意思是:
当 .py
文件被直接运行时,if __name__ == '__main__'
之下的代码块将被运行;
当 .py
文件以模块形式被导入时,if __name__ == '__main__'
之下的代码块不会被运行。
作用域
正常的函数和变量名是公开的(public),可以直接被引用
类似__xxx__
这样的变量是特殊变量,可以直接被引用,但是有特殊用途,一般自己这样命名
类似_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该直接被引用
一般外部不需要引用的函数全部定义成private,只有外部需要引用的才定义为public
安装第三方库
Windows用pip
OOP
- 类(Class)
- 实例(Instance)
- 属性(Property)
- 方法(Method)
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s : %s' %(self.name, self.score))
bart = Student('Bart Simpson', 59)
bart.print_score()
class Student(object):
def __init__(self, name, score):
# 第一个参数永远是self
# 表示创建的实例本身
# 有了该方法,创建实例时必须传入匹配的参数
self.name = name
self.score = score
# 定义时 self 放在参数第一个参数
# 其他参数正常传入
def print_score(self, n):
print(' %s: %s' %(self.name, self.score*n))
bart = Student('Bart',90)
bart.print_score(3) # Bart : 270
!特殊方法__init__
前后分别有两个下划线
访问限制
bar = Student('Bart', 59)
# 外部代码可自由修改
bart.score # 59
bart.score = 99
vart.score # 99
# 让内部属性不被外部访问
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
pass
此时已经无法在外部访问实例变量.__name
和实例变量.__score
此时想要获取name
和score
可以增加方法
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
如果想要允许外部代码修改score
怎么办
可以继续增加方法:
class Student(object):
def set_name(self, name):
self.__name = name
def set_score(self, score):
self.__score = score
虽然之前的bart.score = 99
也可以修改,
但是这样可以做参数检查:
class Student(Object):
def set_score(self, score):
if 0<= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
坑!!!
# Python解释器会将私有变量__name改成_Student__name
# 所以仍可以通过_Student__name来访问私有变量
# 但是不同的解释器可能会改不同的变量名
bart = Student('Bart' ,59)
bart._Student__name # 'Bart'
bart.get_name() # 'Bart'
bart.__name = 'New Name' # 设置__name变量!
bart.__name # 'New Name'
bart.get_name() # 'Bart'
继承和多态
在我们定义一个class,从某个现有的class继承,
新的class称为子类 Subclass
而被继承的class被称为基类、父类或超类Base class/Super class
class Animal(object):
def run(self):
print('Animal is running')
编写Dog
和Cat
类的时候可以直接从Animal
类继承
class Dog(Animal):
pass
class Cat(Animal):
pass
子类会继承父类的全部功能,所以:
dog = Dog()
cat = Cat()
dog.run() # Animal is running
cat.run() # Animal is running
当然我们可以让Dog
类的run
方法被覆盖:
# 覆盖父类的run方法
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
dog = Dog()
dog.run() # Dog is running...
子类的run()
覆盖了父类的run()
,在代码运行时,总是会调用子类的run()
,这里是继承的另一个好处:多态
a = list()
b = Animal()
c = Dog()
isinstance(a, list) # True
isinstance(b, Animal) # True
isinstance(c, Dog) # True
# 除此之外
isinstance(c, Animal) # True
# Dog 可以看成 Animal
# but
isinstance(b, Dag) # False
# Animal 不可以看成 Dog
# 以上应该很好理解
def run_twice(animal()):
animal.run()
animal.run()
run_twice(Animal)
# Animal is running
# Animal is running
# 定义一个Timer类
class Timer(object):
def run(self):
print('Start...')
run_twice(Timer())
# Start...
# Start...
以上被叫做动态语言的“鸭子”类行,不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那他就可以被看作是鸭子
上面这个例子,只需要是个对象,并且有run()
方法即可
获取对象信息
# 判断对象类型
# 基本类型都可以用 type() 判断
type(123) # <class 'int'>
type('str') # <class 'str'>
type(None) # <type(None) 'NoneType'>
# 如果一个变量指向函数或者类,也可以用type() 判断
type(abs)
# <class 'builtin_function_or_method'>
type()
返回的是对应的Class类型
type(123) == type(456) # True
type(123) == int # True
type(123) == type('str') # False
判断基本类型可以直接写int
,str
等,
判断一个对象是否是函数,可以使用types
中定义的常量
import types
def fn():
pass
type(fn) == types.FunctionType # True
type(abs) == types.BuiltinFunctionType # True
type(lambda x: x) == types.LambdaType # True
type((x for x in range(10))) == types.GeneratorType # True
判断继承关系时,用type()
比较的不方便,可以用isinstance()
判断
能用type()
判断的基本类型也可用isinstance()
判断:
isinstance('s',str) # True
isinstance(b'a',bytes) # True
# 可以判断一个变量是否是某些类型中的一种
isinstance([1,2,3], (list, tuple)) # True
isinstance((1,2,3), (list, tuple)) # True
判断类型的的时候总是优先使用isinstance()
如果要获得一个对象的所有属性和方法,可以使用dir()
函数,它返回一个包含字符串的list,比如:
dir('ABC')
# ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
模块
类似__xxx__
的属性和方法在Python种都是有特殊用途的,比如__len__
返回长度。在Python中,如果你调用len()
函数试图获取一个对象的长度,实际上,在len()
内部,它自动去调用该对象的__len__()
方法,所以:
len('asd') # 3
'asd'.__len__() # 3
自己写的类,如果也想用len(myobj)
的话,就自己写一个__len__()
方法:
class MyDog(object):
def __len__(self):
return 100
dog = MyDog()
len(dog) # 100
剩下的是普通属性或方法,比如lower()
返回小写的字符串:
'ABC'.lower() # 'abc'
# 用getattr()/hasattr()/hasattr()
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x*self.x
obj = MyObject()
hasattr(obj, 'x') # 有属性 'x' 吗?# True
obj.x # 9
hasattr(obj, 'y') # 有属性 'y' 吗?# False
setattr(obj, 'y', 19) # 设置一个属性 'y'
hasattr(obj, 'y') # 有属性 'y' 吗?# True
getattr(obj, 'y') # 获取属性 'y' # 19
obj.y # 19
# 如果试图获取不存在的属性,会抛出AttributeError的错误
getattr(obj, 'z') # 获取属性'z'
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# AttributeError: 'MyObject' object has no attribute 'z'
# 可以传入一个default参数,如果属性不存在,就返回默认值
getattr(obj, 'z', 404) # 404
也可以获得对象的方法:
hasattr(obj, 'power') # 有属性 'power' 吗?# True
getattr(obj, 'power') # 获取属性 'power'
fn = getattr(obj, 'power' ) # 获取属性 'power' 并赋值到变量fn
fn #指向 obj.power
fn() # 81
一般只有在不知道对象信息的时候,我们才会取获取对象信息:
# 如果可以直接写:
sum = obj.x +obj.y
# 就不要写:
sum = getattr(obj, 'x') + getattr(obj, 'y')
# 正确用法
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
实例属性和类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性
给实例绑定属性的方法是通过实例变量,或者通过self
变量:
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score
如果Student
类本身要绑定一个属性,可以在class中定义属性,这种属性是类属性,归Student
类所有:
class Student(object):
name = 'Student'
这个属性虽然归类所有,但类的所有实例都可以访问:
class Student(object):
name = 'Student'
s = Student() # 创建实例s
print(s.name) # 打印name属性,因为实例并没有name属性,所有会继续查找class的name属性
# Student
print(Student.name) # 打印类的name属性
# Student
s.name = 'Bob' # 给实例绑定name属性
print(s.name) # 由于实例属性优先级比类属性高,因此它会屏蔽掉类的属性
# Bob
print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
# Student
del s.name # 如果删除实例的name属性
print(s.name) # 再次调用s.name,由于实力的name属性没有找到,类的name属性就显示出来了
# Student
实例属性属于各个实例所有,互不干扰;
类属性属于类所有,所有实例共享一个属性;
不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。
使用__slots__
我们定义了一个class,创建了一个class的实例后,可以对这个实例绑定任意的属性和方法,这就是动态语言的灵活性。定义一个class:
class Student(object):
pass
然后创建一个实例,给这个实例绑定一个属性
s = Student()
s.score = 97
print(s.score)
>>> 97
除此之外,我们可以给实例绑定一个方法
# 先定义一个函数作为实例对象
def set_age(self, age):
aelf.age = age
# 需要用到某个模块
from types import MethodType
s.set_age = MethodType(set_age, s) # 绑定方法
s.set_age(25) # 调用实例方法
s.age # 25
但是绑定方法是独立的,对另外的实例是不起作用的:
s2 = Student()
s2.set_age(25) # 此时会报AttributeError错误
# 因为s2没有这个方法
如果要对创建的实例都绑定一个方法,可以直接对class去绑定方法:
def set_score(self, score):
self.score = score
Student.set_score = set_score
s.set_score(100)
s.score # 100
如果要限制实例的属性怎么办?比如只允许添加age
和name
变量
可以在定义class的时候,定义一个特殊的变量__slots__
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
!__slots__
只对当前类的实例起作用,对其继承的子类是不起作用的
除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
和父类的__slots__
使用@property
在前面学了为了数据的安全,可以在类定义时设置get()
和set()
方法,这种方法还可以对数据进行检查
class Student(object):
def get_score(self):
return self._score
def set_score(self, score):
if not isinstance(score, int):
raise ValueError('score must be an integer')
if score < or score >100:
raise ValueError('score must between 0 ~ 100')
self._score = score
s = Student()
s.set_score(60)
s.get_score() # 60
s.set_score(9999) # ValueError : score must between 0 ~ 100
上面的调用方法略显复杂,不如直接用属性这么直接简单
所以我们需要既能检查参数,又可以用类似属性这样简单的方式来访问类的变量
Python内置的@property
装饰器就是负责把一个方法变成属性调用:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100')
self._score = value
@property
的实现比较复杂,我们先考察如何使用。把一个getter
方法变成属性,只需要加上@property
就可以了,此时@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
s = Student()
s.score = 60 # 实际转化为s.set_score(60)
s.score # 60 实际转化为s.get_score()
s.score = 999 # ValueError : score must between 0 ~ 100
可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015-self._birth
上面的birth
是可读写属性,而age
就是一个只读属性,因为age
可以根据当前birth
和当前时间计算出来
多重继承
简单来说就是可以继承多个父类,避免分类方式增加时出现指数型增长
example:
按照哺乳动物分类
- Animal
- Mammal(哺乳动物)
- Dog
- Bat
- Bird(鸟类)
- Parrot(鹦鹉)
- Ostrich(鸵鸟) 按照能跑和能飞来分类
- Mammal(哺乳动物)
- Animal
- Runnable
- Dog
- Ostrich
- Flyable
- Parrot
- Bat 如果要把上面的两种分类都包含进来,我们需要设计更多的层次
- Runnable
- Animal
- Mammal
- MRun
- Dog
- MFly
- Bat
- MRun
- Bird
- BRun
- Ostrich
- BFly
- Parrot
- BRun
- Mammal
如果还要增加其他分类,类的数量会呈指数增长,很明显这样问题很大,正确的做法是采用多重继承
class Animal(object):
pass
# 大类
class Mammal(Animal):
pass
class Bird(Animal):
pass
# 各种动物
class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
现在我们要给动物加上Runnable
和Flyable
的功能,只需要定义好Runnable
和Flyable
的类:
class Runnable(object):
def run(self):
print('Running...')
class Flyable(object):
def fly(self):
print('Flying...')
对于需要Runnable
功能的动物,就多继承一个Runnable
:
class Dog(Mammal, Runnable):
pass
对于需要Flyable
功能的动,就多继承一个Flyable
,例如Bat
:
class Bat(Mammal, Flyable):
pass
通过多重继承,一个子类就可以同时获得父类的所有功能
在设计类的继承关系时,通常,诛仙都是单一继承下来的,例如,Ostrich
继承自Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让OStrich
除了继承自Bird
外,再同时继承Runnable
。这种设计通常称之为MixIn
。
为了更好地看出继承关系,我们把Runnable
和Flyable
改为RunnableMixIn
和FlyableMixIn
。类似的,你还可以定义出肉食动物CarnivorousMixIn
和植食动物HerbivoresMixIn
,让某个动物同时拥有好几个MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的继承关系。
Python自带了TCPServer
和UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn
和ThreadingMixIn
提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPSercer, ForkingMixIn):
pass
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
定制类
看到类似__slots__
这种形如__xxx__
的变量或者函数名要想到,这些是在Python中是有特殊作用的。
前面已经学过了__slots__
我们已经知道怎么用了,__len()__
方法我们也是知道为了能让class作用于len()
函数。
class Student(object):
def __init__(self, name):
self.name = name
print(Student('LuoKey'))
# >>> <__main__.Student object at 0x0000000003CA9F60>
这样打印出来不好看?只需要定义好__str__()
方法,返回一个字符串就可以了:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name :%s)' %self.name
print(Student('Luokey'))
# >>> Student object (name :Luokey)
# but
s # <__main__.Student object at 0x00000000024FF6D8>
上面s显示变量调用的不是__str__()
,而是__repr__()
,两者的区别是___str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。
解决的办法是再定义一个__repr__()
。但是通常__str__()
和__repr__()
代码都是一样的,所以,有个偷懒的写法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name = %s)' %s self.name
__repr__ = __str__
__iter__
如果一个类想被用于for ... in
循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,知道遇到StopIteration
错误时退出循环。
# 写一个Fib类
class Fib(object):
def __init__(self):
self.a, slef,b = 0, 1 # 初始化两个计数器a, b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b #计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration
return self.a # 返回下一个值
# 测试:
for n in Fib():
print(n)
# 1
# 1
# 2
# 3
# ...
# 46368
# 75025
# 试图获取第5个元素
Fib()[5]
# TypeError:'Fib' object does not support indexing
虽然像list,但是终究不能当list用,我们需要实现__getitem__()
方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
f = Fib()
f[0] # 1
f[100] # 573147844013817084101
list有个神奇的切片方法:
list(range(100))[5:10]
# [5,6,7,8,9]
但是Fib会报错,因为__getitem__()
传入的可能是一个int,也可能是一个切片对象slice
,所以要判断:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n is an index
a, b = 1, 1
for x in range(n):
a, b = b, a+b
return a
if isinstance(n, slice): # n is a slice
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a+b
return L
f = Fib()
f[:5] # [1, 1, 2, 3, 5]
但是没有对step参数做处理,也没有对附属作处理,所以要正确实现一个__getitem__()
还有很多工作要做
__getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。比如:
class Student(object):
def __init__(self):
self.name = 'Michael'
s = Student()
print(s.name)
# >>> Miclael
print(s.score)
# >>> AttributeError: 'Student' object has no attribute 'score'
我们可以写一个__getattr__()
方法,动态返回一个属性,如下:
class Student(object):
def __init__(self):
self.name = 'Luokey'
def __getattr__(self, attr):
if 'attr' == 'score'
return 99
当调用不存在的属性时,Python解释器就会试图调用__getattr__(self, attr)
来尝试获得属性:
s = Student()
s.name # 'Luokey'
s.score # 99
当然也可以返回函数:
class Student(object):
def __getattr(self, attr):
if attr == 'age':
return lambda: 25
# 调用时会有区别
s.age() # 25
只有在没有找到属性的情况下,才会调用__getattr__
,已有的属性不会在__getattr__
中查找,没有的属性,且在__getattr__
中也没有,会默认返回None
,可以修改:
class Student(object):
def __getattr__(self, attr):
if attr == 'age':
return 25
elif attr == 'score':
return 147
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
__call__
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()
来调用。能不能直接在实例本身上调用呢?可以,需要__call__()
方法:
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.'%self.name)
s = Student('Luokey')
s()
# >>> My name is Luokey
我们要怎么判断一个对象是否可以被调用呢,能被调用的对象是一个Callable
对象:
callable(s) # True
callable(Student) # True
callable(max) # True
callable(None) # False
callable('str') # False
callable([1, 2, 3]) # False
枚举类
当我们定义常量的时候,一个办法是用大写变量通过证整数来定义:
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12
虽然简单,但类型是int
,并且实际上仍然是变量。
更好的方法是为这样的枚举类型定义一个class类型,每个场地昂都是class的唯一一个实例。Python提供了Enum
类来实现:
from enum import Enum
Month = Enum('Month',('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'))
for name, member in Month.__members__.items():
print(name,'=>',member,',',member.value)
# >>>
# Jan => Month.Jan , 1
# Feb => Month.Feb , 2
# Mar => Month.Mar , 3
# Apr => Month.Apr , 4
# May => Month.May , 5
# Jun => Month.Jun , 6
# Jul => Month.Jul , 7
# Aug => Month.Aug , 8
# Sep => Month.Sep , 9
# Oct => Month.Oct , 10
# Nov => Month.Nov , 11
# Dec => Month.Dec , 12
待解决问题
- 不可变对象问题
https://www.liaoxuefeng.com/wiki/1016959663602400/1017104324028448
- dict.get(str, value)错误问题
- 尾递归优化不太清楚
- 汉诺塔问题不熟
- List Comprehensions 的条件使用环境
- 模块部分