python基础篇(二)

PYTHON基础篇(二)


 

  • if;else,缩进
    • A:if的基础格式和缩进
    • B:循环判断
    • C:range()函数和len()函数
    • D:break,contiue和pass语句
  • for,while循环
  • 函数基础
    • A:函数的定义和返回值
    • B:返回值的三种情况
    • C:函数的注释
  • 函数的进阶(命名空间和作用域)

    • A:内置命名空间
    • B:全局命名空间
    • C:局部命名空间
    • D:全局作用域
    • E:局部作用域
    • F:函数的嵌套和作用域链
    • G:函数名的本质
  • 闭包


♣一:if;else和缩进

A:if的基础格式和缩进

我们先看看if判断和循环能做什么。

在生产环境中,将会大量用到判断循环,来将一个需要重复执行的操作简化执行,如果没有判断循环,代码就和记流水账一样麻烦,会加大人工时间成本,比如说我要打印1到100的数字,按照普通的方式打印print(1)........print(100),需要输入100行相同的语句,会很麻烦,但是如果用循环判断,简单的几行就可以写出来。

打印0到20的数字:

#coding=gbk
a = 1                       #定义变量的初始值是1
b = 21                      #定义打印数字的结束值21,为什么是21要看下的a < b判断语句
for a in range(21):        #for语句后面的range(21)在此是要for循环执行21次
    if a < b:              #1是否小于21,小于就执行下面的print语句
        print(a, end=",")  #上面的if条件满足就执行打印操作。
#执行结果
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,

在上述的循环判断中,可以看到我们已经实现了0到20的数字打印,也基础的了解到了循环判断的用法,python的条件判断是通过代码块里面的运算符来控制输出的内容,前面基础篇(一)里面我们介绍了基础的运算符,现在就可以在循环判断里面进行实现重复的工作。

接下来我们通过if的基本模式来认识if的执行过程。

现在有一个要求:评判整个班级的分数等级,1;大于50小于65的学生需要每天晚自习到9点,2;大于65小于85的晚自习到8点,3;大于85小于100的晚自习到7点, 4;其余的晚自习到10点。

#coding=gbk
score = int(input("请输入你的分数:\n"))    #在用户输入的时候就需要指定输入的类型,否则后面程序执行会报类型错误
if score >= 50 and score <= 65:            #程序要求大于和小于某一个数字,但是在代码里面需要考虑到用户输入的数字是等于设定值的情况下,怎么处理
    print("你需要晚自习到9点")              #当用户输入数字了,那么该数字会和下面的每一条判断语句中的条件进行比对,满足指定的条件就会返回相应的结果
elif score >= 65 and score <= 85:
    print("你需要晚自习到8点")
elif score >= 85 and score <= 100:       #在每句判断语句结束之后必须要加上冒号(:),这个也是最基本的语法要求,而且接下来的打印语句前面是带空格的,这个也是python有别于其他语言的地方,在java语言里面可能看到判断
    print("你需要晚自习到7点")              #语句后面是一对{花括号},这个是让程序知道一段代码的作用域就是靠{花括号}来定义的,这样程序就知道这个判断是要执行这个作用域下面的语句,而python就是靠缩进来定义作用域
else:                                    #的,而且这个缩进标准就是4个空格,大家默认都是这么遵守的,而且代码越复杂,缩进就越多,这个也成了python的特点,强制缩进。
    print("你需要晚自习到10点")
#执行结果:
请输入你的分数:                            #后面执行代码如果出现IndentationError错误的,就是语法缩进有错误。
20
你需要晚自习到10点
请输入你的分数:
90
你需要晚自习到7点                                                                    

可以通过下图来简单了解下语句执行的过程:

  注:

  • python里面每个条件语句后面都需要加冒号(:),表示满足条件之后接下来要执行的代码块,如果不加就会报错。
  • 使用缩进来划分语句块,相同缩进数的语句在一起组成一个语句块。
  • python中用elif替代了else if,语句形式为if xxxx:elif xxxx:else xxxx:
  • 在Python中没有switch – case语句。

 B:循环判断:

上面的if判断只能满足一次执行,一般在需要输入密码的地方都是需要限定次数的,如果超过这个次数限制可能就是有人在暴力破解,所以按照上面的代码是不能实现的,永远只能匹配到一次正确的,如果是本人不小心输错密码,直接终止了程序,那还需要重新执行下程序,这个是及其不友好的,所以就需要我们加上计数器这样的代码。

#coding=gbk
y = 1
username = "克鲁斯"
password = 123123
while True:
    user = input("请输入用户名:\n")
    ps = int(input("请输入密码:\n"))
    if user == username and ps == password:
        print("欢迎回来,%s" %user)
        break
    else:
        y += 1                                            #通过y定义的初始值,当每次循环到这里的时候就加1,然后再去执行下一次循环,当y > 3了,就执行break的操作
        if y > 3:                                         #通过计数器的功能,可以让循环可控。
            print("密码错误次数太多,请20秒之后在重新登录")
            break                                         #break是结束退出本次循环,还有一个continue是跳出本次循环,进行下一次循环
            #exit()                                       #和break的功能类似
执行结果:登陆成功
请输入用户名:
克鲁斯
请输入密码:
1
请输入用户名:
克鲁斯
请输入密码:
123123
欢迎回来,克鲁斯
登陆失败:
请输入用户名:
克鲁斯
请输入密码:
1
请输入用户名:
克鲁斯
请输入密码:
123123
欢迎回来,克鲁斯

C:range()函数和len()函数:

range()函数和len()函数经常会在一起使用,range()函数用于快速生成一个有序的数字列,而且range只能在后面添加整数,len()函数用于返回列的索引,也就是前面说的下标,这两个会经常一起出现

一:
#coding=gbk
for i in range(10):
    print(i, end=",")
#执行结果:
0,1,2,3,4,5,6,7,8,9,

二:
for i in range(5,20):   #从5开始到20结束
    print(i, end=",")
#执行结果:
5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,

三:
#coding=gbk
for i in range(0,20 ,3):   #从0开始到20结束,每隔3位数打印一个数
    print(i, end=",")
#执行结果:
0,3,6,9,12,15,18,     #当然也可以打印负数
range()函数用法
#coding=gbk
a = [1,2,3,4,5,6,7,8,9]
for i in range(len(a)):   #生成a列表中的下标参数
    print(i,a[i])         
#执行结果:
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
range() + len()用法 

renge还可以用于数值列表的快速生成:

list(range(0,20,3))       #列表,调用函数range,从数字0开始到数字20结束,每隔3个数字取一个数字(含起始数字)
#执行结果
[0,3,6,9,12,15,18,]

D:break,contiue和pass语句:

break的功能是退出for和while循环体,遇到else语句也不执行。

#coding=gbk
for i in range(0,20):   #从0开始到20结束
    if i == 15:         #当循环体里面打印到15的时候
        break          #就结束整个循环体
    print(i,end=",")
#执行结果:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,
break退出循环

 continue的功能是用于跳出(跳过)本次的循环体,执行一次循环。

#coding=gbk
for i in range(0,20):                #从0开始到10结束
    if i == 6 or i == 15 or i ==18 : #当循环体里面打印到6,15和18的时候
        continue                     #就跳出本次循环,执行下一次循环
    print(i,end=",")
#执行结果:
0,1,2,3,4,5,7,8,9,10,11,12,13,14,16,17,19,    #可以看到6,15,18就没有打印
continue跳出循环

在代码for和while循环的时候,同时使用了continue和break语句,在这种情况下,continue要先于break执行,否则break会结束掉循环,continue可能就执行不到,除非是在break没有触发的情况先使用continue和其他特殊情况。

#coding=gbk
for i in range(0,20):
    if i == 6 or i == 15:
        continue
    elif i == 18:
        break
    print(i,end=",")
#执行结果:
0,1,2,3,4,5,7,8,9,10,11,12,13,14,16,17,
注意事项

pass没有特殊的用意,一般用于代码的完整性,主要就用于占位语句,例如代码的大部分已经完成,但是有些地方还没有想好怎么写,就可以使用pass来占据这个代码位置,让代码先可以执行,后续在优化的时候在回来把占位的地方补上。

还有return,return主要是用于函数,作用也是终止的,后续说明,还有exit,退出。


 ♣二:for,while循环:

for和while最直接的区别就是,当明确循环次数的时候使用for,不清楚循环多少次的时候用while循环,(在实际开发过程中for循环使用的几率大于while循环)

例:让用户输入3个数,比对大小进行排序(这个里面明确了次数3,就可以使用for循环)

 

l = []
for i in range(3):
    x = int(raw_input('integer:\n'))
    l.append(x)
l.sort()
print l

1:首先要解决用户输入;

2:进行比对,这个可以使用到排序sort()用法;

3:显示用户输入程序排序过的数据。


 ♣三:函数基础

上面我们在完成一个需求写代码的时候还是从上到下一步步完成,如果是出现需求调整,我们就需要对代码从上到下进行二次调整来满足需求,这样就会出现一个问题就是代码很不灵活,有时候需求调整一个很小的功能点就需要对代码进行很大幅度的调整,还有就是下次遇到一个相似的需求,还是要重新写一遍的,这个时候就需要部分基础代码能被重复利用,这样我就只需要调整其他部分来满足需求,这个功能就是函数。

A:函数的定义和返回值

例如:我需要打印一个列表的长度,我们可以使用len()这个内置函数来完成,但是不使用len()的情况下就需要使用for循环:

s = '初始化版本1'
i = 0
for k in s:
    i += 1    #通过循环相加来完成计算
print(i)  

如果是使用函数来完成:
s = '初始化版本1'
def my_s():  #使用关键字def后面接函数名(函数同变量定义方式一样,后面的()括号是固定格式,括号里面还可以填写内容。
    i = 0
    for k in s:
        i += 1
    print(i)
my_s()      #函数的调用,直接使用函数名即可

上面是函数的基本使用方法,但是从上面的函数来看存在几个问题,1;函数只能处理列表s,2;my_s()打印的方式不友好,如果是从可读性来考虑的话,应该是pritn(i)这样,3;无返回值。

我们先看第三个问题,为什么需要返回值,有两个列表,a和b,a列表长度为3,b列表长度为10,如果是要计算a+b列表的长度和,普通的print打印是不行的,这个时候需要将值暂存起来返回,然后通过print(a + b)来计算出和。

接下来在看第二个问题,my_s()如果是放在其他代码里面执行,会不清楚my_s()打印的对象是谁。

s = '初始化版本1'
def my_s():  #使用关键字def后面接函数名(函数同变量定义方式一样,后面的()括号是固定格式,括号里面还可以填写内容。
    i = 0
    for k in s:
        i += 1
    return i  #返回值的关键词就是renurn
s1 = my_s()
print(s1)
返回值

通过返回值我们的代码可读性变强了.

B:返回值的三种情况:

1:不写renurn
s = '初始化版本1'
def my_s():
    i = 0
    for k in s:
        i += 1
s1 = my_s()
print(s1)
#执行结果:
None

2:只写return:
s = '初始化版本1'
def my_s():
    i = 0
    for k in s:
        i += 1
    return   #return后面不写要返回的值的对象 
s1 = my_s()
print(s1)
#执行结果:
None

第一种的不写return还能理解,但是第二种写了return又不返回值,这个看起来是没有意义的。这个时候需要说明到return的特殊用意,如果函数体里面只要需要return,后面的代码就不会继续执行。
def my_s():
    s = ['初始化版本1','版本2']
    for i in s:
        print(i)
    return
    print('版本3')  #会发现这行是没有打印的,以为这函数到return就结束了
s1 = my_s()
print(s1)
#执行结果:
初始化版本1
版本2
None
其实这个也是没有意义的,但是我想执行到列表里面的某一个元素就结束函数体,这个就会有意义了。
def my_s():
    s = ['初始化版本1','版本2']
    for i in s:
        print(i)
        if i == '初始化版本1':  #遇到指定元素就结束函数体,下一个元素就不会打印了
            return
s1 = my_s()
print(s1)
#执行结果:
初始化版本1
None

def my_s():
    s = ['初始化版本1','版本2']
    for i in s:
        print(i)
        if i == '初始化版本1':  #遇到指定元素就结束函数体,下一个元素就不会打印了
            break   #之前我们也学过break跳脱,但是这个跳脱只是跳出for这个循环,跳不出函数体,函数下面还有代码的情况下照样打印
    print('@'*10)
s1 = my_s()
print(s1)
#执行结果:
初始化版本1
@@@@@@@@@@
None

def my_s():
    s = ['初始化版本1','版本2']
    for i in s:
        print(i)
        if i == '初始化版本1':  #遇到指定元素就结束函数体,下一个元素就不会打印了
            return   #return是结束一个函数
    print('@'*10)
s1 = my_s()
print(s1)
#执行结果:
初始化版本1
None
没有返回值(这个情况下默认返回None)
def my_k():
    return [1,2,3,4]
k = my_k()   #之前的情况都是让函数赋值给一个变量在print
print(k)
#执行结果
[1, 2, 3, 4]

实际上是不需要这么麻烦的,可以直接打印函数名就可以
def my_k():
    return [1,2,3,4]
print(my_k())
#执行结果
[1, 2, 3, 4]

从上面的条件能看出
1:return可以返回任何数据
2:只要return返回了就可以接受到
3:在返回一个值的情况下也是和之前一样,如果函数里面有多个return,只执行第一个,后面都不执行
返回一个值
def my_k():
    return 1,2,3   #有三个返回值的情况
k1,k2 = my_k()
print(k1,k2)
#执行结果
Traceback (most recent call last):
  File "/Users/yangpengcheng/Documents/python_file/mg_函数基础.py", line 33, in <module>
    k1,k2 = my_k()
ValueError: too many values to unpack (expected 2)
执行结果报错。



def my_k():
    return 1,2,3   #有三个返回值的情况
print(my_k())
#执行结果
#(1, 2, 3)  #得到的是一个元祖,这个结果就可以使用元祖的方法来操作了

调整函数:
def my_k():
    return 1,2,3   #有三个返回值的情况
k1 = my_k()
print(k1)
#执行结果
(1, 2, 3) #得到的是一个元祖,这个结果就可以使用元祖的方法来操作了

def my_k():
    return 1,2,3   #有三个返回值的情况
k1,k2,k3 = my_k()
print(k1,k2,k3)
#执行结果
1 2 3 #和预期结果相符

返回值3个,打印两个的情况
def my_k():
    return 1,2,3   #有三个返回值的情况
k1,k2 = my_k()
print(k1,k2)
#执行结果
Traceback (most recent call last):
  File "/Users/yangpengcheng/Documents/python_file/mg_函数基础.py", line 33, in <module>
    k1,k2 = my_k()
ValueError: too many values to unpack (expected 2)
执行结果报错。

从上面得出两个结论:
1:接受多个返回值的时候,返回值有多少个就需要多少个变量来接受,不能多也不能少
2:如果是使用一个变量来接受或者直接print打印的情况下得到的结果是一个元祖,这个是python内部帮忙进行的转换。
返回多个值

优化上述函数只能处理列表和计算和的问题:

def my_k1(k):  #通过函数名括号里面标记接受参数,但是后面函数调用的时候传参并不知道是以什么形式体现的,所以这个地方的标记接受参数就只是一个形式上的参数,简称形参
    i = 0
    for k1 in k:
        i += 1
    return i
k2 = my_k1("初始化版本1")  #通过函数名来传递参数,由函数体接受传递进去的参数来执行,这个传递的参数是真正的取用的实际参数,简称实参
k3 = my_k1([1,2,3,4,5,6,7])
print(k2,k3)
print(k2 + k3)
#执行结果
6 7
13 

 返回多个值的位置参数和关键字参数:

def my_sum(a,b):    #在接收参数的位置我们接收多个参数
    my1 = a + b
    return my1
my2 = my_sum('版本1','版本2')#在传递参数的时候也需要针对上面接收参数的位数给足参数
my3 = my_sum(1,99)
print(my2)
print(my3)
执行结果:
版本1版本2
100

def my_sum(a,b):    #在接收参数的位置我们接收多个参数,这个接收多个参数的形式叫位置参数
    my1 = a + b
    return my1
# my2 = my_sum('版本1','版本2')#在传递参数的时候也需要针对上面接收参数的位数给足参数
# my3 = my_sum(1,99)
my4 = my_sum(2)  #传递参数的位置如果是给了一个参数,就会报错
# print(my2)
# print(my3)
print(my4)
#执行结果:
Traceback (most recent call last):
  File "/Users/yangpengcheng/Documents/python_file/mg_函数基础.py", line 100, in <module>
    my4 = my_sum(2)
TypeError: my_sum() missing 1 required positional argument: 'b'
执行报错

def my_sum(a,b):
    print(a,b)
    my1 = a + b
    return my1
my2 = my_sum(99,1)
my3 = my_sum(b = 1,a = 99) #也可以通过关键字传递参数
my4 = my_sum(1,b = 99) #也可以混合使用,但是必须先进行位置传参,在进行关键字传参数,不得将多个实参指向同一个位置形参。
print(my2,my3,my4)
#执行结果:
99 1
99 1
1 99
100 100 100 

返回多个值的默认参数:

def classmate(name,sex='男'):   #当参数里面出现一个频繁出现的参数时,就可以使用默认参数,默认参数可以不传,如果传了就用传递的参数,而且这个默认参数其实就是上面的关键字参数
    print('%s : %s'%(name,sex))
classmate('金吒')
classmate('哪吒')
classmate('木吒','女')
# 执行结果:
# 金吒 : 男
# 哪吒 : 男
# 木吒 : 女

返回多个值的动态参数:def sum(*kk): #动态参数定义的关键是*,后面是什么无所谓,但是在实际工作中动态参数是*后面接args,这个是习惯问题。

 def  sum(*args) 
    k = 0
    for i in kk:
        k += i
    return k
print(sum(1,2,3,4))#这样计算数据我就不需要考虑事先留下位置或者默认值,有多少处理多少。
print(sum(67,234)) 
执行结果:
10
301 

def sum(*kk,num='12'):  #在动态参数后面在接一个关键字参数。
    k = 0
    for i in kk:
        k += i
    return k
print(sum(1,2,3,4,num=13))
print(sum(67,234))
执行结果:
10     #发现关键字参数并没有被执行,这个也就说明,动态参数是不能处理关键字参数的
301  
但是我就想执行上面的关键字参数,可以使用**来完成,**后面也有一个约定俗成的词kwargs
def fum(**kwargs): #**就可以处理任何情况下参数
    print(kwargs)
fum(a = 1,b = 3,c = 5)
fum(b = 100,c = 200)   #注,此处传递的参数还是需要遵循变量的命名方式来,即不能是数字开头
# 执行结果:
{'a': 1, 'b': 3, 'c': 5}
{'b': 100, 'c': 200}
动态参数:
  #*args  :接收的是按照位置传参的值,组织成一个元祖
  #**kwargs :接收是按照关键字传参的值,组织成一个字典
def fum(*args,**kwargs):
    print(args,kwargs)
fum(1,2,3,4,5,a = "kkk",b = "版本1")
# 执行结果:
(1, 2, 3, 4, 5) {'a': 'kkk', 'b': '版本1'}
  #必须是args在前,kwargs在后面

通过上述的例子,可以总结出来函数特性:

  • 位置参数:直接定义
  • 默认参数(关键字参数):参数名 = “默认值”
  • 动态参数:可接收任意多的参数
  • 顺序:位置参数,*args,默认参数,**kwargs  
def fun(*args):
    print(args)
fun(1,2,3,4,5)
k = [1,2,3,4,5]
fun(k[0],k[1],k[2])
执行结果:
(1, 2, 3, 4, 5)
(1, 2, 3)    #取元祖的下表可以把参数打印出来,但是如果是有100个参数,这个方法有点不适用了。

def fun(*args):
    print(args)
fun(1,2,3,4,5)
k = [1,2,3,4,5]
fun(*k)      #可以在变量名前面加一个*号,就会按照顺序来打散
执行结果:
(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)

def fun(**kwargs):
    print(kwargs)
fun(a=1,b=2)
k = {'a':1,'b':2}
fun(**k)   #同样的**也可以完成一样的需求
#执行结果
{'a': 1, 'b': 2}
{'a': 1, 'b': 2}
动态参数的另外一种传参方式
def ll(l = []):
    l.append(1)
    print(l)

ll()
ll([])
ll()

def aa(k,k1 = {}):#这两个函数存在一个共同点就是数据类型都是一个可变的类型。
    k1[k] = 'v'   #字典和列表都是可以编辑的
    print(k1)
aa(1)
aa(2)
aa(3)
#执行结果
[1]
[1]             #这个执行方式是11([]),函数形参是默认参数形式,当默认参数有值就用默认的值,我传了值就用传的值
[1, 1]
{1: 'v'}
{1: 'v', 2: 'v'}
{1: 'v', 2: 'v', 3: 'v'}
#通过上述结果我们得出,如果默认参数是一个可变类型的数据,如果不给它传值,他就始终公用同一个数据类型的资源
默认参数的误区(可变类型参数)

 C:函数的注释:

def fun():
    '''
    函数的功能
    参数1
    参数2
    :return:返回的是什么类型的数据,下面在开始写你的函数体
    '''
函数的注释

四:函数的进阶(命名空间和作用域):

A:内置命名空间:

此类空间是在python解释器启动的时候就加在到内存空间中,这个空间里面函数名就是例如print,inport等,我们可以随时调用。

在内置里面是不能使用全局和局部命名空间中的名字,因为内置是python启动的时候加载进来的,后面的空间都是解释器加载结束后再逐一加载的,除非你能把你的命名加到python启动的过程中。

B:全局命名空间

此类空间是我们程序在执行的过程中从上到下逐一加到内存空间中,这个空间里面存的命名其实是我们设置的变量,函数名等。

在全局里面可以使用内置的命名空间中的名字,但不能使用局部的,因为局部执行结束就会释放。

C:局部命名空间

此类命名是我们函数内部定义的名字,当函数调用的时候才会产生这个空间,这个函数执行结束了,开辟的内存空间也就随之释放掉了。

在局部里面可以使用全局,内置命名空间中的名字。

在命名空间里面永远是内置<全局<局部,但是当我们全局定义了内置空间中的名字,会使用全局的名字,同样,在局部使用了全局和内置空间中的名字,会使用局部空间里面的名字

同理,当局部一段代码执行,程序先去局部找,局部找不到再去全局,最后去内置找,都找不到就报错,但是全局和内置是不能反过来到局部里面去找名字的。

在局部空间函数名是隔离开来的,即使两个函数使用了相同的命名,两者不相互影响

D:全局作用域:

在内置和全局命名空间的名字都属于全局作用域

E:局部作用域:

函数(局部命名空间的名字属于局部作用域)

a = 1
def k1():
    a += 1    #在全局已经定义,在局部直接去修改是不可以的,此类数据对于局部来说是不可变的类型

def k2():
    global a  #如果要想直接调整全局作用域的数据,需要通过global来声明,而且在局部通过global声明了变量,那么这个变量在局部的操作也会对全局有效
    a += 1
k2()
print(a)
#执行结果
2       #在实际场景中global是尽量少用,因为他们会调整全局变量的值,这样会导致全局变量被改变出现bug。

b = 1
c = 2
def k3():
    x = 'bbb'
    y = 'ccc'
    print(locals())  #使用locals()可以查看到我局部空间的所有名字
k3()
print(globals()) #使用glbals()可以查看到全局和内置空间中的名字,glbals()只能看全局和内置,不能看局部的
print(locals())   #把locals()放到全局也可以查看到全局和内置的名字,这个locals()查看的范围取决于locals()所在的位置
#执行结果
{'y': 'ccc', 'x': 'bbb'}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x101f5a3c8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/yangpengcheng/Documents/python_file/mg_函数的进阶.py', '__cached__': None, 'a': 2, 'k1': <function k1 at 0x101bcaea0>, 'k2': <function k2 at 0x1041c6510>, 'b': 1, 'c': 2, 'k3': <function k3 at 0x1041c6620>}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10405a3c8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/yangpengcheng/Documents/python_file/mg_函数的进阶.py', '__cached__': None, 'a': 2, 'k1': <function k1 at 0x101bcaea0>, 'k2': <function k2 at 0x1041a6510>, 'b': 1, 'c': 2, 'k3': <function k3 at 0x1041a6620>}
global,locals(),globals()

F:函数的嵌套和作用域链:

def max(a,b):
    return a if a>b else b  #定义比大小函数1

def max1(x,y,z):
    c = max(x,y)       #在第二个函数里面我直接使用函数1的功能,有相同的需求,可以重复使用,减少工作量
    return max(c,z)
print(max1(3,9,2))
#执行结果
9    #上述的过程就是函数的嵌套调用。
函数的嵌套调用
a = 1
def k1():
    a = 1
    def k2():
        b = 2
        nonlocal a  #如果是要调整局部变量的值,py3里面提供了nonlocal,它可以调整函数局部变量的值,但是它的逻辑是在函数体里面往上找,离nonlocal最近的变量的值将被改变,使用global只能改全局的变量值
        a += 1
        print(a)
        print('k2')
    k2()
k1()
print(a)
执行结果
1
k2
1
取消nonlocal注释:
2
k2
1
nonlocal 

G:函数名的本质:

函数名实质就是一个内存地址,一个内存地址是可以被多个变量名,函数名指向的,这样我们就可以使用这个内存地址点对多指向特性完成很多复杂的事情。

def sum1():
    print(123)
sum2 = sum1     #1:函数名是可以赋值给其他变量名的,并且两个函数指向了同一个变量值。
sum2()

sum3 = [sum1,sum2]  #2:函数名可以作为容器类型(列表,元祖等)的元素
print(sum3)      #可以看到sum2和sum1指向的是一个内存地址

for i in sum3:
    i()         #可以看到函数执行的结果是一样的
# 执行结果:
# 123
# [<function sum1 at 0x000001463FD041E0>, <function sum1 at 0x000001463FD041E0>]
# 123
# 123

def sum2(sum3):
    sum3()

sum2(sum1)  #3:函数名可以作为函数的参数
# 执行结果:
# 123

def sum2(sum3):
    sum3()
    return sum3  #4:函数名可以作为函数的返回值

sum4 = sum2(sum1)
sum4()
# 执行结果
# 123
# 123
函数名的本质 

只要满足以下几种类型的都统称为第一类对象:

第一类对象可以理解为普通变量,用法相似,唯独函数可以函数名()这样去执行

  1. 可以在运行期创建
  2. 可以作函数参数或返回值
  3. 可以存入变量的实体(容器类型)

♣五:闭包

闭包的一定是嵌套函数机构,内部函数调用外部函数的变量
def sum1():
    a = 1
    def sum2():
        print(a)
    print(sum2.__closure__)
sum1()
print(sum1.__closure__)
# 执行结果:
# (<cell at 0x000002BA196AA618: int object at 0x000000006493BC10>,) #如果执行结果里面有cell at字样就说明是闭包
# None 直接打印是空的就不是闭包

def sum1():
    a = 1
    def sum2():
        print(1)   #此处的1是没有定义的,可以看到结果是空,空就不是闭包
    print(sum2.__closure__)
sum1()
# 执行结果
# None
闭包的基本概念
def sum1():
    a = 1
    def sum2():
        print(1)
    return sum2
sum3 = sum1()
sum3()   #闭包的最常用方法就是外部使用内部的函数变量
# 执行结果
# 1
闭包的基本使用
为什么要使用闭包,如果没有闭包的情况下,我们外部用内部的函数值,执行的过程中是要在内存里面开启内存空间的,而且是每次执行都要单独再开启,函数使用完毕内存就释放了,所以为了执行效率,解决办法就是让这个内存空间延续,
不要释放,闭包目的就在于此。
import urllib  #导入urllib函数
from urllib.request import urlopen
ret = urlopen('https://www.autohome.com.cn/beijing/').read()  #使用urllib模块用法打开一个网页
print(ret)

上面我们打开网页,如果是想爬取页面上的内容,比如是汽车之家上面的汽车的图片,那么肯定是要拿到网页源码,拿到之后进行筛选,关键字是jpg结尾的图片,这样的情况下就不能让网页来回加载(重复生成变量),效率会很低。
def get_url():
    url = 'https://www.autohome.com.cn/beijing/'
    def get():
        ret = urlopen(url).read()
        print(ret)
    return get
get_open = get_url()
get_open()
执行结果
b'\r\n\r\n<!DOCTYPE html>\r\n\r\n<html>\r\n<head>\r\n
html网页源码内容,和你网页右键查看源代码显示的内容一样
闭包实例

posted on 2018-08-05 12:01  ppc_server  阅读(435)  评论(0编辑  收藏  举报

导航