(一)python函数变量作用域

(1)作用域的分类

Python中,程序的变量并不是在任何位置都可以访问的,具体从哪个位置可以访问,取决于这个变量是在哪里赋值的。

变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。

Python的作用域好一共有4种,分别是:

  • L(Local) 局部作用域
  • E(Enclosing)闭包函数外的函数中
  • G(Global)全局作用域
  • B(Built-in)内建作用域

以上作用域范围按L->E->G->B的规则查找,即:在局部找不到,变回去局部外的局部找(例如闭包),再找不到就回去全局找,再者去内建找。

建议全局变量名以g_开头,例如:g_sum

(2)作用域的界定

在python中,以下定义都会产生新的作用域:

  • 模块 module
  • 类 class
  • 函数 def lambda

而以下代码块则不会引入新的作用域:

  • if…elif….else
  • try/except
  • for/while
    在以上这些语句中定义的变量,外部都可以访问

例如:

  1. #sum_1在循环体内定义,但是从循环体外也可以访问,for不影响变量作用域,不会产生新的作用域
  2. for i in range(0,101):
  3. if i == 0:
  4. sum_1 = 0
  5. sum_1 += i
  6. print("sum1=%d" % sum_1)

运行结果为:

  1. sum1=5050

关于函数影响变量作用域的例子,例如:

  1. #这里i定义在函数体内部,则不能在函数体外访问,如果访问会报错
  2. def sum_1(i):
  3. if i > 0:
  4. return i + sum_1(i-1)
  5. else:
  6. return 0
  7. print(sum_1(100))
  8. print("i=%d" % i )

运行后,报错信息为:

  1. NameError: name 'i' is not defined

(3)局部变量和全局变量

局部变量定义在函数体内部,只拥有一个局部作用域,而全局变量定义在函数体外部,拥有全局作用域。

局部变量只能在其被声明的函数体内部访问,而全局变量可以在整个程序范围内被访问。调用函数时,所有在函数体内声明的变量名称都将被加入到作用域中。如:

  1. #这里在set_name和get_name重定义的name都属于局部变量,只能从函数体内部访问
  2. def set_name():
  3. name = "zhangsan"
  4. return name
  5. def get_name(name):
  6. print(name)
  7. nm = set_name()
  8. print("nm={}".format(nm))
  9. get_name("susan")

运行结果为

  1. nm=zhangsan
  2. susan

全局变量从函数体外定义,可以在不同的函数中使用,属于全局型变量,如:

  1. #函数体外定义的name变量,为全局变量,无论从函数内访问,还是函数外访问,都是一样的
  2. name = "zhangsan"
  3. def get_name():
  4. print(name)
  5. def get_name2():
  6. print(name)
  7. get_name()
  8. get_name2()
  9. print(name)

结果都是

  1. zhangsan
  2. zhangsan
  3. zhangsan

(4)修改全局变量的值

全局变量的值可以从函数体内,也可以从函数体外修改,如:

  1. #定义了一个全局变量
  2. age = 20
  3. def change_age():
  4. #函数体内重新定义了一个age,这个是局部变量,不同于全局变量
  5. age=25
  6. print("函数体内的age=%d" % age)
  7. change_age()
  8. print("全局变量age=%d" % age)

运行结果为

  1. 函数体内的age=25
  2. 全局变量age=20

由此可见全局变量并没有被修改

如果要在函数体内重新给全局变量赋值,则需要使用global关键字来重新定义变量的作用域
例如:

  1. age = 20
  2. def change_age():
  3. #global关键字指定其后的age为全局变量,这样在函数体内可以重新给全局变量赋值
  4. global age
  5. age=25
  6. print("函数体内的age=%d" % age)
  7. change_age()
  8. print("全局变量age=%d" % age)

运行结果为:

  1. 函数体内的age=25
  2. 全局变量age=25

由此,我们可以发现,在函数体内通过指定变量为全局变量,可以重新给全局变量赋值

(5)全局变量定义的位置

  1. g_num1=100
  2. def print_global_num():
  3. print("g_num1:%d" % g_num1)
  4. print("g_num2:%d" % g_num2)
  5. print("g_num3:%d" % g_num3)
  6. g_num2 = 200
  7. print_global_num()
  8. g_num3 = 300

运行得:

  1. g_num1:100
  2. g_num2:200
  3. NameError: name 'g_num3' is not defined

全局变量必须在被使用之前定义

(6)字典、列表作为全局变量,在函数体内修改这种类型的全局变量中的元素

字典和列表被定义为全局变量之后,可以直接在函数内修改字典和列表中的元素,例如:

  1. g_num_list = [1, 2, 3]
  2. g_info_dict = {"anme": "zhangsan", "age": 20}
  3. def update_info():
  4. #增加一个元素4
  5. g_num_list.append(4)
  6. #增加一个键值对:gender和male
  7. g_info_dict["gender"]="male"
  8. update_info()
  9. print(g_num_list)
  10. print(g_info_dict)

运行结果为:

  1. [1, 2, 3, 4]
  2. {'anme': 'zhangsan', 'age': 20, 'gender': 'male'}

(7)缺省参数

定义函数形参的时候,可以指定缺省值,形如:
def fun(x=10,y=20)
举例如下:

  1. #定义函数x_y_sum,并指定形参y的缺省值为20
  2. def x_y_sum(x, y = 20):
  3. print("x=%d" % x)
  4. print("y=%d" % y)
  5. return x + y
  6. rs1 = x_y_sum(10, 20)
  7. print("rs1=", rs1)
  8. #调用的时候形参y对应的实参缺省,则取默认值
  9. rs2 = x_y_sum(10)
  10. print("rs2=", rs2)
  11. #定义的时候形参y对应的实参指定为30,则y取值为指定的30
  12. rs3 = x_y_sum(10, 30)
  13. print("rs3=", rs3)

运行结果为:

  1. x=10
  2. y=20
  3. rs1= 30
  4. x=10
  5. y=20
  6. rs2= 30
  7. x=10
  8. y=30
  9. rs3= 40

注意:定义有默认值的形参的时候,必须把默认值放到最后面,否则形参和实参结合的时候会发生错误,以下的定义就是错误的

  1. def x_y_sum(y=20,x):
  2. return x+y

(8)命名参数

命名函数,即在调用函数的时候,指定名称的实参,实参名称与函数定义时的形参要相同,但是顺序可以不同
例如:

  1. def x_y_sum(x=10,y=20):
  2. return x+y
  3. #用命名参数调用,指定调用实参要结合的形参名
  4. rs1 = x_y_sum(y=50,x=100)
  5. #用命名参数调用,只指定了x,y没指定,缺省
  6. rs2 = x_y_sum(x=15)
  7. #x,y都缺省
  8. rs3 = x_y_sum()
  9. print("rs1=%d" % rs1)
  10. print("rs2=%d" % rs2)
  11. print("rs3=%d" % rs3)

运行结果为

  1. rs1=150
  2. rs2=35
  3. rs3=30

(二)不定长参数

(1)用元组接收不定长的参数

1)格式

很多时候,一个函数调用的参数事先我们并不知道,这个时候就可以使用不定长参数

第一种不定长参数是使用元组接收参数,格式如下

  1. def func(x,y,z...,*args):
  2. statements

2)不定长参数举例

  1. #定义一个函数,函数中包含了接收不定长参数的*args,函数内对不定长参数进行处理
  2. def any_num_sum(x, y=10, *args):
  3. print("args={}".format(args))
  4. rs = x + y
  5. if len(args) > 0:
  6. for arg in args:
  7. rs += arg
  8. return rs
  9. rss = any_num_sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  10. print("rs={:d}".format(rss))

运行得:

  1. args=(3, 4, 5, 6, 7, 8, 9, 10)
  2. rs=55

3)改变不定长参数的位置

不定长参数可以放到命名参数的前面,这要求命名参数在与实参结合的时候,一定要指定实参的名称,否则匹配会发生错误
例如:

  1. def any_num_sum2(x, *args, y=10):
  2. print("args={}".format(args))
  3. print("x={}".format(x))
  4. print("y={}".format(y))
  5. rs = x + y
  6. if len(args) > 0:
  7. for arg in args:
  8. rs += arg
  9. return rs
  10. #调用的时候只有一个实参
  11. rs1 = any_num_sum2(20)
  12. #指定了三个实参,一个按位置结合,一个不定长参数,一个命名参数
  13. rs2 = any_num_sum2(20, 30, y=100)
  14. #最后一个命名参数同形参y结合
  15. rs3 = any_num_sum2(20, 30, 40, 50, 60, 70, y=100)
  16. print(rs1)
  17. print(rs2)
  18. print(rs3)

运行结果为:

  1. args=()
  2. x=20
  3. y=10
  4. args=(30,)
  5. x=20
  6. y=100
  7. args=(30, 40, 50, 60, 70)
  8. x=20
  9. y=100
  10. 30
  11. 150
  12. 370

(2)用字典接收不定长的参数

用字典接收的参数,都是key-value键值对的形式,key为变量名,value为变量的值。

其基本形式如下:
def function([formal_args,]**kvargs)

使用这种形式,在调用的时候需用变量名=值的形式赋值。
举例如下:

  1. #使用列表和字典格式接收的时候,形参列表中*argu在前,**kwargu在后
  2. def social_insurance_comp(basic_money, *staff, **proportion):
  3. print(proportion)
  4. print(staff)
  5. e_money = basic_money * proportion["e"]
  6. m_money = basic_money * proportion["m"]
  7. a_money = basic_money * proportion["a"]
  8. total_money = e_money + m_money + a_money
  9. for item in staff:
  10. print("item=%s" % item)
  11. return total_money
  12. #当实参和形参结合的时候,列表实参变量在前,字典实参变量在后,顺序不能发生错误
  13. sum_1 = social_insurance_comp(1000, "zhangsan", "lisi", "wangwu", e=0.2, m=0.1, a=0.05)
  14. print("sum={}".format(sum_1))

关于拆包
举例如下:

  1. def salary_comp(basic_money, *other_money, **proportion):
  2. print("缴费基数:{}".format(basic_money))
  3. print("其他工资:{}".format(other_money))
  4. print("比例:{}".format(proportion))
  5. other_money = (500, 200, 100, 1000)
  6. proportion_dict = {"e": 0.2, "m": 0.1, "a": 0.12}
  7. #在这种调用形式下,other_money和proportion_dict被当做一个列表参数提提供给了*other_money,则结果只能是列表和词典作为一个元组传入
  8. salary_comp(8000, other_money, proportion_dict)

运行结果如下:

  1. 缴费基数:8000
  2. 其他工资:((500, 200, 100, 1000), {'e': 0.2, 'm': 0.1, 'a': 0.12})
  3. 比例:{}

正确,采用拆包方式传递参数,应该如下:

  1. def salary_comp(basic_money, *other_money, **proportion):
  2. print("缴费基数:{}".format(basic_money))
  3. print("其他工资:{}".format(other_money))
  4. print("比例:{}".format(proportion))
  5. other_money = (500, 200, 100, 1000)
  6. proportion_dict = {"e": 0.2, "m": 0.1, "a": 0.12}
  7. #调用的时候,列表前加*,词典前加**,则可以实现拆包调用
  8. salary_comp(8000, *other_money, **proportion_dict)

(三)递归函数

递归函数,是调用其自身的函数,其典型应用为求阶乘,例如:

  1. def recursive(num):
  2. if num > 1:
  3. return num * recursive(num-1)
  4. else:
  5. return 1
  6. print('{}'.format(recursive(4)))

运行结果为

  1. 24

(四)匿名函数

lamba匿名函数,其实质为没有名称的函数,定义格式如下:

  1. lambda 形参1,形参2:返回值表达式
  2. 等同于:
  3. def 函数名(形参1,形参2):
  4. 返回值表达式

举例如下:

(1)匿名函数作为参数传入到自定义函数中

  1. def x_y_comp(x,y,func):
  2. rs = func(x,y)
  3. print(rs)
  4. #在传入参数的时候,指定函数的功能为加法
  5. x_y_comp(3, 5, lambda x,y:x+y)
  6. print("---")
  7. #在传入参数的时候,指定函数的功能为减法
  8. x_y_comp(10, 6, lambda x, y:x-y)

运行结果为:

  1. 8
  2. ---
  3. 4

(2)匿名函数作为python内置函数的参数使用

例如:

  1. #定义一个字典列表
  2. user_infos = [{"name":"zhangsan", "age":20}, {"name": "lisi", "age":30}, {"name":"wangwu", "age": 18}]
  3. #按指定关键字排序,注意:通过匿名函数去出每个字典的age成员,参与排序,字典本身不能排序
  4. user_infos.sort(key=lambda info:info["age"], reverse=True)
  5. print(user_infos)

运行结果为:

  1. [{'name': 'lisi', 'age': 30}, {'name': 'zhangsan', 'age': 20}, {'name': 'wangwu', 'age': 18}]