P014_01_python生成器进阶及生成器表达式、列表推导式

生成器回顾

双下方法 : 很少直接调用的方法。一般情况下,是通过其他语法触发的

next()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#娃哈哈%i
def wahaha():
for i in range(2000000):
yield '娃哈哈%s'%i

# wahaha是一个生成器函数
g = wahaha()

#我们获取它的内容明显不该 这样
# g.__next__()

print(next(g))
print(next(g))
print(next(g))
print(next(g))

send()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def generator():
print(123)
content = yield 1
print('=======',content)
print(456)
yield 2

# 返回生成器对象
g = generator()

# 返回第一个yield 后的值 1
ret = g.__next__()
print('***',ret)

# 给上一个 yield传递一个值
ret = g.send('hello') #send的效果和next一样
print('***',ret)

结果:
123
*** 1
======= hello
456
*** 2

send和 next的区别

  • 相同点:都可以让生成器向下走一步
  • 异同点: send 可以给一个值,这个值作为上一个 yield 的整体的结果返回

使用send的注意事项:

  1. 第一次使用生成器的时候,必须要用next获取下一个值(因为send是给上一个yield 设置结果,而第一次的时候没有上一个yield)
  2. 最后一个yield不能接受外部的值(因为后面没有 yield 了)

为啥第一次不能用send()

因为会报错!!!详情请看 为什么python的yield第一次不能用send发送数据?

经典练习

求移动平均值

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
'''
10 => 10
10 20 => 15
10 20 30 => 20
10 20 30 10 => 17.5
avg =sum/count
'''
def xxx():
sum = 0
count = 0
num = 0
avg = 0
while True:
# num = yield # 接受一个10
sum += num # 10
count += 1 # 1
avg = sum/count
num = yield avg

avg_g = xxx()

avg_g.__next__()
avg_g.send(10)
avg_g.send(20)
avg_g.send(30)
avg_g.send(10)

结果:
0.0
5.0
10.0
15.0

移动平均值优化

需求就是不要第一次调用next(),因为对使用者没有意义

提示:装饰器

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
def init(func):
def inner(*args,**kwargs):
g = func(*args,**kwargs)
g.__next__()
return g
return inner

@init
def xxx():
sum = 0
count = 0
num = 0
avg = 0
while True:
# num = yield # 接受一个10
sum += num # 10
count += 1 # 1
avg = sum/count
num = yield avg

avg_g = xxx()

res = avg_g.send(10)
print(res)
res = avg_g.send(20)
print(res)
res = avg_g.send(30)
print(res)

这个效果就是 预激活装饰器

python3新增知识点

先看如下例子:把a,b里面的字符一个一个的返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def xxx():
a = 'abc'
b = '123'

for i in a:
yield i
for i in b:
yield i

g = xxx()

for i in g:
print(i)


结果:
a
b
c
1
2
3

添加 yield from

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def xxx():
a = 'abc'
b = '123'

yield from a
yield from b

g = xxx()

for i in g:
print(i)

结果:
a
b
c
1
2
3

yield from 的作用就是:

从一个容器类型里取得值不需要一个一个取而是集体返回

各种表达式推到式

列表推到式

10个鸡蛋的列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'''
eggList = []
for i in range(10):
eggList.append("鸡蛋%s"%i)

print(eggList)
'''

# 一句话代替上面的效果
eggList2 = ["鸡蛋%s"%i for i in range(10)] #列表推到式
print(eggList2)


[i for i in range(10)] # [0,1,2,3,4,5,6,7,8,9]

[i*i for i in range(10)] # [0,1,4,9,16,25,36,49,64,81]

生成器表达式

1
2
g = ( i for i in range(10) )
print(g) #注意这是生成器

列表推导式和生成器表达式的区别

  • 括号不一样
  • 返回值不同,生成器表达式几乎不占用内存

一道面试题

1
2
3
4
5
g = ( i for i in range(10) )

发生了什么:
1.跟函数定义一样什么也不执行
2.直到 g.__next__() 时,里面的循环才走一次

各种推导式

公式

1
2
[每一个元素 for 元素 in 可迭代数据类型]
[满足条件元素 for 元素 in 可迭代数据类型 if 元素相关条件判断]

30以内被3整除的数

1
[i for i in range(30) if i%3 == 0]

30以内被3整除的数的平方

1
[i*i for i in range(30) if i%3 == 0]

找到多重嵌套列表内名字包含e字母 超过两个的

1
2
3
4
5
6
names = [
['ae','ee','eve','even'],
['xx','xe','egg','ele']
]

[name for lst in names for name in lst if name.count('e')==2 ]
字典推导式

将一个字典的key和value颠倒

1
2
3
xx = {'a':10,'b':22}

xx2 = { xx[k]:k for k in xx}
集合推导式

计算列表中每个值的平方,在带去重功能

1
2
3
squared = {x**2 for x in [1, -1, 2]}
print(squared)
# set([1, 4])

面试题详解

面试题1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def demo():
for i in range(4):
yield i

g = demo()
g1 = ( i for i in g)
g2 = ( i for i in g1)

print(list(g1))
print(list(g2))

结果:
[0,1,2,3]
[]

前5行啥都没干
直到print(list(g1)) 时才从g中生成到g1 ,但是g1是一个生成器 在list(g1)时把g1里的值已经取走了

所以到print(list(g2))的时候 是个空列表

面试题2

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
def add(n,i):
return n+i

def test():
for i in range(4):
yield i

g = test()
for n in [1,10,5]:
g =( add(n,i) for i in g)

print(list(g))

解析:
第一步
n = 1
g= (add(n,i) for i in g)
n = 10
g= (add(n,i) for i in g)
n = 5
g= (add(n,i) for i in g)
第二步--》换元法
n = 1
g= (add(n,i) for i in test())
n = 10
g= (add(n,i) for i in (add(n,i) for i in test()))
第三步--》换元法
n = 5
g= (add(n,i) for i in (add(n,i) for i in (add(n,i) for i in test())) )
第四步带入 n =5 0,1,2,3
(add(n,i) for i in (add(n,i) for i in (add(n,i) for i in (0,1,2,3))))

n = 5
(add(n,i) for i in (add(n,i) for i in (5,6,7,8)))
n = 5
(add(n,i) for i in (10,11,12,13))

(15,16,17,18)

P013_01_python迭代器

迭代器初识

1
2
3
4
5
6
7
8
9
a = [1,2,3]
# 索引
# 循环for

for i in a:
print(i)

for k in dic:
pass
  • list
  • dic
  • str
  • set
  • tuple
  • f = open()文件
  • range()
  • enumerate

迭代器的概念

先来一个小知识点,查看一个对象可用的方法 dir

dir(对象)

1
2
3
4
print(dir([])) #告诉我列表拥有的方法
print(dir({})) #告诉我字典拥有的方法
print(dir('')) #告诉我字符串拥有的方法
print(dir(range(10))) #告诉我range(10)拥有的方法

我们知道数组相加如下

1
2
[1] + [2] # [1,2]
实际上就是调用 [1].__add__([2])

找这些能被for循环使用的方法的交集

1
2
3
# 取这些可被for循环的数据类型的交集
ret = set(dir([]))&set(dir({}))&set(dir(''))&set(dir(range(10)))
print(ret) #iterable

只要能被for循环的数据类型,就一定有iter方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
print('__iter__' in dir(int))
print('__iter__' in dir(bool))
print('__iter__' in dir(list))
print('__iter__' in dir(dict))
print('__iter__' in dir(set))
print('__iter__' in dir(tuple))
print('__iter__' in dir(enumerate([])))
print('__iter__' in dir(range(1)))

结果:
False
False
True
True
True
True
True
True

一个列表执行了iter()之后的返回值就是一个迭代器

1
2
3
4
print(dir([]))
print(dir([].__iter__()))
print(set(dir([].__iter__())) - set(dir([])))
print([1,'a','bbb'].__iter__().__length_hint__()) #元素个数

再看双下划线 next方法

1
2
3
4
5
6
7
8
l = [1,2,3]
iterator = l.__iter__()
print(iterator.__next__()) # 1
print(iterator.__next__()) # 2
print(iterator.__next__()) # 3
print(iterator.__next__()) # 报错

[].__iter__() 迭代器 -- > __next__ #通过next就可以从迭代器中一个一个的取值

迭代器要点:

1
2
3
Iterable  可迭代的    -- > __iter__  #只要含有__iter__方法的都是可迭代的

只要含有__iter__方法的都是可迭代的 —— 可迭代协议
1
2
3
l = [1,2,3,4]
for i in l.__iter__():
print(i)

迭代器的概念

迭代器协议 —— 内部含有nextiter方法的就是迭代器

迭代器协议和可迭代协议

1
2
3
4
5
可以被for循环的都是可迭代的
可迭代的内部都有__iter__方法
只要是迭代器 一定可迭代
可迭代的.__iter__()方法就可以得到一个迭代器
迭代器中的__next__()方法可以一个一个的获取值
1
2
3
4
5
6
for循环其实就是在使用迭代器
iterator
可迭代对象
直接给你内存地址
print([].__iter__())
print(range(10))

for循环的本质

只有 是可迭代对象的时候 才能用for
当我们遇到一个新的变量,不确定能不能for循环的时候,就判断它是否可迭代

1
2
3
4
5
6
for i in l:
pass

# for循环的本质就是迭代器
#iterator = l.__iter__()
#iterator.__next__()

迭代器的好处:

  • 从容器类型中一个一个的取值,会把所有的值都取到。
  • 节省内存空间
    迭代器并不会在内存中再占用一大块内存,
    而是随着循环 每次生成一个
    每次next每次给我一个
1
2
3
range(10000000000000000)  # 并不是立即生成,否则很浪费内存空间

list(range(10000000000000000)) # 估计内存炸了,把迭代器转为列表

生成器

本质是个迭代器

生成器函数

1.我们先实现一个普通方法

1
2
3
4
5
6
7
8
9
10
def generator():
print(1)
return 'a'

xxx = generator()
print(xxx)

结果
1
a

2.yield关键字

将刚刚的return 换成 yield

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def generator():
print(1)
yield 'a'

xxx = generator()
print(xxx)

结果:
<generator object generator at 0x000000000238C518>

------------------------------------------------------------
print(xxx.__next__())
结果:
1
a

#### 什么是生成器

  • 只要含有yield关键字的函数都是生成器函数
  • yield不能和return共用而且需要写在函数内部
  • 函数内部有yield关键字,这个函数执行返回的结果就是生成器,并且不会执行函数内部代码

生成器函数 : 执行之后会得到一个生成器作为返回值

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
def xxx():
print('====>a')
yield 1
print('====>b')
yield 2
print('====>c')
yield 3
print('====>end')

g=xxx()

print(g.__next__()) # 返回第一次yield关键字后面的值
print(g.__next__()) # 返回第二次yield关键字后面的值
print(g.__next__()) # 返回第三次yield关键字后面的值
print(g.__next__()) # 报错

====>a
1
====>b
2
====>c
3
====>end
Traceback (most recent call last):
File "D:/python_code/py20180718/001.py", line 18, in <module>
print(g.__next__())
StopIteration

需求打印 200,0000次 哇哈哈1==》哇哈哈2000000,而我现在只要50个

1
2
3
4
5
6
7
8
9
10
11
12
#娃哈哈%i
def wahaha():
for i in range(2000000):
yield '娃哈哈%s'%i

g = wahaha()
count = 0
for i in g:
count +=1
if count > 50:
break
print('```````````'+i+'`````````````')

生成器真的省内存吗?

碰巧前面我们刚学过如何测试效率和装饰器,正好拿来练习!!!

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
import time

def test(func):
def inner(*args,**kwargs):
start_time = time.time()
res = func(*args,**kwargs)
end_time = time.time()
print("测试效率:", end_time - start_time)
return res
return inner

@test
def getList(n):
l1 = list(range(n))
@test
def getGenerator(n):
for i in range(n):
yield i


getList(100000)
getGenerator(100000)


结果:
测试效率: 0.004000663757324219
测试效率: 0.0

分析下就可以知道,列表是将0-99999都生成后放进列表里了,所以用时比较多。
而生成器只是封装了算法,每次调用在去调用算法,这样做就可以做到节省内存了。

P012_01_python装饰器进阶

先来看看一些题外话(一个函数的相关信息)

  • name 函数名
  • doc 函数文档
1
2
3
4
5
6
7
8
def xxx():
'''
一个打印 哈哈哈 的函数
'''
print('哈哈哈')

print(xxx.__name__) # xxx
print(xxx.__doc__) # 一个打印 哈哈哈 的函数

那要是被装饰器的函数呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def wrapper(func):
def inner(*args,**kwargs):
res = func(*args,**kwargs)
return res
return inner

@wrapper
def xxx2():
'''
测试xxx2
'''
print('HAHA')

print(xxx2.__name__) # inner
print(xxx2.__doc__) # None

wraps模块

解决查看函数相关信息的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# step1引入wraps模块
from functools import wraps

def wrapper(func):
# step2 使用这个模块的装饰器函数
@wraps(func)
def inner(*args,**kwargs):
res = func(*args,**kwargs)
return res
return inner

@wrapper
def xxx2():
'''
测试xxx2
'''
print('HAHA')

print(xxx2.__name__) # xxx2
print(xxx2.__doc__) # 测试xxx2

带参数装饰器

通过布尔值来决定「是否」启动装饰器

首先装饰器的原理是闭包

控制装饰器是否开启的原理就是:在原装饰器的基础上再一次的闭包

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
import time
FLAGE = False # 决定装饰器是否开启的布尔值
def timmer_out(flag): #接受参数的装饰器用来处理是否开启装饰器
def timmer(func):
def inner(*args,**kwargs):
if flag:
start = time.time()
ret = func(*args,**kwargs)
end = time.time()
print(end-start)
return ret
else:
ret = func(*args, **kwargs)
return ret
return inner
return timmer

# timmer = timmer_out(FLAGE)
@timmer_out(FLAGE) #wahaha = timmer(wahaha)
def wahaha():
time.sleep(0.1)
print('wahahahahahaha')

@timmer_out(FLAGE)
def wahaha2():
time.sleep(0.1)
print('wahahahahahaha2222222')

wahaha()
wahaha2()

多个装饰器调用顺序

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
40
41
42
def wrapper1(func):
def inner1():
print('wrapper1 ,before func')
ret = func()
print('wrapper1 ,after func')
return ret
return inner1

def wrapper2(func):
def inner2():
print('wrapper2 ,before func')
ret = func()
print('wrapper2 ,after func')
return ret
return inner2

def wrapper3(func):
def inner3():
print('wrapper3 ,before func')
ret = func()
print('wrapper3 ,after func')
return ret
return inner3

@wrapper3
@wrapper2
@wrapper1
def f():
print('in f')
return '哈哈哈'

print(f())

结果:
wrapper3 ,before func
wrapper2 ,before func
wrapper1 ,before func
in f
wrapper1 ,after func
wrapper2 ,after func
wrapper3 ,after func
哈哈哈

多个装饰器的效果就是「俄罗斯套娃」

P011_01_python装饰器初识

1.零碎知识点

三目运算符

公式: 变量 = 条件返回True的结果 if 条件 else 条件返回False的结果

1
2
3
4
a = 1
b = 5
c = a if a>b else b # 三元运算
print(c) # 5

time模块

  • time.time()
  • time.sleep() 让程序执行到这个位置的时候停一会儿
1
2
3
4
5
6
7
import time 
time.sleep(5) # 让程序执行到这个位置的时候停一会儿
print('哈哈哈')

print(time.time()) # 1514426720.953799 返回从1970.1.1 0:0:0到现在的 秒

# 5秒后 打印 哈哈哈

2.装饰器前奏之年终评级测试代码效率

1.测试效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time 

def test():
time.sleep(0.1)
print('老板好同事好大家好')

def timmer(func):
start = time.time()
func()
end = time.time()
print(end - start)

# 测试代码效率
timmer(test)

2.这样虽然实现了,但是你的同事必须 每次都timmer(xx) /timmer(aa) /timmer(bb)

我们想要的最终结果就是 直接调用func()

需求:不想修改函数的调用方式,但是还想在原来函数前后添加功能

timmer 就是一个装饰器函数,只是对一个函数,有一些装饰作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time 

def test():
time.sleep(0.1)
print('老板好同事好大家好')

def timmer(func):
def inner():
start = time.time()
func()
end = time.time()
print(end - start)
return inner

# 这样的效果就是如下
test = timmer(test)
test()

3.装饰器初识

原则:开放封闭原则

  • 开放:对扩展是开放的(在不改变原功能函数调用方式的基础上扩展)
  • 封闭:对修改是封闭的(不修改原功能函数)

语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time 

def timmer(func): #装饰器函数
def inner():
start = time.time()
func()
end = time.time()
print(end - start)
return inner

@timmer #语法糖 @+装饰器函数
def test(): #被装饰的函数
time.sleep(0.1)
print('老板好同事好大家好')

# 直接这样就实现了刚刚的功能
# test = timmer(test) 语法糖就是去掉了这句话
test()

print(test) #None

为什么最后 test()的结果是None

  • inner没有返回值

修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time 
def timmer(func): #装饰器函数
def inner():
start = time.time()
res = func() # 用一个变量保存 func的执行结果
end = time.time()
print(end - start)
return res
return inner

@timmer
def test():
time.sleep(0.1)
print('老板好同事好大家好')
return '老板好'

test() # '老板好'

虽然我们简单的实现了装饰器的雏形,但是如果功能函数要接受参会素怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time 
def timmer(func): #装饰器函数
def inner(a):
start = time.time()
res = func(a) # 用一个变量保存 func的执行结果
end = time.time()
print(end - start)
return res
return inner

@timmer
def test(a):
time.sleep(0.1)
print('老板好同事好大家好',a)
return '老板好'


# 内部原理
# test = timmer(test) 此时 test = inner
# test(1) inner(1)

test(1)

如果有两个参数呢?继续傻傻的填参数修改。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time 
def timmer(func): #装饰器函数
def inner(a,b):
start = time.time()
res = func(a,b) # 用一个变量保存 func的执行结果
end = time.time()
print(end - start)
return res
return inner

@timmer
def test(a,b):
time.sleep(0.1)
print('老板好同事好大家好',a,b)
return '老板好'


# 内部原理
# test = timmer(test) 此时 test = inner
# test(1,2) inner(1,2)

test(1,2)

我们不知道函数参数有多少或者是什么形式的(万能参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time 
def timmer(func): #装饰器函数
def inner(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs) # 用一个变量保存 func的执行结果
end = time.time()
print(end - start)
return res
return inner

@timmer
def test(a,b,c):
time.sleep(0.1)
print('老板好同事好大家好',a,b)
return '老板好'


# 内部原理
# test = timmer(test) 此时 test = inner
# test(1,2,c=3) inner(1,2,c=3)

test(1,2,c=3)

去掉所有多余的代码,再看装饰器代码

1
2
3
4
5
6
7
8
9
10
11
def wrapper(func):  #装饰器函数 func是被装饰的函数
def inner(*args,**kwargs):
res = func(*args,**kwargs) # 用一个变量保存 func的执行结果
return res
return inner

@wrapper # test = wrapper(test)
def test(a,b,c):
time.sleep(0.1)
print('老板好同事好大家好',a,b)
return '老板好'

小结

装饰器

  • 开发原则:开放封闭原则
  • 装饰器的作用:在不改变原函数的调用方式的情况下,在函数前后添加功能
  • 装饰器的本质:闭包函数

P010_01_python命名空间和作用域

函数的命名空间

命名空间一共分为三种:

  • 内置命名空间 —— Python解释器
    1. 就是python解释器一启动就可以使用的名字存储在内置命名空间中
    2. 内置的名字在启动解释器的时候被加载进内存里
      print() 、len()、list、tuple等
  • 全局命名空间 —— 我们写的代码但不是函数中的代码
    1. 是在程序从上到下被执行的过程中依次加载进内存的
    2. 放置了我们设置的所有变量名和函数名
  • 局部命名空间 —— 函数
    1. 就是函数内部定义的名字
    2. 当调用函数的时候,才会产生这个名称空间,随着函数执行的结束,这个命名空间又消失了

注意:

  • 在局部:可以使用全局、内置命名空间中的名字
  • 在全局:可以使用内置命名空间中的名字,但是不能用局部的
  • 在内置:不能使用局部和全局的名字的

命名空间的使用

全局不能访问局部

1
2
3
4
5
def func():
a = 1

func()
print(a) # 报错 a为 func函数内部定义的变量为局部变量,不能在全局中访问

内置和全局都存在的max方法

这个非常神似js的作用域链

  1. max为系统定义过的
  2. 你在全局里重新定义max
  3. 调用的时候就会使用全局里定义的(就近原则)
1
2
3
4
def max(l):
print('in max func')

print(max([1,2,3]))

python的关怀

  • func –> 函数的内存地址
  • 函数名() 函数的调用
  • 函数的内存地址() 函数的调用
1
2
3
4
def xxx():
print(1)

print(xxx) # 打印出一个地址 <function xxx at 0x00000000004D2E18>

通过函数名调用明显比 调用形如「0x00000000004D2E18 」的十六进制数表示的内存地址要快。

(在一个班级的环境中:你是选择用人名来产生会话?还是身份证号。)

xxx函数的调用过程

  1. xxx() 的时候
  2. 会现在全局范围内找有没有 xxx的定义,有则调用,反之去它的上一层也就是 内置命名空间,有则调用,反之报错

作用域

作用域的种类

  • 全局作用域 —— 作用在全局 —— 内置和全局名字空间中的名字都属于全局作用域 ——globals()
    注:永远打印全局的名字
  • 局部作用域 —— 作用在局部 —— 函数(局部名字空间中的名字属于局部作用域) ——locals()
    注:输出什么,根据locals所在的位置(根据调用位置来输出结果)
1
2
3
print(globals())
print(locals())
# 结果一样 因为都在全局作用域里
1
2
3
4
5
6
7
8
9
10
def func():
a = 12
b = 20
print(locals())
print(globals())

func()
# 结果不一样
# globals在哪里调用都是输出全局作用域里的内容
# locals 这里只有 {'a': 12 , 'b': 20}

global关键字的作用

  1. 对于不可变数据类型,在局部可查看全局作用域中的变量,但是不能直接修改
  2. 如果想要修改的时候影响到全局,需要在程序的一开始添加global声明
  3. 如果在一个局部(函数)内声明了一个global变量,那么这个变量在局部的所有操作将对全局的变量有效

不修改的情况下可以直接访问全局作用域的变量

1
2
3
4
5
a = 1
def xxx():
print(a)

xxx() # 1

修改全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = 1
def xxx():
a = 2
print(a)

xxx() # 2
print(a) # 1

# 影响到全局
b = 1
def xxx():
global b
b = 2
print(b)

xxx() # 2
print(b) # 2

函数的嵌套和作用域链

1.函数的嵌套调用

1
2
3
4
5
6
7
8
def max(a,b):
return a if a>b else b

def the_max(x,y,z): #函数的嵌套调用
c = max(x,y)
return max(c,z)

print(the_max(1,2,3))

2.函数的嵌套定义

1
2
3
4
5
6
7
def f1():
print("f1")
def f2():
print("f2")
f2()

f1()

3.函数的作用域链

初学让你疑惑的执行结果!!! 尤其是你会js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def f1():
a = 1
def f2():
a = 2
f2()
print('a in f1 : ',a)

f1() # a in f1 : 1

'''
f2中修改了 局部变量 a 的值
但是 在 f1中a值仍然为 1

ps : js里这个肯定是 2
'''

原来python里要加 nonlocal 声明

1
2
3
4
5
6
7
8
9
def f1():
a = 1
def f2():
nonlocal a
a = 2
f2()
print('a in f1 : ',a)

f1() # a in f1 : 2

nonlocal

首先,要明确 nonlocal 关键字是定义在闭包里面的。请看以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
x = 0
def outer():
x = 1
def inner():
x = 2
print("inner:", x)

inner()
print("outer:", x)

outer()
print("global:", x)

结果:
inner: 2
outer: 1
global: 0

现在,在闭包里面加入nonlocal关键字进行声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
x = 0
def outer():
x = 1
def inner():
nonlocal x
x = 2
print("inner:", x)

inner()
print("outer:", x)

outer()
print("global:", x)

结果:
inner: 2
outer: 2
global: 0

区别:一个函数里面嵌套了一个函数。当使用 nonlocal 时,就声明了该变量不只在嵌套函数inner()里面才有效, 而是在整个大函数里面都有效。

我们在看 global

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
x = 0
def outer():
x = 1
def inner():
global x
x = 2
print("inner:", x)

inner()
print("outer:", x)

outer()
print("global:", x)

结果:
inner: 2
outer: 1
global: 2

结论:global 是对整个环境下的变量起作用,而不是对函数类的变量起作用。

4.函数的本质

  1. 可以被引用

    1
    2
    3
    4
    5
    def func():
    print('in func')

    f = func
    print(f)
  2. 可以被当作容器类型的元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def f1():
    print('f1')

    def f2():
    print('f2')

    def f3():
    print('f3')

    l = [f1,f2,f3]
    d = {'f1':f1,'f2':f2,'f3':f3}
    #调用
    l[0]()
    d['f2']()
  3. 可以当作的参数和返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def func():
    print(123)

    def wahaha(f):
    f()
    return f #函数名可以作为函数的返回值

    qqxing = wahaha(func) # 函数名可以作为函数的参数
    qqxing()

5.闭包

嵌套函数,内部函数调用外部函数的变量

  1. 常用方式

    1
    2
    3
    4
    5
    6
    7
    8
    def func():
    name = 'eva'
    def inner():
    print(name)
    return inner

    f = func()
    f()
  2. 判断闭包函数的方法 closure

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
#输出的__closure__有cell元素 :是闭包函数
def func():
name = 'eva'
def inner():
print(name)
print(inner.__closure__)
return inner

f = func()
f()

结果:
(<cell at 0x0000000001ECA4C8: str object at 0x0000000001DCB1B8>,)
eva
-------------------------华丽的分割线--------------------------------------------------------------
#输出的__closure__为None :不是闭包函数
name = 'egon'
def func2():
def inner():
print(name)
print(inner.__closure__)
return inner

f2 = func2()
f2()

结果:
None
egon
  1. 闭包嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def wrapper():
money = 1000
def func():
name = 'eva'
def inner():
print(name,money)
return inner
return func

f = wrapper()
i = f()
i()

结果:
eva 1000
  1. 闭包函数获取网络应用
1
2
3
4
5
6
7
8
9
10
11
from urllib.request import urlopen

def index():
url = "http://www.xiaohua100.cn/index.html"
def get():
return urlopen(url).read()
return get

xiaohua = index()
content = xiaohua()
print(content)

P009_01_python函数

详情请参考之前写的知识总结

Python004函数

快捷知识点

函数参数的顺序

位置参数,*args,默认参数,**kwargs

一道恶心的题

1
2
3
def xx(a = []):
a.append(1)
print(a)

调用方式一:

1
2
3
4
5
6
7
8
9
10
xx()
xx()
xx()
xx()

结果
[1]
[1,1]
[1,1,1]
[1,1,1,1]

调用方式二:

xx()
xx([]) # 传递了参数
xx()
xx()

结果
[1]
[1]
[1,1]
[1,1,1]

如果默认参数的值是一个可变数据类型,那么每次调用函数的时候,如果不传就公用这个数据类型的资源

P008_01_python文件操作

文件处理

1.只读 和 读写

‘r’ 只读模式【默认模式,文件必须存在,不存在则抛出异常】

1
2
3
f = open('a.txt','r',encoding='utf-8')
data = f.read()
f.close()

‘r+’ 读写【可读,可写】

1
2
3
f = open('a.txt','r+',encoding='utf-8')
data = f.read()
f.close()

2.只写 和 写读:

‘w’ 只写模式【不可读;不存在则创建;存在则清空内容】

1
2
3
f = open('log','w',encoding='utf-8')
f.write('aaa')
f.close()

‘w+’ 写读【可读,可写】

1
2
3
4
f = open('log','w+',encoding='utf-8')
f.write('aaa')
print(f.read())
f.close()

3.追加 和 追加写

‘a’ 之追加写模式【不可读;不存在则创建;存在则只追加内容】

1
2
3
f = open('log','a',encoding='utf-8')
f.write('aaa')
f.close()

‘a+’ 写读【可读,可写】

1
2
3
4
f = open('log',mode='a+',encoding='utf-8')
f.write('aaa')
print(f.read())
f.close()

4.只写 和 写读

1
2
3
'x'    只写模式【不可读;不存在则创建,存在则报错】

'x+' 写读【可读,可写】

5.rb、wb、ab、xb的用法:

  1. 对于非文本文件,我们只能使用b模式,”b”表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码、图片文件的jgp格式、视频文件的avi格式)

  2. 以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码

b模式写入的时候如果是汉字就得转换为进制码

1
2
3
4
5
6
7
8
f = open('log',mode='rb')
f.read() # 进制码
f.close()


f = open('log',mode='wb')
f.write('纠纷'.encode('utf-8'))
f.close()

6.操作文件的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
f.read() #读取所有内容,光标移动到文件末尾
f.readline() #读取一行内容,光标移动到第二行首部
f.readlines() #读取每一行内容,存放于列表中

f.write('1111\n222\n') #针对文本模式的写,需要自己写换行符
f.write('1111\n222\n'.encode('utf-8')) #针对b模式的写,需要自己写换行符
f.writelines(['333\n','444\n']) #文件模式
f.writelines([bytes('333\n',encoding='utf-8'),'444\n'.encode('utf-8')]) #b模式

#seek 光标移动到第几个字节的位置
f.seek() 移动到最开始
f.seek(2)

#tell 告诉你光标的位置
f.tell()

#readable #文件是否可读
f.readable()

7.truncate的用法:

1
truncate是截断文件,所有文件的打开方式必须可写,但是不能用w或w+等方式打开,因为那样直接清空文件了,所以truncate要在r+或a或a+等模式下测试效果

8.强大装逼打开和关闭文件结合代码:

1
2
3
with open('log',mode='r+',encoding='utf-8') as f:
f.read()
# 自动调用close()

对俩文件执行读写操作

1
with open('log',mode='r+',encoding='utf-8') as f1, open('log',mode='w+',encoding='utf-8') as f2:

注意实现 with后的读写操作必须在with的缩进内

9.修改文件

需求:实现文件的编辑效果

1
2
3
4
5
6
7
8
9
10
with open('小护士班主任',encoding='utf-8') as f,open('小护士班主任.bak','w',encoding='utf-8') as f2:
for line in f:
if '星儿' in line: #班主任:星儿
line = line.replace('星儿','啊娇')
#写文件
f2.write(line) #小护士:金老板

import os
os.remove('小护士班主任') #删除文件
os.rename('小护士班主任.bak','小护士班主任') #重命名文件

总结

打开文件

1
2
3
4
#open('路径','打开方式','指定编码方式')
# 打开方式 r w a r+ w+ a+ b
#r+ 打开文件直接写 和读完再写
# 编码方式 —— utf-8

操作文件

1
2
3
4
5
6
7
read 一次性读
readlines 一次性读
readline 一行一行读
不知道在哪儿结束
视频 图片 rb bytes 按照字节读

for循环 —— 最好!!!
1
write
  • 光标 —— 文件指针
1
2
3
seek _ 指定光标移动到某个位置
tell _ 获取光标当前的位置
truncate _ 截取文件

关闭文件

  • close

P007_01_python集合和深浅拷贝

集合

1.集合的创建

1
2
3
4
5
6
7
set1 = set({1,2,'barry'})
set2 = {1,2,'barry'}
print(set1,type(set1))
print(set2,type(set2))
结果:
{1, 2, 'barry'} <class 'set'>
{1, 2, 'barry'} <class 'set'>

2.集合的增

  • 直接增加
1
2
3
4
5
set1 = {'alex','wusir','ritian','egon','barry'}
set1.add('女神')
print(set1) #增加,无序的。
结果:
{'ritian', 'alex', 'egon', '女神', 'wusir', 'barry'}
  • 迭代增加
1
2
3
4
5
6
7
8
9
10
11
set1 = {'alex','wusir','ritian','egon','barry'}
set1.update('A')
print(set1)
set1.update('老师')
print(set1)
set1.update([1,2,3])
print(set1)
结果:
{'egon', 'alex', 'ritian', 'wusir', 'A', 'barry'}
{'egon', '老', 'alex', 'ritian', 'wusir', 'A', '师', 'barry'}
{'egon', '老', 1, 2, 3, 'alex', 'ritian', 'wusir', 'A', '师', 'barry'}

3.集合的删

  • 3.1 .remove(‘元素’)
1
2
3
4
set1 = {'alex','wusir','ritian','egon','barry'}
set1.remove('alex') #删除一个元素
print(set1)
结果:{'wusir', 'ritian', 'barry', 'egon'}
  • 3.2 .pop()
1
2
3
4
5
set1 = {'alex','wusir','ritian','egon','barry'}
set1.pop() #随机删除一个元素
print(set1)
结果:
{'alex', 'ritian', 'wusir', 'egon'}
  • 3.3 .clear()

    1
    2
    3
    4
    5
    set1 = {'alex','wusir','ritian','egon','barry'}
    set1.clear() #清空集合
    print(set1)
    结果:
    set()
  • 3.4 del

    1
    2
    3
    4
    set1 = {'alex','wusir','ritian','egon','barry'}
    del set1 #删除集合,会报错
    print(set1)
    结果:报错

4.集合之大招

  • 4.1交集 (& 或者 intersection)
1
2
3
4
5
6
7
set1 = {1,2,3,4,5}
set2 = {4,5,6,7,8}
print(set1 & set2)
print(set1.intersection(set2))
结果:
{4, 5}
{4, 5}
  • 4.2并集 (| 或者 union)
1
2
3
4
5
6
set1 = {1,2,3,4,5}
set2 = {4,5,6,7,8}
print(set1 | set2)
print(set1.union(set2))
结果:{1, 2, 3, 4, 5, 6, 7, 8}
{1, 2, 3, 4, 5, 6, 7, 8}
  • 4.3差集 (- 或difference)

    1
    2
    3
    4
    5
    6
    7
    set1 = {1,2,3,4,5}
    set2 = {4,5,6,7,8}
    print(set{1, 2, 3}1 - set2)
    print(set1.difference(set2))
    结果:
    {1, 2, 3}
    {1, 2, 3}
  • 4.4反交集(^ 或者 symmetric_difference)

    1
    2
    3
    4
    5
    6
    7
    set1 = {1,2,3,4,5}
    set2 = {4,5,6,7,8}
    print(set1 ^ set2)
    print(set1.symmetric_difference(set2))
    结果:
    {1, 2, 3, 6, 7, 8}
    {1, 2, 3, 6, 7, 8}
  • 4.5子集与超集(< 或者 issubset)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    set1 = {1,2,3}
    set2 = {1,2,3,4,5,6}
    print(set1 < set2)
    print(set1.issubset(set2))
    结果:
    True
    True

    print(set2 > set1)
    print(set2.issuperset(set1))
    结果:
    True
    True

5.frozenset不可变集合,让集合变成不可变类型。

1
2
3
s = frozenset('barry')
print(s,type(s))
# frozenset({'a', 'y', 'b', 'r'}) <class 'frozenset'>

深浅拷贝

请参考我之前写的知识点总结《Python021==和 is和深浅拷贝》

深浅copy的区别

  1. 对于浅copy来说,第一层创建的是新的内存地址,而从第二层开始,指向的都是同一个内存地址,所以,对于第二层以及更深的层数来说,保持一致性。
  1. 对于深copy来说,两个是完全独立的,改变任意一个的任何元素(无论多少层),另一个绝对不改变。

P006_01_python字符集区别和转换

编码

ascii:

  • A:00000010 8位 一个字节

unicode:

  • A:00000000 00000001 00000010 00000100 32位 四个字节
  • 中:00000000 00000001 00000010 00000100 32位 四个字节

utf-8:

  • A:00000110 8位 一个字节
  • 中:00000010 00000110 16位 两个字节
    gbk:
  • A:00000110 8位 一个字节
  • 中:00000010 00000110 16位 两个字节
  1. 各个编码之间的二进制,是不能互相识别的,会产生乱码。
  2. 文件的存储,传输,不能是unicode (只能是utf-8 utf-16 gbk gbk2312 ascii等)

#### py3:

  • str 在内存中是Unicode编码。
  • bytes类型,可以是 ascii utf-8 gbk ….

b前缀代表它是bytes类型

对于英文:

1
2
3
4
5
str:表现形式:s = 'alex'
编码方式:010101010 unicode

bytes:表现形式:s = b'alex'
编码方式:000101010 utf-8 gbk。。。。

对于中文:

1
2
3
4
5
6
7
str:表现形式:s = '中国'
编码方式:010101010 unicode

bytes: 表现形式:s = b' x\e91\e91\e01\e21\e31\e32'
编码方式:000101010 utf-8 gbk。。。。

\e91代表一个字节 三个字节代表一个汉字 utf-8里

转换

encode 编码,如何将 str ——> bytes

使用方法: str.encode(‘utf-8’)

decode 解码,如何将 bytes——> str

使用方法: bytes.decode(‘utf-8’)

P005_01_python字典dict

dict

神似js里的对象字面量 k/v组成复杂数据类型

但是有更加让人惊讶的方式

1
2
3
4
5
6
7
dic = {
'name':['大乔','小乔'],
'py3':[{'num':71,'age':18}],
True:1,
(1,2,3):'aaa',
2:'周瑜'
}

没有则增加,有则覆盖

1
2
3
a = {'age':18,'gender':1}
a['name'] = 'xxx'
a['age'] = 22

增的扩展

有键值对,不做更改,没有才添加

1
2
3
4
5
6
7
8
a = {'age':18,'gender':1}
a.setdefault('weight') # a['weight'] 的值为None

a.setdefault('yy',2) # a['yy'] 的值为 2


如果设置默认值的时候在字典初始化的时候已经有值了 就会用其初始的值
a.setdefault('gender',2) # a['gender'] 的值为 1
1
2
3
4
5
6
7
8
9
10
11
info = {'age':15,name:'aa'}
info['name']
info.get("name")


----------------------------
注意 如果字典里没有 name字段
a['name']就会报错
a.get('name') 返回 None
# 返回没有键的情况下返回自定义的值
a.get('xxxx','没有这个键')
  • 修改

    1
    2
    3
    4
    5
    6
    7
    info["age"] = 10000

    a = {'name':'aa','age':18,}
    b = {'name':'bb','weight':50}
    # 将a的所有键值对 更新到b里
    b.update(a)
    # {'name':'aa','age':18',weight':50}
  • 删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 删除 info里的 name字段
del info["name"]

# pop的删-->按照键去删
info.pop('name') # 如果info里没有name字段就会报错

# 如何删除你不知道存在不存在的键,同时不报错
# 设置返回值
info.pop('name',None) # 即使name不存在也不会报错

# 随机删除 返回值是元组,删除的键值
info.popitem()

# 整个删除
del info
  • 滞空
1
2
3
a = {a:1,b:2}
a.clear()
# {}

字典信息

1
2
3
4
5
6
7
8
9
10
info = {a:1,b:2,c:3}

len(info)
# 3

info.keys()
# ['a','b','c']

info.values()
# [1,2,3]
items

返回包含(k,v)元组的列表

1
2
3
info = {"a":1,"b":2}
info.items()
# [("a",1),("b":2)]
has_key

查看字典中key是否存在

1
2
3
info = {a:1,b:2}
info.has_key('a')
# True
字典的遍历

info = {a:1,b:2,c:3}

  1. 遍历字典的key
1
2
for key in info.keys():
print(key)
  1. 遍历字典的value
1
2
for val in info.values():
print(val)
  1. 遍历字典的
1
2
3
4
5
6
7
8
9
10
for item in info.items():
print(item)

# ("a",1)
# ("b",2)
# ("c",3)

for key,val in info.items():
print(key)
print(val)

扩展问题

如何实现列表带索引的遍历

1
2
3
4
5
6
7
方法1 比较low的方法是声明个变量然后随着循环递增 很low 不写了

方法2
a = ['a','b','c']
for i,v in enumerate(a):
print(i)
print(v)

「+ *」

1
2
3
4
5
6
7
8
9
10
11
12
13
"abc"+"123"
# "abc123"

[11,22,33]+[44,55,66]
# [11,22,33,44,55,66]



"-"*3
# "---"

[11,22,33]*2
# [11,22,33,11,22,33]

内置函数

  • cmp 比较两个值
  • len(item) 计算容器中元素个数
  • max(item) 返回容器中最大的元素
  • min(item) 返回容器中最小的元素
  • del(item) 删除变量
1
2
3
4
5
6
7
8
cmp("hello","itcast")
# -1

cmp("itcast","hello")
# 1

cmp("hello","hello")
# 0