python函数对象-命名空间-作用域-02

  • 2019 年 10 月 7 日
  • 筆記

函数对象

函数是第一对象: # 函数名指向的值可以被当做参数传递

函数对象的特性(*****灵活运用,后面讲装饰器会用到)

函数名可以像变量一样被传递

# 变量可以被传递  name = 'jason'  x = name  print(x)  # jason  print(id(name), id(x))  # 3085464224688 3085464224688    # 尝试函数像变量一样被传递  def func():      print('from func')  print(func)  # <function func at 0x0000016E5E062E18>    f = func  # 其实指向的也是函数func 指向的函数体代码的内存地址  print(f)  # <function func at 0x000001B4D0D92E18>  f()  # from func  print(id(func), id(f))  # 3085463137816 3085463137816

函数名可以被当做参数传递给其他函数

def func():      print("from func")    def index(args):      print(args)      args()  # 函数主要一定义(先定义)就可以在任意地方调用      print("from index")    index(func)  # <function func at 0x000001B7429A2E18>  # from func  # from index

函数名可以被当做函数的返回值

def index():      print('index')    def func():      print('func')      return index    res = func()  # 将返回的函数名index 赋值给变量res  # func  print(res)  # <function index at 0x000001EF64362E18>  res()  # index

  函数名可以被当做容器类型的参数

def func():      print('func')    l = [1, 2, func, func()]  # 定义的时候默认执行了func(),所以下面先打印了 func  # func  # func函数没有返回值,所以默认是None  print(l)  # [1, 2, <function func at 0x0000013931C92E18>, None]

函数对象小练习

题目: # 循环打印项目功能提示信息 供用户选择 用户选择谁就执行谁

def register():      print("注册了")      pass      def login():      print("登录了")      pass      def shopping():      print("购物了")      pass      def output_func_list():      print("----- 请选择功能!")      for key in func_list:          print(f"----  {key}.{func_list[key][1]}")      func_list = {      0: [register, '注册'],      1: [login, '登录'],      2: [shopping, '购物'],  }      while True:      output_func_list()      chose_func = input("请输入功能编号(q 退出系统):").strip()      if chose_func.isdigit():          # 执行相应功能          chose_func = int(chose_func)          # 判断输入的编号在不在功能列表里          if chose_func in func_list:              func_list[chose_func][0]()  # 取到功能函数名,加括号调用          else:              print("您输入的功能编号不存在,请重新输入!")      elif chose_func.lower() in ['q', 'quit']:          print("感谢您的使用,祝您生活愉快~")          break      else:          print("请正确输入数字!")

知识点: # 函数名可以作为容器对象的元素值 , # 函数名(即函数内存地址)可以加括号直接调用

  上述其他三个特性在装饰器中会有灵活运用,就暂不举例了

函数的嵌套调用与定义

嵌套调用

函数的嵌套调用: # 在函数内部调用其他函数

def index():      print('index')    def func():      index()  # 在定义 func 函数的时候不会直接调用 index 的方法 --> 函数定义的时候不执行代码      print('func')    func()  # index  # 通过 func()函数内部调用了index() 函数,打印出了 index  # func

函数的嵌套调用可以 # 将复杂的逻辑简单化

小练习: # 写一个函数可以求四个数中的最大值

def my_max(x, y):      if x > y:          return x      return y    def my_max4(a, b, c, d):      res = my_max(a, b)      res = my_max(res, c)      res = my_max(res, d)      return res    print(my_max4(1, 5, 7, 1))  # 7

嵌套定义

def outer():      x = 1      print("outer")      def inner():          print("inner")      inner()    # inner()  # 会报错,在外部无法访问内部内容  outer()  # outer  # inner

实现在外部调用 outer函数的内部函数 inner

# 想在外部调用inner 可通过把内部的函数名当做外部函数的返回值来返回给外部  def outer():      x = 1      print("outer")      def inner():          print("inner")      return inner  # 把 inner 函数当做函数的返回值返回给 outer函数的调用者    res = outer()  # outer  res()  # 变相调用inner  # inner

小案例: # 写一个函数,该函用户可以通过传参的不同 控制函数指向不同的功能

def all_func(type):      def register():          print('register')      def login():          print('login')      def shopping():          print('shopping')        if type == 1:          register()      if type == 2:          login()      if type == 3:          shopping()    all_func(1)  all_func(2)  all_func(3)  # register  # login  # shopping

名称空间(****绕且重要)

 名称空间: # 存放的是变量名与变量值的内存地址绑定关系的地方 ,后文可能称之为命名空间。

 访问变量的值: # 要想访问一个变量的值,必须先去名称空间拿到对应的名字,才能访问变量的值

命名空间的分类

 命名空间分为: # 内置名称空间、全局名称空间、局部名称空间 三大类

 内置命名空间

内置名称空间: # python 解释器提前已经定义好了的名字(已经存放到了内置名称空间中了)

print("hello world")  max(1, 44, 62, 15)  len('26515f1asfafqw')  sum([1, 2, 3, 4, 5])  # 像上面的print max len sum 并没有定义就可以值使用,它们就是python解释器提前定义好了的函数,属于内置命名空间的

 全局命名空间

 全局命名空间: # 文件级别的代码

x = 1  if x == 1:      y = 2  print(y)  # 2    for i in [1, 2]:      print(i)  print(i)  # 1  # 2  # 2    # 上面的 x y z 都在全局名称空间,不要以为缩进的就是局部的(if、 for、 while 无论嵌套,多少层,他们内部所创建的名字都是全局名称空间的)

 局部命名空间

 局部命名空间: # (目前所学)函数体内创建的名字都属于局部名称空间(最外层的函数名是属于全局名称空间的)

def func():      username = 'jason'  # print(username)  # 会报错 NameError: name 'username' is not defined  func()

  至于为什么上面的 print(username) 为什么会报错,学完下面的知识你就知道啦。

命名空间的生命周期

'''  名称空间的生命周期      内置名称空间:(最长)只要 python解释器启动,立马创建  关闭 python解释器时自动销毁      全局名称空间: 只要右键运行 py文件就会自动创建  py文件程序运行结束自动销毁      局部名称空间:(动态创建动态销毁)函数被调用的时候自动创建  函数执行结束后立即销毁  '''

补充:与垃圾回收机制的关系

# 名称空间生命周期结束 -- >  里面存的变量与指向值的内存地址解绑,内存中的值等待垃圾回收机制回收  # def 删除变量 -- >  里面存的变量与指向值的内存地址解绑,内存中的值等待垃圾回收机制回收  ---> 等同于名称空间里删除了一个变量(绑定关系)  # 垃圾回收机制:垃圾回收机制隔一段时间就会检查一次,内存中的值如果没有变量指向它(引用),那垃圾回收机制就会把它清除掉(释放内存)  #             如果多次检查都有变量等指向它,那就会把它等级提升,检查频率就会变低

 命名空间的查找顺序

 验证思路: # 找一个三个地方都有的东西来验证(比如 len、max等,暂时忽略命名规范不能与关键字重复) , # 分别注释来测试其查找顺序(全局、局部)

 验证过程

len = '我是全局名称空间的len'  def func():      len = '我是局部名称空间的len'      print(len)  print(len)  # 这里是全局的位置  # 我是全局名称空间的len  '''  # 把全局的len 注释掉,就去找了内置的len  print(len)  # 是全局的位置  # <built-in function len>  '''  func()  # 我是局部名称空间的len

 大致结论:

'''  (******)名称空间的查找顺序      1.需要先确定当前的在哪(全局、局部),大前提          1.1 站在全局:全局 >>> 内置          1.2 站在局部:局部 >>> 全局 >>> 内置            1.2.2 站在局部的内部(多个局部嵌套):局部 >>> 上一级局部 >>> 上一级局部 >>> .... >>> 全局 >>> 内置                会在作用域同级的前后(这句代码前后的同级语句)去找,然后再上一级      2.函数在定义阶段查找名字的顺序(范围)就已经固定了, 不会因为函数的调用位置变化而变化(*******)            可以在函数定义的时候写个注释,指出他查找的位置,防止逻辑复杂了搞不清楚  '''

加深理解的小案例

# 通过注释不同函数层内的x 来加深理解命名空间查找顺序(可以采用收起(折叠)代码块的技巧来快速指定)  x = 111  def f1():      x = 222      def f2():          x = 333          def f3():              # x = 444              def f4():                  # x = 555                  print(x)  # 这个案例在本局部找到了 变量x, 所以用的是内部的这个 777,在调用前定义了,所以不会报错              x = 777  # 纯粹为了教学演示              f4()              x = 777  # 纯粹为了教学演示          f3()      f2()  f1()  # 777
def func():      x = 1      def index():          print(x)  # 查找顺序:本作用域找x,没找到,上一级func里找,找到了,那就引用的是func 作用域里的 局部变量x      return index    res = func()  x = 999  res()  # 1
x = 111  def outer():      def inner():          print('from inner', x)  # 查找顺序:函数体inner 内上下没有x,再找 outer里面,也没有x, 那就找全局,找到了x,所以这里的x 就是全局的x      return inner  f = outer()  x = 222  # 调用前改变了全局 x 的值,所以最后结果是 222  f()  # from inner 222
x = 111  def outer():      def inner():          print('from inner', x)  # 查找顺序:函数体inner 内上下没有x,再找 outer里面,也没有x, 那就找全局,找到了x,所以这里的x 就是全局的x      return inner  f = outer()  def func():      x = 333  # 没有global 关键字指定x 为全局变量,这里是重修申请了一个局部变量 x,这里并不会影响 全局的那个x      f()  func()  # from inner 111
# 下面这个案例会直接报错  x = 111  def outer():      def inner():          print('from inner', x)  # 会直接报错,UnboundLocalError: local variable 'x' referenced before assignment ---> 本地变量(局部变量)x 在定以前被引用了          # 报错原因: ---> 查找顺序:函数体 inner内部有 x, 所以这个print 内x 指定的是x = 66666666的那个局部变量,而调用print 时,他还没定义出来          x = 66666666      return inner  f=outer()  f()

案例一原理图

作用域

python中的作用域有 全局作用域 与 局部作用域 , 全局作用域: # 全局有效: 内置名称空间、全局名称空间 都属于全局作用域 , 局部作用域: # 局部有效:局部名称空间

局部修改全局变量(修改和访问是两回事)

# 尝试修改不可变类型的全局变量  x = 1  def func():      x = 2  # 实质是又创建了一个局部变量 x  func()  print(x)  # 局部无法修改不可变类型的全局变量  # 1    # 尝试修改可变类型的局部变量  x = []  def func():      x.append('嘿嘿嘿')  func()  print(x)  # 修改成功,局部可以修改可变类型的全局变量  # ['嘿嘿嘿']    # 全局访问不了局部的变量,所以不展开研究

    小结论: # 局部无法修改不可变类型的全局变量 , # 局部可以修改可变类型的全局变量 (前提:在不使用 global  和  nonlocal  关键字的情况下)

  通过  global  关键字在局部修改全局,修改多个用 , 隔开

x = 1  # 不可变类型  username = 'jason'  def func():      global x,username      x = 999  # 修改全局变量,而不是创建局部变量      username = 'egon'  func()  print(x, username)  # 999 egon

  通过  nonlocal  关键字在局部修改局部,修改多个用 , 隔开

def func():      x = 1      def index():          x = 2      index()      print(x)  func()  # 1    # 想就在 index 里把 x 改了  def func():      x = 1      def index():          nonlocal x          x = 2      index()      print(x)  func()  # 2