# 例:编写一个函数,能至少接受两个参数,返回最大值和最小值
# max可以传可迭代对象 *参数是将参数每个都解开类似的像是做一个循环取数,相当于传了好几个参数,而如果没有用*则表示只有一个参数
# *的理解
# def add(*x):
# print(x) # 将x封装成tuple,如果传【1,2】,则是一个元素,如果是*【1,2】那么是两个元素;类似与(【1,2,3,4】,)和【1,2,3,4】的区别)
# return max(x)
# print(add([2,4]),add(*[1,3]))
#
# import random
# def double_values(*num):
# print(num,type(num))
# return max(num),min(num)
# print(double_values(*[1,2,3,4,5]))
# print(*double_values([random.randint(10,20) for i in range(10,20)]))
# 函数作用域 Local-Enclosing-Globle-Build-in
# 原则:外部变量内部可见,内部变量外部不可见,内部变量高度自治
# 错误示例
# x = 1
# def foo():
# y = x + 1
# print(y)
# x += 1
# print(x)
# foo()
# 解决方案,将x扩大作用域,但是要global x 要写在最上面
# x = 1
# def foo():
# global x
# y = x + 1
# print(y)
# x += 1
# print(x)
# foo()
# 自由变量:未在本地作用域中定义的变量,例如定义在内层函数外的外层函数中的变量
# 闭包:出现在嵌套函数中,指的是内层函数应用到了外层函数的自由变量,对内层函数就形成了闭包;简而言之就是内层函数用到了外层函数的本地变量
# 按道理来说 函数执行完以后,局部变量就应该消失了,但是我们拿到了内部函数的引用,inner函数不消亡,inner用到外部变量,才产生了闭包;
# nonlocal:是将变量标记为不在本地作用域定义,而在上级的某一级局部作用域中定义,但不能是全局作用域中定义,形成了闭包
# def counter():
# c = [0]
# def inc():
# c[0] += 1
# print(c[0])
# return c[0]
# return inc
# foo = counter() #<function counter at 0x0000027202B12F78>
# foo()
# foo()
# c = 100
# print(foo())
# 默认值作用域:存了一个属性,伴随着函数的整个周期
# def foo(xyz=[]):# 虽然xyz是形式参数,但它是局部变量
# xyz.append(1)
# print(xyz)
# foo() # [1]
# foo() # [1,1] # 因为函数也是对象,python把函数的默认值放在了属性中,这个属性伴随着这个函数的整个生命周期(当前作用域)
# #当前作用域下,函数对象还没有消亡,而因为是默认值参数,它存到了对象的属性中,xyz是临时的本地变量,执行结束就消亡了;因为属性中存的是列表,所以【1,1】
# print(foo.__defaults__)
# def foo(xyz=[],m=5,y=6):
# xyz.append(100)
# print(xyz)
# print(foo(),id(foo))
# print(foo.__defaults__)
# print(foo(),id(foo))
# print(foo.__defaults__)
# 属性__defaults__中使用元组保存所有位置参数默认值,它不会因为存在函数体内使用了它而发生改变
# def foo(w,u='abc',z=123):
# u = 'xyz'
# z = 345
# print(w,u,z)
# print(foo.__defaults__)
# foo('sddd')
# print(foo.__defaults__)
# 默认值作用域
# 1.使用可变类型作为默认值,这个可能修改默认值
# def foo(xyz=[],u='aaa',z=123):
# # xyz = xyz[:] # 重新赋值的方式,这个时候默认值不会改变
# xyz.append(1)
# print(xyz)
# foo()
# print(1,foo.__defaults__)
# foo()
# print(2,foo.__defaults__)
# foo([10])
# print(3,foo.__defaults__)
# foo([10,5])
# print(4,foo.__defaults__)
# 2,使用不可变类型的默认值********
# 如果使用缺省值None就创建一个列表
# 如果传入一个列表,就修改这个列表
# def foo(xyz=None,u='abc',z=123):
# if xyz==None:
# xyz = []
# xyz.append(1)
# print(xyz)
#
# foo()
# print(1,foo.__defaults__)
# foo()
# print(2,foo.__defaults__)
# lst = [100]
# foo(lst)
# print(3,foo.__defaults__)
# 总结 :1.使用影子拷贝创建一个新的对象,永远不能改变传入的参数
# 2.通过之的判断就可以灵活的选择创建或者修改传入对象,极其广泛,很多函数的定义都是使用None这个不可变的值作为默认值
# 3.函数的形参就是本地变量,如果你用的是默认值,那么就存到foo.__defaults__属性中,用元组存
# 属性__kwdefaults__中使用字典保存所有的keyword-only参数的默认值
# 函数的销毁
# 全局函数的销毁 1.重新定义 2.del销毁 3.程序结束
# 局部函数的销毁 1.重新在上级作用域定义的同名函数 2.del语句 3.上级作用域销毁
# def foo(xyz=[],u='abc',z=123):
# xyz.append(1)
# def inner(a=10):
# pass
# def inner(a=100):
# print(xyz)
# print(inner)
# return inner
# bar = foo()
# print(id(foo),id(bar),foo.__defaults__,bar.__defaults__)
# del bar
# print(id(foo),id(bar),foo.__defaults__,bar.__defaults__)
# 函数执行流程
#
# def foo1(b,b1=3):
# print("foo1 called",b,b1)
# def foo2(c):
# foo3(c)
# print("foo2 called",c)
# def foo3(d):
# print("foo3 called",d)
# def main():
# print("main called")
# foo1(100,200)
# foo2(200)
# print("main ending")
# main()