亲宝软件园·资讯

展开

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

Dylan_Yu 人气:0

补充知识点

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

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

      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('求和的结果为:\t' + 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 方法取值,但是不直观,操作方法比较单一的数据集。

    应用:

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

加载全部内容

相关教程
猜你喜欢
用户评论