• 记录我的 python 学习历程-Day11 两个被忽视的坑、补充知识点、函数名的应用、新版格式化输出、迭代器


    补充知识点

    • 函数形参中默认参数的陷阱

      • 针对不可变数据类型,它是没有陷阱的

        def func(name, sex='男'):
            print(name)
            print(sex)
        
        func('Dylan')
        
        # 输出结果:
        Dylan
        男
        
      • 陷阱只针对默认参数是可变的数据类型

        def func(name, a_list=[]):  # a_list=[]不在全局名称空间里,也不在局部名称空间里,它在一个特殊的内存空间中
            a_list.append(name)     # 将实参传进来的 name 增加到 a_list 这个空列表里.
            return a_list           # 返回 a_list 这个列表
        
        ret = func('Dylan')
        print(ret, id(ret))     # ['Dylan'] 4344392712
        ret2 = func('xiaobai')
        print(ret2, id(ret2))   # ['Dylan', 'xiaobai'] 4344392712
        
        # 如果你的默认参数指向的是可变的数据类型,那么你无论调用多少次这个默认参数,都是同一个.
        

        面试题-1 ***

        def func(a, list=[]):
            list.append(a)
            return list
        
        print(func(10,))        # [10]
        print(func(20, []))     # [20]
        print(func(100,))       # [10, 100]
        
        # 在传参的时候,如果使用默认参数传参,那么无论你调用多少次,使用的都将是同一个 id,如果你将默认参数覆盖传入,将会重新创建另一个 id.
        

        面试题-2 ***

        def func(a, list=[]):
            list.append(a)
            return list
        
        ret1 = func(10,)
        ret2 = func(20, [])
        ret3 = func(100,)
        
        # ret1 和 ret2 和 ret3 都执行完毕后,其 ret1和 ret3其实是同一个列表,那当然就是 ret3改变 ret1也跟着改变了.
        
        print(ret1)     # [10, 100]
        print(ret2)     # [20]
        print(ret3)     # [10, 100]
        
    • 局部作用域当中的坑

      • 先引用,后定义的坑

        # 在函数中,如果定义了一个变量,但是在定义这个变量之前对其引用了,那么解释器就会认为这是一个语法问题.
        # 你应该在使用之间先定义.
        
        cont =1
        def func():
            print(cont)
            cont = 3
            
        func()
        # 此时就会报错了,因为你是先引用后定义的
        

    globlal 和 nonlocal 关键字

    • global

      • 在局部作用域声明一个全局变量

        def func():
            global name	# 在局部作用域中声明一个全局变量
            name = 'Dylan'
            print(name) # Dylan
        
        func()
        print(name)     # Dylan
        
        # 这样用 global 在函数里声明一下,那么在全局作用域中就可以找到局部作用域中的变量了
        
      • 修改一个全局变量

        cont = 1
        def func():
            global cont
            cont += 1
        
        print(cont)     # 1   这之所以是1,是因为函数还没有执行,此时打印的是全局作用域中原有的 cont
        func()
        print(cont)     # 2   这之所以是2,是因为,函数执行了,在函数里定义了全局变量,函数里把 cont 自加了个1,所以此时打印的 cont 就变成了2.函数内的自加改变了函数外的变量.就是因为 golbal在函数中定义了全局变量.
        
    • nonlocal

      • 不能够操作全局变量

        cont = 1
        def func():
            nonlocal cont
            cont += 1
        func()
        
        print(cont)
        
        # 报错了 SyntaxError: no binding for nonlocal 'cont' found
        
      • 局部作用域:内层函数对外层函数的局部变量进行修改

        def wrapper():
            cont = 1
            def inner():
                nonlocal cont
                cont += 1
            print(cont)     # 1
            inner()
            print(cont)     # 2
        wrapper()
        

    函数名的运用

    ​ 函数名的定义和变量的定义几乎一致,在变量的角度,函数名其实就是一个变量,具有变量的功能:可以赋值;但是作为函数名他也有特殊的功能就是加上()就会执行对应的函数,所以我们可以把函数名当做一个特殊的变量,那么接下来,我们就来研究一下这个特殊的变量。

    • 函数的内存地址

      函数名指向的是这个函数的内存地址

      def func():
          print('Dylan')
      print(func)
      
      # 结果:<function func at 0x102020e18>
      
      

      其实深一步理解可得知,与其说函数名()可以执行这个函数,不如说是函数的内存地址()才是执行这个函数的关键,就好比:

      a = 1
      b = 2
      c = a + b
      print(c)  # 3
      

      a + b 并不是变量的相加,而是 两个变量指向的int对象的相加。

    • 函数名可以赋值给其它变量

      def func():
          print("Dylan")
      print(func) 
      a = func    # 把函数当成一个变量赋值给另一个变量
      a()         # 函数调用 func()
      

      通过变量的赋值,变量a,和变量func都指向的这个函数的内存地址,那么a() 当然可以执行这个函数了。

    • 函数名可以当做容器类的元素

      函数名就是一个变量,我的变量是可以当做容器类类型的元素的:

      def func1():
          print("in func1: 嘻嘻")
      def func2():
          print("in func2: 哈哈")
      def func3():
          print("in func3: 咯咯")
      def func4():
          print("in func4: 吱吱")
      lst = [func1, func2, func3, func4]
      for i in lst:
          i()
          
      # 结果:
      # in func1: 嘻嘻
      # in func2: 哈哈
      # in func3: 咯咯
      # in func4: 吱吱
      
    • 函数名可以当做函数的参数

      变量可以做的,函数名都可以做到

      def func1():
          print('in func1')
      
      def func2(f):					# 这相当于 func1
          print('in func2')			# in func2
          f()		#这相当于func1()	# in func1
      
      func2(func1)
      
    • 函数名可以作为函数的返回值

      def func1():
          print('in func1')
      
      def func2(f):
          print('in func2')
          return f
      
      ret = func2(func1)
      ret()  # ret, f, func1 都是指向的func1这个函数的内存地址
      

      小结:函数名是一个特殊的变量,他除了具有变量的功能,还有最主要一个特点就是加上() 就执行,其实他还有一个学名叫第一类对象

    Python新特性:f-strings格式化输出

    ​ f-strings 是python3.6开始加入标准库的格式化输出新的写法,这个格式化输出比之前的%s 或者 format 效率高并且更加简化,非常的好用,学完这个之后,以后再用格式化输出这就是你们唯一的选择。

    • 简单举例

      ​ 他的结构就是F(f)+ str的形式,在字符串中想替换的位置用{}展位,与format类似,但是用在字符串后面写入替换的内容,而他可以直接识别。

      ame = 'Dylan'
      age = 18
      sex = '男'
      msg = f'姓名:{name}, 年龄:{age}, 性别:{sex}'  # f不区分大小写
      
      print(msg)
      
      # 输出结果: 姓名:Dylan, 年龄:18, 性别:男
      
    • 任意表达式

      他可以加任意的表达式,非常方便:

      print(f'{3*21}')  # 63
      
      name = 'barry'
      print(f"全部大写:{name.upper()}")  # 全部大写:BARRY
      
      # 字典也可以
      teacher = {'name': 'Dylan', 'age': 18}
      msg = f"The teacher is {teacher['name']}, aged {teacher['age']}"
      print(msg)  # The comedian is Dylan, aged 18
      
      # 列表也行
      l1 = ['Dylan', 18]
      msg = f'姓名:{l1[0]},年龄:{l1[1]}.'
      print(msg)  # 姓名:Dylan,年龄:18.
      
    • 插入表达式

      可以用函数完成相应的功能,然后将返回值返回到字符串相应的位置

      def sum_a_b(a,b):
          return a + b
      a = 1
      b = 2
      print('求和的结果为:	' + f'{sum_a_b(a,b)}')     # 求和的结果为:	3
      
    • 多行 f(不常用)

      name = 'barry'
      age = 18
      ajd = 'handsome'
      
      # speaker = f'''Hi {name}.
      # You are {age} years old.
      # You are a {ajd} guy!'''
      
      speaker = f'Hi {name}.'
                f'You are {age} years old.'
                f'You are a {ajd} guy!'
      print(speaker)  # Hi barry.You are 18 years old.You are a handsome guy!
      
    • 其他细节

    这里有一些注意的细节,了解一下就行。
    
    ```python
    print(f"{{73}}")  # {73}
    print(f"{{{73}}}")  # {73}
    print(f"{{{{73}}}}")  # {{73}}
    m = 21
    # ! , : { } ;这些标点不能出现在{} 这里面。
    # print(f'{;12}')  # 报错
    # 所以使用lambda 表达式会出现一些问题。
    # 解决方式:可将lambda嵌套在圆括号里面解决此问题。
    x = 5
    print(f'{(lambda x: x*2) (x)}')  # 10
    ```
    
    **总结**:f-string的格式化输出更加简洁,方便,易读。而且他的处理速度对之前的%s 或者format 有了较高的提升,所以以后尽量使用此种格式化输出。
    

    可迭代对象和迭代器

    • 可迭代对象

      • 可迭代对象的定义

        字面意思:

        ​ 对象:python 中一切皆对象。一个实实在在存在的值,对象。

        ​ 可迭代:更新迭代。重复的,循环一个过程,更新迭代每次都有新的内容,可以进行循环更新的一个实实在在的值(或对象)。

        专业角度:在python中,但凡内部含有__iter__方法的对象,都是可迭代对象。

        目前学过的可迭代对象有:srt / list / tuple / dict / set / range / 文件句柄

      • 查看对象的内部方法 dir()

        ​ 该对象内部含有什么方法除了看源码,还可以通过 dir() 去判断一个对象具有什么方法。

        s1 = 'Dylan'
        print(dir(s1))
        # 结果:
        ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
        

        dir() 会返回一个列表,这个列表中含有该对象的以字符串的形式所有方法名。

        这样我们就可以判断python中的一个对象中是不是有__iter__方法,从而就可以判断这个对象是不是可迭代对象了:

        s1 = 'alex'
        i = 100
        print('__iter__' in dir(i))  # False
        print('__iter__' in dir(s1))  # True
        

        小结:

        • 从字面意思来说:可迭代对象就是一个可以重复取值的实实在在的东西。

        • 从专业角度来说:但凡内部含有'__iter__'方法的对象,都是可迭代对象。

        • 判断一个对象是不是可迭代对象: '__iter__' in dir(对象)

        • 优点:

          • 存储的数据直接能显示,比较直观。
          • 拥有的方法比较多,操作方便。
        • 缺点:

          • 占用内存。

          • 可迭代对象是不能迭代取值的(除去索引,key 以外)

            那么这个缺点有人就提出质疑了,即使抛去索引,key以外,这些我可以通过for循环进行取值呀!对,他们都可以通过for循环进行取值,其实for循环在底层做了一个小小的转化,就是先将可迭代对象转化成迭代器,然后在进行取值的。

    • 迭代器

      • 迭代器的定义

        • 字面意思:更新迭代,器:工具,可更新迭代的工具。
        • 专业角度:迭代器是这样的对象:实现了无参数的__next__方法,返回序列中的下一个元素,如果没有元素了,那么抛出StopIteration异常.python中的迭代器还实现了__iter__方法,因此迭代器也可以迭代。 ——出自《流畅的python》
        • 简单来说:在 python 中,内部含有'__iter__'方法,并且含有'__next__'方法的对象,就是迭代器。
      • 判断一个对象是否是迭代器

        # 方法如下:
        # print('__iter__' in dir(对象) and '__next__' in dir(对象))
        
        # 例子1:
        with open('文件', encoding='utf-8',mode='w') as f:
            print('__iter__' in dir(f) and '__next__' in dir(f))
        
        # 输出结果: True
        # 这说明,文件句柄是一个迭代器。
        
        # 例子2:
        o1 = 'alex'
        o2 = [1, 2, 3]
        o3 = (1, 2, 3)
        o4 = {'name': '太白','age': 18}
        o5 = {1, 2, 3}
        with open('文件', encoding='utf-8',mode='w') as f:
            
        print('__iter__' in dir(o1))  # True
        print('__iter__' in dir(o2))  # True
        print('__iter__' in dir(o3))  # True
        print('__iter__' in dir(o4))  # True
        print('__iter__' in dir(o5))  # True
        print('__iter__' in dir(f))  # True
        # 
        print('__next__' in dir(o1))  # False
        print('__next__' in dir(o2))  # False
        print('__next__' in dir(o3))  # False
        print('__next__' in dir(o4))  # False
        print('__next__' in dir(o5))  # False
        print('__next__' in dir(f))  # True
        
        # 这说明,上面这些数据类型(或者对象),除了文件句柄外,其它的都不是迭代器,只是可迭代对象。
        
      • 可迭代对象如何转化成迭代器 iter(可迭代对象)或者 可迭代对象.__iter__()

        s = 'Dylan'
        obj = s.__iter__()  # 或者 obj = iter(s)
        print(obj)  # <str_iterator object at 0x102894a90>
        
      • 迭代器取值

        l1 = [11, 22, 33, 44, 55, 66]
        obj = iter(l1)  # 把 l1列表转换为迭代器赋值给变量 obj。
        print(obj)      # <list_iterator object at 0x1032949e8>
        print(next(obj))    # 11
        print(next(obj))    # 22
        print(next(obj))    # 33
        print(next(obj))    # 44
        print(next(obj))    # 55
        print(next(obj))    # 66
        print(next(obj))    # 再多一个就会报错了。StopIteration 停止迭代
        
        # 迭代器利用 next 取值:一个 next 取对应的一个值,如果迭代器里面的值取完了,还要 next,那么就会报'StopIteration' 停止迭代的错误
        
      • while 循环模拟 for 循环机制

        l1 = [1, 2, 3, 4, 5, 6]
        # 1,将可迭代对象转化成迭代器。
        obj = iter(l1)
        # 2,利用 while 循环,next 进行取值。
        while 1:
            # 3,利用异常处理,终止循环。
            try:
                print(next(obj))
            except StopIteration:
                break
        
      • 小结:

        • 从字面意思来说:迭代器就是可以迭代取值的工具。

        • 从专业角度来说:在 python 中,内部含有'__iter__'方法并且含有'__next__'方法的对象就是迭代器。

        • 迭代器的优点:

          • 节省内存。

            迭代器在内存中相当于只占一个数据的空间,因为每次取值都会将上一条数据在内存中释放,加载当前的此条数据。

          • 惰性机制。

            next 一次,取一个值。

            (有一个迭代器模式可以很好的解释上面这两条:迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式。)

        • 迭代顺的缺点:

          • 速度慢,不能直观的查看里面的数据。

          • 取值时不走回头路,只能一直向下取值。

            l1 = [1, 2, 3, 4, 5, 6]
            obj = iter(l1)
            
            for i in range(2):
                print(next(obj))
            
            for i in range(2):
                print(next(obj))
                
            # 迭代器会记住当前程序最后一次取值的位置,下次取值时,会继续上次的位置进行取值。
            
    • 可迭代对象与迭代器的对比

      可迭代对象:

      ​ 是一个操作方法比较多,比较直观,存储数据相对少(几百万个对象,8G 内存是可以承受的)的一个数据集。

      应用:

      ​ 当你侧重于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。

      迭代器:

      ​ 是一个非常节省内存,可以记录取值位置,可以直接通过循环+next 方法取值,但是不直观,操作方法比较单一的数据集。

      应用:

      ​ 当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。

    人生苦短,我用 Python
  • 相关阅读:
    es6笔记6^_^generator
    es6笔记5^_^set、map、iterator
    es6笔记4^_^function
    es6笔记3^_^object
    关于eslint的使用与配置,以及prettier的使用
    关于查看本机ssh公钥以及生成公钥
    Github上传图片图床
    力扣剑指Offer:39. 数组中出现次数超过一半的数字
    计蒜客:求平均年龄Python方法
    力扣:面试题59. 滑动窗口的最大值Python题解
  • 原文地址:https://www.cnblogs.com/guanshou/p/12116730.html
Copyright © 2020-2023  润新知