• 第三篇:Python的数据结构、函数和⽂件


    一、数据结构和序列:Python的数据结构简单⽽强⼤。通晓它们才能成为熟练的Python程序员。


    1、元组:元组是⼀个固定⻓度,不可改变的Python序列对象。
    创建元组的最简单⽅式,是⽤逗号分隔⼀列值:
    tup = 4, 5, 6
    tup # 输出:(4, 5, 6)
    当⽤复杂的表达式定义元组,最好将值放到圆括号内,如下所示:
    nested_tup = (4, 5, 6), (7, 8)
    nested_tup # 输出:((4, 5, 6), (7, 8))
    ⽤tuple可以将任意序列或迭代器转换成元组:
    tuple([4, 0, 2]) # 输出:(4, 0, 2)
    tup = tuple('string')
    tup # 输出:('s', 't', 'r', 'i', 'n', 'g')

    可以⽤⽅括号访问元组中的元素。和C、C++、JAVA等语⾔⼀样,序列是从0开始的:
    tup[0]   # 输出:'s'
    元组中存储的对象可能是可变对象。⼀旦创建了元组,元组中的对象就不能修改了:
    tup = tuple(['foo', [1, 2], True])
    如果元组中的某个对象是可变的,⽐如列表,可以在原位进⾏修改:
    tup[1].append(3)
    tup   # 输出:('foo', [1, 2, 3], True)
    可以⽤加号运算符将元组串联起来:
    (4, None, 'foo') + (6, 0) + ('bar',)   # 结果如下:(4, None, 'foo', 6, 0, 'bar')
    元组乘以⼀个整数,像列表⼀样,会将⼏个元组的复制串联起来:
    ('foo', 'bar') * 4   # 结果如下:('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')
    对象本身并没有被复制,只是引⽤了它。

    2、拆分元组

    将元组赋值给类似元组的变量,Python会试图拆分等号右边的值:
    tup = (4, 5, 6)
    a, b, c = tup
    即使含有元组的元组也会被拆分:
    tup = 4, 5, (6, 7)
    a, b, (c, d) = tup
    使⽤这个功能,你可以很容易地替换变量的名字。替换可以这样做:
    a, b = 1, 2
    b, a = a, b    # a=2, b=1

    变量拆分常⽤来迭代元组或列表序列:
    seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
    for a, b, c in seq:
    print('a={0}, b={1}, c={2}'.format(a, b, c))
    a=1, b=2, c=3
    a=4, b=5, c=6
    a=7, b=8, c=9
    另⼀个常⻅⽤法是从函数返回多个值。

    Python新增了更多⾼级的元组拆分功能,允许从元组的开头“摘取”⼏个元素。它使⽤了特殊的语法*rest,这也⽤在函数签名中以抓取任意⻓度列表的位置参数:例如:
    values = 1, 2, 3, 4, 5
    a, b, *rest = values   # rest前面的*号才是最重要的,名字“rest”可任意
    rest的部分是想要舍弃的部分,rest的名字不重要。作为惯⽤写法,许多Python程序员会将不需要的变量使⽤下划线:
    a, b, *_ = values

    3、tuple(元组)⽅法
    ⼀个很有⽤的方法是count(也适⽤于列表),统计某个值的出现频率:
    a = (1, 2, 2, 2, 3, 4, 2)
    a.count(2)    # 输出:4

    4、列表

    与元组对⽐,列表的⻓度可变、内容可以被修改。可以⽤⽅括号定义,或⽤list函数:
    a_list = [2, 3, 7, None]
    tup = ('foo', 'bar', 'baz')
    b_list = list(tup)   # 元组转列表,使用list函数
    b_list        # 输出:['foo', 'bar', 'baz']

    列表和元组的语义接近,在许多函数中可以交叉使⽤。
    list函数常⽤来在数据处理中实体化迭代器或⽣成器:
    gen = range(10) # range(0, 10)
    list(gen) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    5、添加和删除元素:⽤append在列表末尾添加元素

    b_list.append('dwarf')    # ['foo', 'bar', 'baz', 'dwarf']
    insert可以在特定的位置插⼊元素:
    b_list.insert(1, 'red')
    插⼊的序号必须在0和列表⻓度之间。
    注意:与append相⽐,insert耗费的计算量⼤,因为对后续元素的引⽤必须在内部迁移,以便为新元素提供空间。如果要在序列的头部和尾部插⼊元素,你可能需要使⽤collections.deque,⼀个双尾部队列。
    insert的逆运算是pop,它移除并返回指定位置的元素:

    b_list.pop(2)

    可以⽤remove去除某个值,remove会先寻找第⼀个值并除去:
    b_list.remove('foo')
    如果不考虑性能,使⽤append和remove,可以把Python的列表当做完美的“多重集”数据结构。

    ⽤in可以检查列表是否包含某个值:
    'dwarf' in b_list   # 输出:True or False
    否定in可以再加⼀个not:
    'dwarf' not in b_list
    在列表中检查是否存在某个值远⽐字典和集合速度慢,因为Python是线性搜索列表中的值,但在字典和集合中,在同样的时间内还可以检查其它项(基于哈希表)。

    6、串联和组合列表

    与元组类似,可以⽤加号将两个列表串联起来:
    [4, None, 'foo'] + [7, 8, (2, 3)]    # [4, None, 'foo', 7, 8, (2, 3)]
    如果已经定义了⼀个列表,⽤extend⽅法可以追加多个元素:例如:
    x = [4, None, 'foo']
    x.extend([7, 8, (2, 3)]) # [4, None, 'foo', 7, 8, (2, 3)]
    通过加法将列表串联的计算量较⼤,因为要新建⼀个列表,并且要复制对象。⽤extend追加元素,尤其是到⼀个⼤列表中,更为可取。

    7、排序
    ⽤sort函数将⼀个列表原地排序(不创建新的对象):
    a = [7, 2, 5, 1, 3]
    a.sort()   # [1, 2, 3, 5, 7]
    sort有⼀些选项,有时会很好⽤。其中之⼀是⼆级排序key,可以⽤这个key进⾏排序。例如,可以按⻓度对字符串进⾏排序:
    b = ['saw', 'small', 'He', 'foxes', 'six']
    b.sort(key=len)   # ['He', 'saw', 'six', 'small', 'foxes']
    注意:len是函数len()的名称

    8、⼆分搜索和维护已排序的列表
    bisect模块⽀持⼆分查找,和向已排序的列表插⼊值。bisect.bisect可以找到插⼊值后仍保证排序的位置,bisect.insort是向这个位置插⼊值:
    import bisect
    c = [1, 2, 2, 2, 3, 4, 7]
    bisect.bisect(c, 2)   # 输出:4,表示要插入数字2的话,应该在索引为4的位置插入
    bisect.bisect(c, 5)   # 输出:6
    bisect.insort(c, 6)   # 插入元素:6
    c            # [1, 2, 2, 2, 3, 4, 6, 7]
    注意:bisect模块不会检查列表是否已排好序,进⾏检查的话会耗费⼤量计算。因此,对未排序的列表使⽤bisect不会产⽣错误,但结果不⼀定正确。

    9、切⽚:⽤切片可以选取⼤多数序列类型的⼀部分
    切⽚的基本形式是在⽅括号中使⽤start:stop:
    seq = [7, 2, 3, 7, 5, 6, 0, 1]
    seq[1:5]   # [2, 3, 7, 5]
    切⽚也可以被序列赋值:
    seq[3:4] = [6, 3]
    seq   # [7, 2, 3, 6, 3, 5, 6, 0, 1]
    切⽚的起始元素是包括的,不包含结束元素。因此,结果中包含的元素个数是stop - start。
    start或stop都可以被省略,省略之后,分别默认序列的开头和结尾:
    seq[:5]   # [7, 2, 3, 6, 3]
    seq[3:]   # [6, 3, 5, 6, 0, 1]

    负数表明从后向前切⽚:
    seq[-4:]    # [5, 6, 0, 1]
    seq[-6:-2]   # [6, 3, 5, 6]
    在第⼆个冒号后⾯使⽤step,可以隔几个取⼀个元素:
    seq[::2]     # [7, 3, 3, 6, 1],这里隔一个取一个元素
    ⼀个聪明的⽅法是使⽤-1,它可以将列表或元组颠倒过来:
    seq[::-1]     # [1, 0, 6, 5, 3, 6, 3, 2, 7]

    二、序列函数:Python有⼀些有⽤的序列函数。

    1、enumerate函数
    Python内建了⼀个enumerate函数,可以返回(i, value)元组序列:
    for i, value in enumerate(collection):
      # do something with value
    当你索引数据时,使⽤enumerate的⼀个好⽅法是计算序列(唯⼀的)dict映射到位置的值:
    some_list = ['foo', 'bar', 'baz']
    mapping = {}
    for i, v in enumerate(some_list):
      mapping[v] = i
    mapping # 输出:{'bar': 1, 'baz': 2, 'foo': 0}

    2、sorted函数

    sorted函数可以从任意序列的元素返回⼀个新的排好序的列表:
    sorted([7, 1, 2, 6, 0, 3, 2])   # [0, 1, 2, 2, 3, 6, 7]
    sorted('horse race')      # [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']
    sorted函数可以接受和sort相同的参数。

    3、zip函数
    zip可以将多个列表、元组或其它序列成对组合成⼀个元组列表:
    seq1 = ['foo', 'bar', 'baz']
    seq2 = ['one', 'two', 'three']
    zipped = zip(seq1, seq2)
    list(zipped)   # [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]

    zip的常⻅⽤法之⼀是同时迭代多个序列,可结合enumerate使⽤:
    for i, (a, b) in enumerate(zip(seq1, seq2)):
      print('{0}: {1}, {2}'.format(i, a, b))
    0: foo, one
    1: bar, two
    2: baz, three

    给出⼀个“被压缩的”序列,zip可以被⽤来解压序列。也可以当
    作把⾏的列表转换为列的列表。这个⽅法看起来有点神奇:
    pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'), ('Schilling', 'Curt')]
    first_names, last_names = zip(*pitchers)
    first_names   # ('Nolan', 'Roger', 'Schilling')
    last_names   # ('Ryan', 'Clemens', 'Curt')

    4、reversed函数
    reversed可以从后向前迭代⼀个序列:
    list(reversed(range(10)))   # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

    5、字典:Python最为重要的数据结构。
    它更为常⻅的名字是哈希映射或关联数组。它是键值对的⼤⼩可变集合,键和值都是
    Python对象。创建字典的⽅法之⼀是使⽤大括号,⽤冒号分隔键和值:
    empty_dict = {}
    d1 = {'a': 'some value', 'b': [1, 2, 3, 4]}
    你可以像访问列表或元组中的元素⼀样,访问、插⼊或设定字典中的元素:
    d1[7] = 'an integer'   # 在字典中添加键值对
    可以⽤检查列表和元组是否包含某个值得⽅法,检查字典中是否包含某个键:
    'b' in d1     # True

    可以⽤del关键字或pop⽅法(返回值得同时删除键)删除值:
    ret = d1.pop('dummy')
    keys和values是字典的键和值的迭代器⽅法。虽然键值对没有顺序,这两个⽅法可以⽤相同的顺序输出键和值:
    d1 = {'a': 'some value', 'b': [1, 2, 3, 4], 7: 'an integer'}
    list(d1.keys())   # ['a', 'b', 7]
    list(d1.values())   # ['some value', [1, 2, 3, 4], 'an integer']
    ⽤update⽅法可以将⼀个字典与另⼀个融合:
    d1.update({'b' : 'foo', 'c' : 12})
    d1     # {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}
    update⽅法是原地改变字典,因此任何传递给update的键的旧的值都会被舍弃。

    6、⽤序列创建字典

    将两个序列配对组合成字典。下⾯是⼀种写法:
    mapping = {}
    for key, value in zip(key_list, value_list):
      mapping[key] = value
    因为字典本质上是2元元组的集合,dict可以接受2元元组的列表:
    mapping = dict(zip(range(5), reversed(range(5))))
    mapping    # {0: 4, 1: 3, 2: 2, 3: 1, 4: 0}

    7、默认值

    下⾯的逻辑很常⻅:
    if key in some_dict:
      value = some_dict[key]
    else:
      value = default_value
    因此,dict的⽅法get和pop可以取默认值进⾏返回,上⾯的if-else语句可以简写成下⾯:
    value = some_dict.get(key, default_value)
    get默认会返回None,如果不存在键,pop会抛出⼀个异常。关于设定值,常⻅的情况是在字典的值是属于其它集合,如列表。
    例如,你可以通过⾸字⺟,将⼀个列表中的单词分类:
    words = ['apple', 'bat', 'bar', 'atom', 'book']
    by_letter = {}
    for word in words:
      letter = word[0]
      if letter not in by_letter:
        by_letter[letter] = [word]
      else:
        by_letter[letter].append(word)
    by_letter   # {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}

    setdefault⽅法就正是⼲这个的。前⾯的for循环可以改写为:

    for word in words:
      letter = word[0]
      by_letter.setdefault(letter, []).append(word)
    by_letter    # {'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}

    collections模块有⼀个很有⽤的类,defaultdict,它可以进
    ⼀步简化上⾯。传递类型或函数以⽣成每个位置的默认值:
    from collections import defaultdict
    by_letter = defaultdict(list)
    for word in words:
      by_letter[word[0]].append(word)

    8、有效的键类型

    字典的值可以是任意Python对象,⽽键通常是不可变的标量类型(整数、浮点型、字符串)或元组(元组中的对象必须是不可变的)。这被称为“可哈希性”。可以⽤hash函数检测⼀个对象是否是可哈希的(可被⽤作字典的键):例如:
    hash('string')     # 输出:-2698396839112811693,可哈希
    hash((1, 2, (2, 3)))   # 输出:1097636502276347782
    hash((1, 2, [2, 3]))   # 报错,列表不能被哈希

    要⽤列表当做键,⼀种⽅法是将列表转化为元组,只要内部元素可以被哈希,它也就可以被哈希:
    d = {}
    d[tuple([1, 2, 3])] = 5    # 列表转换为元组,可以被哈希,就可以当做字典的键
    d   # 输出:{(1, 2, 3): 5}

    9、集合

    集合是⽆序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。可以⽤两种⽅式创建集合:通过set函数或使⽤大括号set语句:
    set([2, 2, 2, 1, 3, 3])   # 输出:{1, 2, 3},set函数创建集合
    {2, 2, 2, 1, 3, 3}     # 输出:{1, 2, 3},大括号创建集合

    集合⽀持合并、交集、差分和对称差等数学集合运算。考虑两个示例集合:
    a = {1, 2, 3, 4, 5}
    b = {3, 4, 5, 6, 7, 8}
    合并是取两个集合中不重复的元素。可以⽤(union)⽅法,或者(|)运算符:
    a.union(b)      # 输出:{1, 2, 3, 4, 5, 6, 7, 8}
    a | b          # 输出:{1, 2, 3, 4, 5, 6, 7, 8}
    交集的元素包含在两个集合中。可以⽤intersection或&运算符:
    a.intersection(b)   # 输出:{3, 4, 5}
    a & b    # 输出:{3, 4, 5}

    所有逻辑集合操作都有另外原地实现⽅法,它可以直接⽤结果替代集合的内容。对于⼤的集合,这么做效率更⾼:
    c = a.copy()
    c |= b
    c      # {1, 2, 3, 4, 5, 6, 7, 8}
    d = a.copy()
    d &= b
    d     # {3, 4, 5}

    与字典类似,集合元素通常都是不可变的。要获得类似列表的元素,必须转换成元组:
    my_data = [1, 2, 3, 4]
    my_set = {tuple(my_data)}   # 先把列表转换成元组才可以
    my_set               # {(1, 2, 3, 4)}

    你还可以检测⼀个集合是否是另⼀个集合的⼦集或⽗集:
    a_set = {1, 2, 3, 4, 5}
    {1, 2, 3}.issubset(a_set)     # True,判断是否是子集
    a_set.issuperset({1, 2, 3})    # True,判断是否是父集
    集合的内容相同时,集合才对等:
    {1, 2, 3} == {3, 2, 1} # True

    Python的集合操作常用方法如下:

    10、列表、集合和字典推导式

    列表推导式是Python最受喜爱的特性之⼀。它允许⽤户⽅便的从⼀个集合过滤元素,形成列表,在传递参数的过程中还可以修改元素。形式如下:
    [expr for val in collection if condition]
    它等同于下⾯的for循环:
    result = []
    for val in collection:
      if condition:
        result.append(expr)

    filter条件可以被忽略,只留下表达式就⾏。例如,给定⼀个字符串列表,我们可以过滤出⻓度在2及以下的字符串,并将其转换成⼤写:

    strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
    [x.upper() for x in strings if len(x) > 2]  # 输出:['BAT', 'CAR', 'DOVE', 'PYTHON']

    ⽤相似的⽅法,还可以推导集合和字典。字典的推导式如下所示:
    dict_comp = {key-expr : value-expr for value in collection if condition}

    集合的推导式与列表很像,只不过⽤的是大括号:
    set_comp = {expr for value in collection if condition}

    与列表推导式类似,集合与字典的推导也很⽅便,⽽且使代码的读写都很容易。
    来看前⾯的字符串列表。假如我们只想要字符串的⻓度,⽤集合推导式的⽅法⾮常⽅便:
    unique_lengths = {len(x) for x in strings}
    unique_lengths     # 输出:{1, 2, 3, 4, 6}

    map函数可以进⼀步简化:
    set(map(len, strings))    # 输出:{1, 2, 3, 4, 6}

    作为⼀个字典推导式的例⼦,可以创建⼀个字符串的查找映射表以确定它在列表中的位置:
    loc_mapping = {val : index for index, val in enumerate(strings)}
    loc_mapping     # 输出:{'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}

    11、嵌套列表推导式
    假设我们有⼀个包含列表的列表,列表如下所示:

    all_data = [['John', 'Emily', 'Michael', 'Mary', 'Steven'], ['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']]
    现在假设想⽤⼀个列表包含所有的名字,这些名字中包含两个或更多的e。可以⽤for循环来做:
    names_of_interest = []
    for names in all_data:
      enough_es = [ nam for name in names if name.count('e') >= 2 ]
      names_of_interest.extend(enough_es)
    可以⽤嵌套列表推导式的⽅法,将这些写在⼀起,如下所示:
    result = [ name for names in all_data for name in names if name.count('e') >= 2 ]
    result   # 输出:['Steven']

    嵌套列表推导式看起来有些复杂。列表推导式的for部分是根据嵌套的顺序,过滤条件还是放在最后。
    下⾯是另⼀个例⼦,将⼀个整数元组的列表扁平化成了⼀个整数列表:
    some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
    flattened = [x for tup in some_tuples for x in tup]
    flattened # 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]

    记住,for表达式的顺序是与嵌套for循环的顺序⼀样(⽽不是列表推导式的顺序):

    flattened = []
    for tup in some_tuples:
      for x in tup:
        flattened.append(x)
    你可以有任意多级别的嵌套,但是如果你有两三个以上的嵌套,你就应该考虑下代码可读性的问题了。
    分辨列表推导式的列表推导式中的语法也是很重要的:
    [[x for x in tup] for tup in some_tuples]  # 输出:[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    这段代码产⽣了⼀个列表的列表,⽽不是扁平化的只包含元素的列表。

    三、函数

    1、函数
    函数是Python中最主要也是最重要的代码组织和复⽤⼿段。作为最重要的原则,如果你要重复使⽤相同或⾮常类似的代码,就需要写⼀个函数。通过给函数起⼀个名字,还可以提⾼代码的可读性。函数使⽤def关键字声明,⽤return关键字返回值:
    def my_function(x, y, z=1.5):
      if z > 1:
        return z * (x + y)
      else:
        return z / (x + y)
    同时拥有多条return语句也是可以的。如果到达函数末尾时没有遇到任何⼀条return语句,则返回None。
    函数可以有⼀些位置参数(positional)和⼀些关键字参数(keyword)。关键字参数通常⽤于指定默认值或可选参数。在上⾯的函数中,x和y是位置参数,⽽z则是关键字参数。

    也就是说,该函数可以下⾯这三种⽅式进⾏调⽤:

    my_function(5, 6, z=0.7)
    my_function(3.14, 7, 3.5)
    my_function(10, 20)

    函数参数的主要限制在于:关键字参数必须位于位置参数(如果有的话)之后。也可以以任何顺序指定关键字参数。也就是说,你不⽤记住函数参数的顺序,只要记得它们的名字就可以了。
    注意:也可以⽤关键字传递位置参数。前⾯的例⼦,也可以写为:这种写法可以提⾼可读性。
    my_function(x=5, y=6, z=7)
    my_function(y=6, x=5, z=7)

    2、命名空间、作⽤域,和局部函数

    函数可以访问两种不同作⽤域中的变量:全局(global)和局部(local)。Python有⼀种更科学的⽤于描述变量作⽤域的名称,即命名空间(namespace)。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。局部命名空间是在函数被调⽤时创建的,函数参数会⽴即填⼊该命名空间。在函数执⾏完毕之后,局部命名空间就会被销毁(会有⼀些例外的情况,具体闭包的那⼀节介绍)。看看下⾯这个函数:

    def func():
      a = []
      for i in range(5):
        a.append(i)

    调⽤func()之后,⾸先会创建出空列表a,然后添加5个元素,最后a会在该函数退出的时候被销毁。假如我们像下⾯这样定义a:
    a = []
    def func():
      for i in range(5):
        a.append(i)
    虽然可以在函数中对全局变量进⾏赋值操作,但是那些变量必须⽤global关键字声明成全局的才⾏。不要频繁使⽤global关键字。因为全局变量⼀般是⽤于存放系统的某些状态的。

    3、返回多个值

    下面函数可以返回多个值
    def f():
      a = 5
      b = 6
      c = 7
      return a, b, c
    a, b, c = f()
    该函数其实只返回了⼀个对象,也就是⼀个元组,最后该元组会被拆包到各个结果变量中。在上⾯的例⼦中,我们还可以这样写:
    return_value = f()
    这⾥的return_value将会是⼀个含有3个返回值的三元元组。此外,还有⼀种⾮常具有吸引⼒的多值返回⽅式——返回字典:

    def f():
      a = 5
      b = 6
      c = 7
      return {'a': a, 'b': b, 'c': c}
    取决于⼯作内容,第⼆种⽅法可能很有⽤。

    4、函数也是对象

    假设我们有下⾯这样⼀个字符串数组,希望对其进⾏⼀些数据清理⼯作并执⾏⼀堆转换:
    states = [' Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda', 'south carolina##', 'West virginia?']
    为了得到⼀组能⽤于分析⼯作的格式统⼀的字符串,需要做很多事情:去除空⽩符、删除各种标点符号、正确的⼤写格式等。做法之⼀是使⽤内建的字符串⽅法和正则表达式re模块:

     1 import re
     2 def clean_strings(strings):
     3      result = []
     4      for value in strings:
     5           value = value.strip()
     6           value = re.sub('[!#?]', '', value)
     7           value = value.title()
     8           result.append(value)
     9      return result
    10 # 输出如下:
    11 ['Alabama',
    12  'Georgia',
    13  'Georgia',
    14  'Georgia',
    15  'Florida',
    16  'South Carolina',
    17  'West Virginia']

    其实还有另外⼀种不错的办法:将需要在⼀组给定字符串上执⾏的所有运算做成⼀个列表:

    def remove_punctuation(value):
         return re.sub('[!#?]', '', value)
    clean_ops = [str.strip, remove_punctuation, str.title]    # 函数列表
    def clean_strings(strings, ops):
         result = []
         for value in strings:
              for function in ops:
                   value = function(value)
              result.append(value)
         return result
    clean_strings(states, clean_ops)        # 调用函数,输出如下:
    ['Alabama',
     'Georgia',
     'Georgia',
     'Georgia',
     'Florida',
     'South Carolina',
     'West Virginia']

    这种多函数模式使你能在很⾼的层次上轻松修改字符串的转换⽅式。此时的clean_strings也更具可复⽤性!
    还可以将函数⽤作其他函数的参数,⽐如内置的map函数,它⽤于在⼀组数据上应⽤⼀个函数:
    for x in map(remove_punctuation, states):
      print(x.strip())
    # 输出如下:
    Alabama
    Georgia
    Georgia
    georgia
    FlOrIda
    south carolina
    West virginia

    5、匿名(lambda)函数
    Python⽀持⼀种被称为匿名的、或lambda函数。由单条语句组成,该语句的结果就是返回值。
    它是通过lambda关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是⼀个匿名函数”。
    def short_function(x):
      return x * 2
    equiv_anon = lambda x: x * 2   # 这条命令和上面的两个命令是一样的
    lambda函数在数据分析⼯作中⾮常⽅便,很多数据转换函数都以函数作为参数的。直接传⼊lambda函数⽐编写完整函数声明要少输⼊很多字(也更清晰),
    甚⾄⽐将lambda函数赋值给⼀个变量还要少输⼊很多字。看看下⾯这个简单的例⼦:
    def apply_to_list(some_list, f):
      return [f(x) for x in some_list]
    ints = [4, 0, 1, 5, 6]
    apply_to_list(ints, lambda x: x * 2)
    虽然你可以直接编写[x *2 for x in ints],但是这⾥我们可以⾮常轻松地传⼊⼀个⾃定义运算给apply_to_list函数。
    再来看另外⼀个例⼦。假设有⼀组字符串,你想要根据各字符串不同字⺟的数量对其进⾏排序:
    strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
    strings.sort(key=lambda x: len(set(list(x))))
    strings # 输出:['aaaa', 'foo', 'abab', 'bar', 'card']

    注意:lambda函数之所以会被称为匿名函数,与def声明的函数不同,原因之⼀就是这种函数对象本身是没有提供名称name属性。

    6、柯⾥化:部分参数应⽤

    柯⾥化(currying)是⼀个有趣的计算机科学术语,它指的是通过“部分参数应⽤”(partial argument application)从现有函数派⽣出新函数的技术。例如,有⼀个执⾏两数相加的简单函数:
    def add_numbers(x, y):
      return x + y
    通过这个函数,可以派⽣出⼀个新的只有⼀个参数的函数 -- add_five,它⽤于对其参数加5:
    add_five = lambda y: add_numbers(5, y)
    add_numbers的第⼆个参数称为“柯⾥化的”(curried)。
    内置的functools模块可以⽤partial函数将此过程简化:
    from functools import partial
    add_five = partial(add_numbers, 5)

    7、⽣成器

    能以⼀种⼀致的⽅式对序列进⾏迭代(⽐如列表中的对象或⽂件中的⾏)是Python的⼀个重要特点。这是通过⼀种叫做迭代器协议(iterator protocol,它是⼀种使对象可迭代的通⽤⽅式)的⽅式实现的,⼀个原⽣的使对象可迭代的⽅法。⽐如说,对字典进⾏迭代可以得到其所有的键:
    some_dict = {'a': 1, 'b': 2, 'c': 3}
    for key in some_dict:
      print(key)
    当你编写for key in some_dict时,Python解释器⾸先会尝试从some_dict创建⼀个迭代器:
    dict_iterator = iter(some_dict)
    dict_iterator   # <dict_keyiterator at 0x7fbbd5a9f908>,键迭代器
    迭代器是⼀种特殊对象,它可以在诸如for循环之类的上下⽂中向Python解释器输送对象。
    ⼤部分能接受列表之类的对象的⽅法也都可以接受任何可迭代对象。⽐如min、max、sum等内置⽅法以及list、tuple等类型构造器:
    list(dict_iterator) # 输出:['a', 'b', 'c']

    ⽣成器(generator)是构造新的可迭代对象的⼀种简单⽅式。⼀般的函数执⾏之后只会返回单个值,⽽⽣成器则是以延迟的⽅式返回⼀个值序列,即每返回⼀个值之后暂停,直到下⼀个值被请求时再继续。要创建⼀个⽣成器,只需将函数中的return替换为yeild即可:

     1 def squares(n=10):
     2      print('Generating squares from 1 to {0}'.format(n ** 2))
     3      for i in range(1, n + 1):
     4           yield i ** 2
     5 调⽤该⽣成器时,没有任何代码会被⽴即执⾏:
     6 gen = squares()
     7 gen    # 输出:<generator object squares at 0x000001DAC0EF35C8>
     8 直到你从该⽣成器中请求元素时,它才会开始执⾏其代码:
     9 for x in gen:
    10      print(x, end=' ')
    11 Generating squares from 1 to 100
    12 1 4 9 16 25 36 49 64 81 100
    13 
    14 它跟下⾯这个冗⻓得多的⽣成器是完全等价的:
    15 def _make_gen():
    16      for x in range(100):
    17           yield x ** 2
    18 gens = _make_gen()
    19 
    20 ⽣成器表达式也可以取代列表推导式,作为函数参数:
    21 sum(x ** 2 for x in range(100))        # 输出:328350
    22 dict((i, i **2) for i in range(5))        # 输出:{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

    8、itertools模块
    标准库itertools模块中有⼀组⽤于许多常⻅数据算法的⽣成器。例如,groupby可以接受任何序列和⼀个函数。
    它根据函数的返回值对序列中的连续元素进⾏分组。下⾯是⼀个例⼦:
    import itertools
    first_letter = lambda x: x[0]
    names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
    for letter, names in itertools.groupby(names, first_letter):
      print(letter, list(names)) # names is a generator
    A   ['Alan', 'Adam']
    W  ['Wes', 'Will']
    A   ['Albert']
    S   ['Steven']


    ⼀些有⽤的itertools函数如下所示:

    9、错误和异常处理
    在数据分析中,许多函数只⽤于部分输⼊。例如,Python的float函数可以将字符串转换成浮点数,但输⼊有误时,有ValueError错误。
    假如想优雅地处理float的错误,让它返回输⼊值。我们可以写⼀个函数,在try/except中调⽤float:

     1 def attempt_float(x):
     2      try:
     3           return float(x)
     4      except:
     5           return x
     6 当float(x)抛出异常时,才会执⾏except的部分
     7 可以⽤元组包含多个异常:
     8 def attempt_float(x):
     9      try:
    10           return float(x)
    11      except (TypeError, ValueError):
    12           return x
    13 某些情况下,可能不想抑制异常,你想⽆论try部分的代码是否成功,都执⾏⼀段代码。可以使⽤finally14 f = open(path, 'w')
    15 try:
    16      write_to_file(f)
    17 finally:
    18      f.close()
    19 这⾥,⽂件处理f总会被关闭。相似的,你可以⽤else让只在try部分成功的情况下,才执⾏代码:
    20 f = open(path, 'w')
    21 try:
    22      write_to_file(f)
    23 except:
    24      print('Failed')
    25 else:
    26      print('Succeeded')
    27 finally:
    28      f.close()

    10、IPython的异常
    如果是在%run⼀个脚本或⼀条语句时抛出异常,IPython默认会打印完整的调⽤栈(traceback),在栈的每个点都会有⼏⾏上下⽂。

    ⾃身就带有⽂本是相对于Python标准解释器的极⼤优点。你可以⽤魔术命令%xmode,从Plain(与Python标准解释器相同)到Verbose(带有函数的参数值)控制⽂本显示的数量。后⾯可以看到,发⽣错误之后,(⽤%debug或%pdb magics)可以进⼊stack进⾏事后调试。

    四、⽂件和操作系统


    1、文件操作
    打开⼀个⽂件以便读写,使⽤内置的open函数以及⼀个相对或绝对的⽂件路径:例如:
    path = 'examples/segismundo.txt'
    f = open(path)
    默认情况下,⽂件是以只读模式('r')打开的。然后,就可以像处理列表那样来处理
    这个⽂件句柄 f 了,⽐如对⾏进⾏迭代:
    for line in f:
      pass
    从⽂件中取出的⾏都带有完整的⾏结束符(EOL),因此你常常会看到下⾯这样的代码(得到⼀组没有EOL的⾏):

    lines = [ x.rstrip() fo x in open(path) ]   # 注意:在实际打开的方法是下面这样
    # lines = [ x.rstrip().decode("utf-8") fo x in open(path, 'rb') ]
    lines   # 输出lines内容
    如果使⽤open创建⽂件对象,⼀定要⽤close关闭它。关闭⽂件可以返回操作系统资源:
    ⽤with语句可以可以更容易地清理打开的⽂件:在退出代码块时,⾃动关闭⽂件。
    with open(path, 'rb') as f:
      lines = [x.rstrip().decode("utf-8") for x in f]

    如果输⼊f =open(path,'w'),就会有⼀个新⽂件被创建在当前工作目录,并覆盖掉该位置原来的任何数据。
    另外有⼀个x⽂件模式,它可以创建可写的⽂件,但是如果⽂件路径存在,就⽆法创建。


    Python的打开⽂件模式如下:

    对于可读⽂件,⼀些常⽤的⽅法是read、seek和tell。read会从⽂件返回字符。字符的内容是由⽂件的编码决定的(如UTF-8),如果是⼆进制模式打开的就是原始字节:
    f = open(path)
    f.read(10)         # 输出:'Sueña el r'
    f2 = open(path, 'rb')   # Binary mode
    f2.read(10)        # 输出:b'Suexc3xb1a el '
    read模式会将⽂件句柄的位置提前,提前的数量是读取的字节数。tell可以给出当前的位置:
    f.tell()          # 输出:11,也有可能是10
    f2.tell()          # 输出:10
    尽管我们从⽂件读取了10个字符,位置却是11,这是因为⽤默认的编码⽤了这么多字节才解码了这10个字符。可以⽤sys模块检查默认的编码:
    import sys
    sys.getdefaultencoding()   # 输出:'utf-8'
    seek将⽂件位置更改为⽂件中的指定字节:
    f.seek(3)          # 输出:3
    f.read(1)          # 输出:读取的字符
    关闭⽂件:
    f.close()
    f2.close()

    向⽂件写⼊,可以使⽤⽂件的write或writelines⽅法。例如,我们可以创建⼀个⽆空⾏版的tmp.txt:
    with open('tmp.txt', 'w') as handle:
      handle.writelines(x for x in open(path) if len(x) > 1)    # 打开另一个文件去掉空行并写入tmp.txt文件
    with open('tmp.txt') as f:
      lines = f.readlines()

    Python重要的文件方法或属性

    2、⽂件的字节和Unicode
    Python⽂件的默认操作是“⽂本模式”,也就是说,你需要处理Python的字符串(即Unicode)。它与“⼆进制模式”相对,⽂件模式加⼀个b。
    UTF-8是⻓度可变的Unicode编码,所以当我从⽂件请求⼀定数量的字符时,Python会从⽂件读取⾜够多的字节进⾏解码。
    如果以“rb”模式打开⽂件,则读取确切的请求字节数。取决于⽂本的编码,你可以将字节解码为str对象,但只有当每个编码的Unicode字符都完全成形时才能这么做。

    ⽂本模式结合了open的编码选项,提供了⼀种更⽅便的⽅法将Unicode转换为另⼀种编码:
    sink_path = 'sink.txt'
    with open(path) as source:
      with open(sink_path, 'xt', encoding='iso-8859-1') as sink:
        sink.write(source.read())
    with open(sink_path, encoding='iso-8859-1') as f:
      print(f.read(10))

    注意,不要在⼆进制模式中使⽤seek。如果⽂件位置位于定义Unicode字符的字节的中间位置,读取后⾯会产⽣错误。
    如果你经常要对⾮ASCII字符⽂本进⾏数据分析,通晓Python的Unicode功能是⾮常重要的。

  • 相关阅读:
    【MySQL疑难杂症】如何将树形结构存储在数据库中(方案二 Path Enumeration)
    【MySQL疑难杂症】如何将树形结构存储在数据库中(方案一 Adjacency List)
    【Java疑难杂症】利用Java核心库实现简单的AOP
    【Java入门提高篇】Day5 Java中的回调(二)
    【Java入门提高篇】Day4 Java中的回调
    【SpringMVC】使用Myeclipse创建SpringMVC项目【超详细教程】
    使用GDAL/OGR读写矢量文件
    WebGL简易教程(四):颜色
    WebGL简易教程(三):绘制一个三角形(缓冲区对象)
    OSG与Shader的结合使用
  • 原文地址:https://www.cnblogs.com/Micro0623/p/10058557.html
Copyright © 2020-2023  润新知