Lesson 025 —— python 常用函数与深浅拷贝

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/woodhost/article/details/83752333

Lesson 025 —— python 常用函数与深浅拷贝

常用函数

  • map()

    map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。(python 3.x 返回一个迭代器对象)

    注意:map()函数不改变原有的 list,而是返回一个新的 list。

    利用map()函数,可以把一个 list 转换为另一个 list,只需要传入转换函数。

    由于list包含的元素可以是任何类型,因此,map() 不仅仅可以处理只包含数值的 list,事实上它可以处理包含任意类型的 list,只要传入的函数f可以处理这种数据类型。

    如果希望把list的每个元素都作平方,就可以用map()函数:

    # test 1
    >>> l = [x for x in range(1, 10)]
    >>> l_pow = map(lambda x:x**2, l)
    >>> ll = list(l_pow)
    >>> print(l)
    [1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> print(l_pow)
    <map object at 0x7f088119e898>
    >>> print(ll)
    [1, 4, 9, 16, 25, 36, 49, 64, 81]
    
    # test 2
    >>> def format_name(s):
    ...     return s.capitalize()
    ... 
    >>> name = ['adam', 'LISA', 'barT']
    >>> name1 = map(format_name, name)
    >>> name2 = list(name1)
    >>> print(name2)
    ['Adam', 'Lisa', 'Bart']
    

    025-01

  • filter()

    filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。

    该接收两个参数,第一个为函数,第二个为序列(可迭代对象),序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

    python 2.x 返回列表,python 3.x 返回迭代器对象。

    def is_odd(n):
        return n % 2 == 1
     
    tmplist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    newlist = list(tmplist)
    print(newlist)
    
    # 结果
    [1, 3, 5, 7, 9]
    
  • zip()

    zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

    如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。

    zip 方法在 Python 2 和 Python 3 中的不同:在 Python 3.x 中为了减少内存,zip() 返回的是一个对象。如需展示列表,需手动 list() 转换。

    >>>a = [1,2,3]
    >>> b = [4,5,6]
    >>> c = [4,5,6,7,8]
    >>> zipped = zip(a,b)     # 返回一个对象
    >>> zipped
    <zip object at 0x103abc288>
    >>> list(zipped)  # list() 转换为列表
    [(1, 4), (2, 5), (3, 6)]
    >>> list(zip(a,c))              # 元素个数与最短的列表一致
    [(1, 4), (2, 5), (3, 6)]
     
    >>> zip(*zip(a, b))
    <zip object at 0x7f0880a4b7c8>
    >>> list(zip(*zip(a,b)))
    [(1, 2, 3), (4, 5, 6)]
    >>> a1, a2 = zip(*zip(a,b))          # 与 zip 相反,zip(*) 可理解为解压,返回二维矩阵式
    >>> a1
    (1, 2, 3)   # 返回元组
    >>> a2
    (4, 5, 6)
    >>> list(a1)
    [1, 2, 3]
    >>> list(a2)
    [4, 5, 6]
    
  • enumerate()

    enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。返回 enumerate(枚举) 对象。语法如下:

    enumerate(sequence, [start=0])
    
    • sequence – 一个序列、迭代器或其他支持迭代对象。
    • start – 下标起始位置。
    >>>seasons = ['Spring', 'Summer', 'Fall', 'Winter']
    >>> list(enumerate(seasons))
    [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
    >>> list(enumerate(seasons, start=1))       # 下标从 1 开始
    [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
    
    # for 循环使用 enumerate
    >>>seq = ['one', 'two', 'three']
    >>> for i, element in enumerate(seq):
    ...     print i, element
    ... 
    0 one
    1 two
    2 three
    
  • max() 、min()

    初级技巧

    tmp = max(1,2,4)
    print(tmp)
    
    #可迭代对象
    a = [1, 2, 3, 4, 5, 6]
    tmp = max(a)
    print(tmp)
    

    中级技巧:key 属性的使用

    当key参数不为空时,就以key的函数对象为判断的标准。
    如果我们想找出一组数中绝对值最大的数,就可以配合lamda先进行处理,再找出最大值

    a = [-9, -8, 1, 3, -4, 6]
    tmp = max(a, key=lambda x: abs(x))
    print(tmp)
    

    高级技巧:找出字典中最大的那组数据

    prices = {
        'A':123,
        'B':450.1,
        'C':12,
        'E':444,
    }
    # 在对字典进行数据操作的时候,默认只会处理key,而不是value
    # 先使用zip把字典的keys和values翻转过来,再用max取出值最大的那组数据
    max_prices = max(zip(prices.values(), prices.keys()))
    print(max_prices) # (450.1, 'B')
    

    当字典中的value相同的时候,才会比较key:

    prices = {
        'A': 123,
        'B': 123,
    }
    
    max_prices = max(zip(prices.values(), prices.keys()))
    print(max_prices) # (123, 'B')
    
    min_prices = min(zip(prices.values(), prices.keys()))
    print(min_prices) # (123, 'A')
    

    max 比较的时候首先选择字典的第一项,即键进行比较;在列表中比较字符串也是从各项的第一个字符开始比较。

  • reduce()

    reduce() 函数会对参数序列中元素进行累积。

    函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,依次类推,最后得到一个结果。

    在Python 3里,reduce() 函数已经被从全局名字空间里移除了,它现在被放置在functools 模块里。语法如下:

    reduce(function, iterable[, initializer])
    
    • function – 函数,有两个参数
    • iterable – 可迭代对象
    • initializer – 可选,初始参数
    >>> from functools import reduce
    >>>def add(x, y) :            # 两数相加
    ...     return x + y
    ... 
    >>> reduce(add, [1,2,3,4,5])   # 计算列表和:1+2+3+4+5
    15
    >>> reduce(lambda x, y: x+y, [1,2,3,4,5], 1)  # 使用 lambda 匿名函数
    16
    

变量的存储

在高级语言中,变量是对内存及其地址的抽象。对于python而言,python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的只本身。

我们来看一张简单易懂的图理解一下python的引用语义和C语言值语义在内存中的存储情况,左右两个图,分别表示了python中变量存储与C语言中变量存储区别:

025-02

在python中的数据类型包括:bool、int、long、float、str、set、list、tuple、dict等等。我们可以大致将这些数据类型归类为简单数据类型和复杂的数据结构。

025-03

由于python中的变量都是采用的引用语义,数据结构可以包含基础数据类型,导致了在python中数据的存储是下图这种情况,每个变量中都存储了这个变量的地址,而不是值本身;对于复杂的数据结构来说,里面的存储的也只只是每个元素的地址而已。

025-04

  1. 数据类型重新初始化

    变量的每一次初始化,都开辟了一个新的空间,将新内容的地址赋值给变量。对于下图来说,我们重复的给str1赋值,其实在内存中的变化如下:

    >>> str1 = "hello world"
    >>> print(id(str1))
    139674494744688
    >>> str1 = "new hello world"
    >>> print(id(str1))
    139674494744560
    

    025-05

    从上图我们可以看出,str1在重复的初始化过程中,是因为str1中存储的元素地址由 hello world 的地址变成了 new hello world 的。

  2. 数据结构内部元素变化对于变量的影响

    对于复杂的数据类型来说,改变其内部的值对于变量的影响:

    025-06

    025-07

    当对列表中的元素进行一些增删改的操作的时候,是不会影响到 lst1 列表本身对于整个列表地址的,只会改变其内部元素的地址引用。可是当我们对于一个列表重新初始化(赋值)的时候,就给 lst1 这个变量重新赋予了一个地址,覆盖了原本列表的地址,这个时候,lst1 列表的内存id就发生了改变。上面这个道理用在所有复杂的数据类型中都是一样的。

变量的赋值

  1. str 的赋值

    >>> str1 = "hello world"
    >>> id(str1)
    139674494744688
    >>> str2 = str1
    >>> id(str2)
    139674494744688
    >>> str1 = "new hello world"
    >>> print(str1)
    new hello world
    >>> print(str2)
    hello world
    >>> id(str1)
    139674494744560
    >>> id(str2)
    139674494744688
    

    025-08

    我们刚刚已经知道,str1的再次初始化(赋值)会导致内存地址的改变,从上图的结果我们可以看出修改了str1之后,被赋值的str2从内存地址到值都没有受到影响。

    看内存中的变化,起始的赋值操作让str1和str2变量都存储了‘hello world’所在的地址,重新对str1初始化,使str1中存储的地址发生了改变,指向了新建的值,此时str2变量存储的内存地址并未改变,所以不受影响。

  2. 复杂数据结构中的赋值

    >>> lst1 = [1,2, 3, 4, 5, 5]
    >>> lst1 = [1,2, 3, 4, 5, 6]
    >>> lst2 = lst1
    >>> id(lst1)
    139674494732040
    >>> id(lst2)
    139674494732040
    >>> lst1.append('new item')
    >>> print(lst1)
    [1, 2, 3, 4, 5, 6, 'new item']
    >>> print(lst2)
    [1, 2, 3, 4, 5, 6, 'new item']
    >>> id(lst1)
    139674494732040
    >>> id(lst2)
    139674494732040
    

    025-09

    上图对列表的增加修改操作,没有改变列表的内存地址,lst1和lst2都发生了变化。

    对照内存图我们不难看出,在列表中添加新值时,列表中又多存储了一个新元素的地址,而列表本身的地址没有变化,所以 lst1 和 lst2 的 id 均没有改变并且都被添加了一个新的元素。

深浅拷贝

我们已经详细了解了变量赋值的过程。对于复杂的数据结构来说,赋值就等于完全共享了资源,一个值的改变会完全被另一个值共享。

然而有的时候,我们偏偏需要将一份数据的原始内容保留一份,再去处理数据,这个时候使用赋值就不够明智了。python为这种需求提供了copy模块。提供了两种主要的copy方法,一种是普通的copy,另一种是deepcopy。我们称前者是浅拷贝,后者为深拷贝。

浅拷贝

不管多么复杂的数据结构,浅拷贝都只会copy一层。

025-10

对于sourceLst和copyLst列表添加一个元素,这两个列表好像是独立的一样都分别发生了变化,但是当我修改lst的时候,这两个列表都发生了变化.

025-11

我们可以知道 sourceLst 和 copyLst 列表中都存储了一坨地址,当我们修改了sourceL st1的元素时,相当于用 sourceChange 的地址替换了原来 str1 的地址,所以 sourceLst 的第一个元素发生了变化。而 copyLst 还是存储了 str1 的地址,所以 copyLst 不会发生改变。

当 sourceLst 列表发生变化,copyLst 中存储的 lst 内存地址没有改变,所以当 lst 发生改变的时候, sourceLst 和 copyLst 两个列表就都发生了改变。

这种情况发生在字典套字典、列表套字典、字典套列表,列表套列表,以及各种复杂数据结构的嵌套中,所以当我们的数据类型很复杂的时候,用 copy 去进行浅拷贝就要非常小心。

深拷贝

python的copy模块提供的另一个deepcopy方法。深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,在这个过程中我们对这两个变量中的一个进行任意修改都不会影响其他变量。

>>> import copy
>>> l = [1, 2, 3, ['a', 'b', 'c'], 'item']
>>> lsc = copy.copy(l)   # 相当于 lsc = copy(l),即和 copy() 函数效果一样
>>> ldc = copy.deepcopy(l)
>>> print(lsc)
[1, 2, 3, ['a', 'b', 'c'], 'item']
>>> print(ldc)
[1, 2, 3, ['a', 'b', 'c'], 'item']
>>> id(l)
139674502461704
>>> id(lsc)
139674502460744
>>> id(ldc)
139674502460872
>>> id(l[3])
139674494743688
>>> id(lsc[3])
139674494743688
>>> id(ldc[3])
139674494743816

025-12

其实深拷贝就是在内存中重新开辟一块空间,不管数据结构多么复杂,只要遇到可能发生改变的数据类型,就重新开辟一块内存空间把内容复制下来,直到最后一层,不再有复杂的数据类型,就保持其原引用。这样,不管数据结构多么的复杂,数据之间的修改都不会相互影响。

总结

只有遇见不可变类型,也即是可哈希类型的时候,其值存储在内存中,而变量中存储的是它的地址。当使用不可变类型时,就会根据地址进行取值。当进行赋值时,由于是不可变类型,即不能修改存储在内存中的值,所以会重新开辟一个地址,将这个新的不可变类型的值的地址存储在变量中,即覆盖掉原来的不可变类型的地址。由于原来的不可变类型没有了变量存储其地址,即没有引用,则认为其已经不需要了,由垃圾回收机制删除回收,使这个内存继续变成可以使用的状态。

也就是说,在 python 中,不可变类型变量的赋值,不是值的覆盖,而是值的内存地址的覆盖,而值则被删除。

猜你喜欢

转载自blog.csdn.net/woodhost/article/details/83752333