【菜gou的CS61A学习笔记11 Mutable Values & List & Iterators】

【菜gou的CS61A学习笔记11 Mutable Values & List & Iterators】,第1张

今天是三次课放在一起写,主要是这三次课互相连接比较紧密,而且涉及到一些之前我们学过的内容,第一节Mutable Values讲述了一些小的概念如nondestructive&destructive,第二节主要讲述了python list的一些方法和作用域等问题;

Mutable Values : ​​​​​​https://inst.eecs.berkeley.edu/~cs61a/sp21/assets/slides/13-Mutable_Values_full.pdf

List mutation + Identity + Nonlocal:

https://inst.eecs.berkeley.edu/~cs61a/sp21/assets/slides/14-List_mutation_+_Identity_+_Nonlocal_full.pdf

Iterators + Generators:

https://inst.eecs.berkeley.edu/~cs61a/sp21/assets/slides/15-Iterators_+_Generators_full.pdf

一、lecture 1.1 Mutable Values

        首先先引出non-destructive和destructive的概念。我们该怎么理解这个内容呢,简单来说,就是一个 *** 作是否对原来的对象有影响,看一下这个定义。

图1 :  non-destructive和destructive的示例

        这样的定义可能有些抽象,就是说有一个对象,如果经过一个 *** 作后其本身没有发生变化,就是非破坏性的;如果经过 *** 作后产生了一个新的对象,并且引用A结果返回的是新对象且和A不同,则称这个 *** 作是破坏性的;接下来看几个例子:

         

图2 : 例1 

        为什么这是非破坏性的,因为虽然其返回的是一个新树,所有节点的值都扩大了两倍,但是对于原来的树t,并没有发生变化;

        若修改一下写法,那么这种 *** 作就可以是破坏性的(只是一种思路,具体实现没有验证过):    

def double(t):
    for tt in child(t):
        tt[1] *= 2
    
    return t

        然后引入Immutability or Mutability的概念,这个在之前我们说基础的数据结构中也提到过,这里也就不过多说明;

        Immutability: An immutable value is unchanging once created. Immutable types (that we've covered): int, float, string, tuple

        Mutability: A mutable value can change in value throughout the course of computation. All names that refer to the same object are afected by a mutation.Mutable types (that we've covered): list, dict

1.2 list

        这一小节我们就来学习一下list的基本 *** 作,主要包括切片、列表复制和列表方法等;

        

图3 : 列表的切片 *** 作 

        对于列表的切片 *** 作我觉得可以多上手熟练一下,基础的语法就如同上图所示,为什么说这里的 *** 作时破坏性的呢?很明显,所有的 *** 作都更改了最开始L列表; 

        参考资料:Python List Slicing - GeeksforGeeks

        然后是列表的一些常用的方法,为了便于后续的查阅我将其整理了表格。

list方法
方法名作用注意事项
list.append()adds a single element to a list可以是单个元素或列表destructive
list.extend()adds all the elements in one list to a list不可以是单个不可迭代元素如intdestructive
list.pop()removes and returns the last elementdestructive
list.remove()removes the first element equal to the argument注意只删除列表中第一个出现的要删除元素destructive
list.index()返回第一个要查询元素的下标non-destructive
list.sort()对列表内元素进行排序可以通过设置关键字定义排序destructive

        这里提到的都是我平时使用的和课程上提到的常用的list方法,但是限于篇幅等未能展开演示一些细节,可以在平时的使用中多理解体会一下。

        具体的使用参照资料:Python 列表(List) | 菜鸟教程 

1.3 Scopes

        这一小节就是简答的作用域的概念,由于python中没有定义数据类型的相关概念,所以对于全局作用域和局部作用域的联系有时需要特殊说明,我们先来看一下下面这种情况:

图4 : 作用域例题 

        首先对于第一种情况, 我们只是向一个列表中添加元素,但是并没有改变这个对象所指向的内存,即这里要区分=赋值语句和这种情况的区别,这是两种不一样的方法,虽然有些时候这些 *** 作的结果是一样的;第二种情况则是我们需要改变全局的current,这种是不被允许的,即函数不能重新赋值全局变量;并且第二种情况是函数先判断了定义了一个局部current变量,但是未经过赋值却进行了计算,这也是不允许的;

        修改的方法是可以在函数中添加一个global current,表示我们可以对全局变量进行修改,但是这是不推荐的,因为会带来一些麻烦,所以尽量避免使用global关键字;

        然后是global和nonlocal的使用规则如下:

图5: 访问绑定在全局范围内的变量是可以的,但是重新分配全局对象是不行的,除非添加global

  

图6 :总的规则

1.4 Iterators + Generators 

        本节内容比较重要,主要是迭代器和生成器的内容,首先我们要记住python中可迭代的对象有list、tuple、dictionary、set等结构。那么这些结构的特点就是可以通过构建迭代器iterator来访问其中的对象,接下来我们将构建一个简单的迭代来演示一下。

        

list_a = [1, 2, 3, 4]
iter_a = iter(list_a)

next(iter_a)#1
next(iter_a)#2
next(iter_a)#3
next(iter_a)#4
next(iter_a)#StopIteration

        这是非常简单的关于建立list迭代器的语法,同时为了避免最后异常的抛出,我们可以回顾一下之前学习到的异常情况,改善一下上述的写法:

         

list_a = [1, 2, 3, 4]
iter_a = iter(list_a)

try:
    while True:    
        print(next(iter_a))
except:
    print("已经遍历完全部对象")

        然后我们就有一个问题,我们为什么要使用迭代器这种写法呢?因为在python的底层语法中,我们就是利用迭代器的思想,对可迭代对象做循环遍历的,而其语法上和效率上对循环进行了优化,所以采用循环的方式会比直接使用迭代器的方式执行效率更高,但是根本上还是基于循环的方式写成的。

         

 图7: python for循环的执行思路

        然后下一页ppt给我们一种对于不同对象重新设计iter和next方法的思路,我们可以在创造一个特殊的类时重写这两个方法达到使对象可迭代。

 图8 : 对象的__iter__()和__next__()方法

        大概讲完了迭代器,我们再来学习一下生成器generator。(说明:课程内容都是讲授一个大概的语法,具体的使用理解还需要搭配作业和实验来巩固)

        我们先来看generator的定义:

图9: generator定义 

        generator使用yield关键字替代了return返回对象,其本质上也是一个迭代器。那么他是怎么工作的呢?首先当调用函数时,python立刻绑定了一个迭代器对象;其次每次调用一次next(),他就会执行一次函数主体并在yield语句停止并返回当前yield语句的值。这样解释有点难理解,我们引入一个例子看一下。

def func():
    num = 0
    while(num <= 10):
	yield num
	num += 2

 

                                                                  图10 : 简单的生成器演示

        那么我们为什么要使用生成器呢?简单来说就是使用生成器可以节省大量内存空间。因为使用生成器只会在使用next()方法时,才会返回值。这样就节省了大量的空间,尤其是有时候程序中一个容器中存在很多元素的情况下,比如说使用普通的返回,函数返回了一个包含3000个元素的列表,而使用生成器的情况下,只要每次你调用next()方法,才会返回一个对象,这在一些情况下确实非常节省内存。

        以上则是本次的三次lec的内容,接下来的这些作业我会抓紧完成贴在后面

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/790781.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存