函数用法进阶

Posted ddzc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数用法进阶相关的知识,希望对你有一定的参考价值。

函数用法进阶

一、函数作为参数和变量来使用

函数名表示的是函数存储在内存中的地址,函数加上括号才是函数。例如:main()表示的是函数, main是该函数的函数名,print(main)得到的是main()的内存地址,原理等同于直接赋值,可以把函数名赋值给一个变量使用,可以把函数名作为参数来使用,也可以函数名作为返回值来进行传递,等到调用的时候在加上括号,直接就可以调用函数了。

def f1():
    print('麻花腾')
def f2():
    print('马云')
def f3():
    print('马克龙')
def f4():
    print('马斯克')
list = [f1,f2,f3,f4]
# 注意函数名可以作为变量被赋值或者作为元素传递给其他的数据类型
# 函数名不加括号打印出来的是内存地址,list列表中是四个函数的内存地址
# 加上括号才是函数
print(list)
# 结果是:内存地址组成的列表
[<function f1 at 0x10e636710>, <function f2 at 0x10e6367a0>, <function f3 at 0x10e6369e0>, <function f4 at 0x10e636b00>]
for fx in list:
    fx()
# 直接调用每一个函数,结果是:
麻花腾
马云
马克龙
马斯克

'''同样的案例'''
def fun():
    def inner():
        print('这是一个内层函数')
    return inner
# 外层函数返回的是内层的函数名
ret = fun()
# 函数返回值赋值给变量
ret()
# 加上括号直接调用内层函数输出结果
# 输出的结果是:
这是一个内层函数

二、闭包

  • 基本概念:内层函数对于外层函数中的变量的引用,闭包的一个很重要的作用是在python中让外层函数的变量常驻内存,后续可以调用。正常的函数程序当执行完毕后,变量的内存将会被清空,但闭包会让内存常驻。

    # 闭包
    def func():
      name='alex'
      def inner():
          print(name)
        # 在内层函数中调用了外层函数中的变量,形成闭包
      return inner
    ret = func()
    ret()
    
  • 闭包和全局变量的区别: 闭包使得程序中更加安全

    def func():
      name = 'wusir' # 常驻内存
      def inner():
          print(name)
          # 闭包调用外层函数
          return name
      def inner1():
          nonlocal name 
          # 如果希望修改外层函数中的变量,必须在外层函数
          # 中再另外创建一个哦函数来重新赋值,相比较全局变量任何函数都可以
          # 赋值,闭包只能在一个函数内重新赋值更加安全。
          name = 'alex'
          return name
      return inner, inner1
    ret = func()
    print(ret[0]())
    print(ret[1]())
  • 闭包的好处:

    • 常驻内存,后边可以直接使用数据,不需要多次的获取。例如爬取一个网站的信息,如果网站信息变化不大的情况下,一次爬取后可以使用很长的时间,不必每次都要再爬取一次。

      from urllib.request import urlopen
      def but():
          content = urlopen('https://www.cnblogs.com').read()
        # 打开并读取url内容
          def get_content():
              return content 
        # 闭包调用外层函数的变量,返回
          return get_content
      con = but()
      print(con().decode('utf-8'))
      # 爬取过来的网址内容是二进制文件需要解码为utf-8格式
      # 后边再使用资源的时候直接调用函数就可以得到常驻内存中的网站信息了
    • 使得数据更加的安全

  • 判断一个函数是否是闭包,可以通过__closure__来查看

    print(get_content.__closure__)
    # get_content是需要进行判断的函数
    # 返回值:
    (<cell at 0x10bdb7d90: bytes object at 0x10bd15000>,)
     # 这个返回值表示的是闭包。如果不是闭包返回值是None
    

三、迭代器

  • 所有包含__iter__()方法的数据类型都是可迭代的数据类型,可以对对象是否可迭代进行判断,dir(对象)查看对象包含的所有的方法和属性, in方法来进行判断。Iterable可迭代的。iter()的作用是返回一个迭代器。

    str = '12345'
    print('__iter__' in dir(str))
    # 每一个可迭代的数据类型中都包含了一个__iter__()方法
    print(dir(str))
    num = 123
    print("__iter__" in dir(num))
    # 数值不是可迭代的数据类型,所以不包括__iter__()方法
  • 迭代器的简单使用__next__()

    list = ['篮球', '足球', '乒乓球']
    it = list.__iter__()
    # 获取迭代器
    print(it.__next__())
    print(it.__next__())
    print(it.__next__())
    #print(it.__next__())
    # 从迭代器中往外拿元素,用__next__()方法
    print(it.__next__())
    # 迭代到最后一个元素后,再进行迭代程序就会报错
    篮球
    Traceback (most recent call last):
      File "/Volumes/workspace/python-study/re_st/函数测试.py", line 188, in <module>
    足球
    乒乓球
        print(it.__next__())
    StopIteration
  • 处理循环迭代中出现的StopIteration

    ls = ['胖熊猫', '乌龟大师', '天煞', '师傅']
    it = ls.__iter__()
    while True:
      # 没从迭代器中拿一个元素,必须使用一次__next__()方法,用while True来循环执行
      try:
          print(it.__next__())
      except StopIteration:
    
          print('迭代已完成')
          break
    # 迭代器在最后一个迭代完成之后还会继续执行__next__()方法,
    # 这样会导致程序报StopIteration错误,需要设置异常程序
  • 判断一个对象是否是可迭代的和是否是迭代器用isinstance()方法,当对象中只包含__iter__()方法,那么这个对象是可迭代的,如果同时还包括__next__()方法,那么这个对象同时也是迭代器。

    from collections import Iterable
    from collections import Iterator
    # 导包的时候需要注意collections,不是单数
    
    ls = ['胖熊猫', '乌龟大师', '天煞', '师傅']
    print(isinstance(ls, Iterable))
    # 判断列表对象是否是一个可迭代的对象 True
    print(isinstance(ls, Iterator))
    # 判断列表是否是一个迭代器 False
    # 格式:isinstance(对象,类型),isinstance(A,B)判断A是否是B的实例
    it = ls.__iter__()
    print(isinstance(it, Iterable))
    print(isinstance(it, Iterator))
    # 迭代器既是可迭代对象,也是迭代器,迭代器一定是可迭代的
    #"__iter__" in dir(it) 迭代器中也包含着__iter__方法、__next__方法
  • python的所有的可迭代数据类型中只有文件句柄和range(1, 10)既是可迭代对象,又是迭代器

    f = open('../re_st/article/file.txt', mode='r', encoding='utf-8')
    # f是文件句柄
    print(isinstance(f, Iterable))
    print(isinstance(f, Iterator))
    # 文件句柄既是可迭代对象,也是迭代器,不同于其他的数据类型,这一点需要格外的注意
    # 结果是:True, True
  • 迭代器的特点:

    • 可以节省内存

    • 惰性机制,如果希望从迭代器里取值,必须用__next__()方法,否则无法取值,并且每取一次值就需要用到一次__next__()方法

    • 不能反复,只能向下执行,即只能从头往后走,不能从后往前走,执行完一次程序后,迭代器自动销毁,如果希望再次取值,必须重新生成迭代器再取值。

      ls = ['哈哈', '呵呵']
      it = ls.__iter__()
      print(it.__next__())
      print(it.__next__())
      print('迭代结束')
      print(it.__next__())
      # 结果是:
      哈哈
      呵呵
      迭代结束
      Traceback (most recent call last):
        File "/Volumes/workspace/python-study/re_st/迭代器与生成器.py", line 40, in <module>
          print(it.__next__())
      StopIteration
      # 结束后再次迭代报错:迭代已结束,必须重新生成迭代器

    四、生成器

    • 生成器分为三种:生成器函数、列表生成式和数据转换生成

    • 生成器函数中yield的用法,yield将函数截成多段,分段执行。最后一个yeild后不要再写程序和传值,如果写程序,系统会报StopIteration错误,

      def func():
          print('这是一个函数')
          return '函数'
      func()
      # 现在调用func函数,并没有打印返回值
      
      def func1():
          print('这是一个生成器函数')
          yield '生成器函数'
          # 把return改成yeild,该函数就从普通的函数变成了生成器函数
          print('第二个打印')
          yield '第二次'
      g = func1()
      print(g.__next__())
      print(g.__next__())
      # 生成器函数和迭代器一样,都具有__next__()方法
      # 这里需要注意的是return和yield的区别,return之后程序执行结束,但是yield
      # 之后还能继续执行程序,可以让函数分段执行,下次执行的开始是本次执行结束的地方。这是两者的一个区别
    • 生成器函数中的send用法, 与yield配合使用,对比send和next用法的区别。send和__next__()的作用一样,都是让程序从上一次yield结束的,地方继续向下执行一个yield,但与yield的用法不同的是:send使用的时候可以传值给上一个yield,next()不能。并且由于send需要传值给上一个yield,send前必须有yield,所以如果使用send来取值,第一次取值的时候还必须是next方法,send运行不了。最后一个yield后不能再传值,否则也会报错。

      def func():
          print("狗不理")
          a = yield '11'
          print(a)
          # send中的值1传递给了变量a
          print('大米粥')
          b = yield '22'
          print('小麻花')
          c = yield '33'
      
      g = func()
      
      print(g.__next__())
      print(g.send(3))
      print(g.__next__())
      # send和__next__()的作用一样,都是让程序从上一次yield结束的
      # 地方继续向下执行一个yield,但与yield的用法不同的是:
      # send使用的时候可以传值给上一个yield,__next__()不能
    • 列表生成器:基本思路是先用yield创建生成器函数,在把生成器函数转换成列表,列表生成器必须在生成器函数的基础上才能实现。

      def func():
          print('程序1')
          yield '1'
          print('程序2')
          yield '2'
          print('程序3')
          yield '3'
          print('程序4')
          yield '4'
          print('程序5')
          yield '5'
          print('程序6')
          yield '6'
      g = func()
      list = list(g)
      # 可迭代对象,运行时内部一定运行了__next__()方法
      print(list)
      # 结果是:
      程序1
      程序2
      程序3
      程序4
      程序5
      程序6
      ['1', '2', '3', '4', '5', '6']
      # 注意:列表生成器是中形成列表的是多个yield的返回值,不是打印的内容。
    • 列表推导式, 基本格式:[最终结果(变量) for 变量 in 可迭代对象 ? 判断条件]

      # 列表推导式
      list = ['python%s' % i  for i in range(1, 15)]
      print(list)
      # 得到的结果:
      ['python1', 'python2', 'python3', 'python4', 'python5', 'python6', 'python7', 'python8', 'python9', 'python10', 'python11', 'python12', 'python13', 'python14']
      '''
      原生的遍历循环:
      '''
      list = []
      for i in range(1, 15):
        list.append('python%s' % s)
      # 得到的结果:
      ['python1', 'python2', 'python3', 'python4', 'python5', 'python6', 'python7', 'python8', 'python9', 'python10', 'python11', 'python12', 'python13', 'python14']
      
      '''
      加上判断条件和多层嵌套循的列表推导式:
      '''
      
      # 从names中找出所有姓马的名字,这里是列表嵌套
      names = [['马云', '张绎', '麻花腾', '王健林'],['陈导民', '达尔文', '爱因斯坦', '居里夫人', '马龙']]
      list = [name for first in names for name in first if name[0]=='马']
      # 列表推导式就是后边其实就是for循环,把正常的需要用到的多层循环放在了列表中
      # 循环前边是对生成的新列表元素的要求。
      print(list)
      
      # 结果是:
      ['马云', '马龙']
    • 生成器表达式:生成器表达式是在列表推导式的基础上演化而来,把列表推导式中的[ ]换成( )即可实现

      g = ('衣服%s' % i for i in range(1,10) if i % 2 == 0)
      # 生成器表达式,和列表推导式的区别就在于方括号和小括号,其他没有区别
      print(g)
      while 1:
          try:
              print(g.__next__())
          except StopIteration:
              print('迭代完成')
              break
      # 结果是:
      <generator object <genexpr> at 0x10359df50>
      # 这个是生成器表达式的所在的内存地址
      衣服2
      衣服4
      衣服6
      衣服8
      迭代完成

      Note

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

      1、列表推导式比较耗内存,一次性的加载。生成器表达式几乎不占用内存,使用的时候才分配和使用内存

      2、得到的值也不一样,列表推导式得到的值式一个列表,生成器表达式得到的值式一个生成器。

      3、生成器和迭代器一样都具有惰性机制,即只有使用__next__()方法取值的时候才有值,否则无法取值。

    • 其他推导式

      '''
          字典推导式: 用花括号
      '''
      list1 = ['k1', 'k2', 'k3', 'k4']
      list2 = ['python', 'Java', 'C', 'C#']
      dict = { list1[i]: list2[i] for i in range(len(list1)) }
      print(dict)
      # 结果是:
      {'k1': 'python', 'k2': 'Java', 'k3': 'C', 'k4': 'C#'}
      
      '''
          集合推导式:用花括号,只有key,没有value
      '''
      # 集合推导式
      list = ['麻花腾', '马云', '汪建中', '王健林','马云']
      set1 = {name for name in list}
      print(set1)
      # 结果是:
      {'王健林', '汪建中', '马云', '麻花腾'}
      # 注意:没有元组推导式:元组是只能进行查询,不能进行增删改, 并且元组是小括号,小括号扩起来在表达式中是生成器

      Note

      生成器的本质就是迭代器

      生成器函数:yield和send用法

      列表生成器: 在yield的基础上把生成器函数返回的内容通过list()的方式来转换成列表

      生成器表达式:直接通过小括号来实现,把推导式用小括号扩起来就是生成器表达式

      推导式:(推导式不是生成器)

      • 列表推导式:[ ]表示
      • 字典推导式: { }表示,但是必须是包含key,value
      • 集合推导式: { }表示,只包含key,没有value

以上是关于函数用法进阶的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5信号与槽用法进阶

C语言进阶:字符串和内存函数

Python进阶-----property用法(实现了get,set,delete三种方法)

Scala 进阶—— implicit 用法:隐式参数

C语言进阶:动态内存管理

python3进阶之*args与**kwargs用法