第3章 Python数据结构、函数

以下内容主要学习自《利用Python进行数据分析》

第3章 内建数据结构、函数及文件

数据结构和序列

序列是Python中最基本的数据结构,简单但强大。序列中的每个元素都分配一个数字:它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。精通这些数据结构是成为优秀Python编程者的必要条件

第3章 Python数据结构、函数_第1张图片
Python数据结构

列表

列表是一组可变的Python对象序列,可以简单的理解为可变长度的数组,但比数组更强大的是:列表中的元素可以是不同类型的对象

创建列表

创建列表最简单的方法就是在方括号中添加元素,并使用逗号隔开:

In[1] : lst = ['a', 'b', 'c', None, '998']
In[2] : lst
out[2] : ['a', 'b', 'c', None, '998']

也可以用list函数将任意序列或迭代器转换为列表:

In [1]: lst1 = list('hello')

In [2]: lst1
Out[2]: ['h', 'e', 'l', 'l', 'o']

In [3]: lst2 = list(range(10))

In [4]: lst2
Out[4]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
访问列表

通过索引号访问列表,注意:索引号从0开始

In [1]: lst = ['a', 'b', 'c', None, 985]

# 用索引获取单个元素
In [2]: lst[1]  
Out[2]: 'b'

# 获取多个元素,获取的数量等于两个索引的差
In [3]: lst[1:3]
Out[3]: ['b', 'c']

# 不指定开始索引,将从开头获取
In [4]: lst[:2]
Out[4]: ['a', 'b']

# 不指定结束索引,将获取到结尾
In [5]: lst[2:]
Out[5]: ['c', None, 985]

# 索引为负数,将倒数获取
In [6]: lst[-1]
Out[6]: 985

# 获取多个元素,索引为负数
In [7]: lst[-3:-1]
Out[7]: ['c', None]

# 不指定开始索引,结束索引为负数
In [8]: lst[:-1]
Out[8]: ['a', 'b', 'c', None]
修改列表

使用append方法在列表末尾添加新的对象

in[9]: lst.append(211)

in[10]: lst
out[10]: ['a', 'b', 'c', None, 985, 211]

使用insert方法在将新的对象添加到列表的指定位置

in[11]: lst.insert(3, 'd')

in[12]: lst
out[12]: ['a', 'b', 'c', 'd', None, 998, 211]

使用reverse方法反向列表中元素

In [13]: lst.reverse()

In [14]: lst
Out[14]: [211, 998, None, 'd', 'c', 'b', 'a']

使用sort方法对原列表进行排序,注意:若列表中的对象类型不一样,使用默认参数的排序可能会失败、报错。排序方法的高级应用请参考教材。

In [15]: lst_num = [3, 7, 1, 9, 12]

In [16]: lst_num.sort()

In [17]: lst_num
Out[17]: [1, 3, 7, 9, 12]
删除列表

使用pop方法移除列表中的某个元素(默认最后一个元素),并且返回该元素的值:

In [18]: item = lst.pop(5)

In [18]: item
Out[18]: 998

In [19]: lst
Out[19]: ['a', 'b', 'c', 'd', None, 211]

使用pop方法移除列表中的某个元素,注意不是根据索引号移除:

In [20]: lst.remove(None)

In [21]: lst
Out[21]: ['a', 'b', 'c', 'd', 211]

如果对象并不在列表中,使用pop方法将会报错,因此可以用in先判断是否包含某对象

In [22]: 'a' in lst
Out[22]: True

In [23]: 'f' in lst
Out[23]: False
列表内置函数

使用len函数获取列表中元素的总数
使用max函数获取列表元素最大值
使用min函数获取列表元素最小值

In [24]: lst = ['a', 'b', 'c', 'd']

In [25]: len(lst)
Out[25]: 4

In [26]: max(lst)
Out[26]: 'd'

In [27]: min(lst)
Out[27]: 'a'

元组

元组是一种固定、不可变的Python对象序列,这意味着元组内的对象是不能改变的,可理解为不可变的数组。

创建元组

创建元组最简单的办法就是在括号中添加元素,并使用逗号隔开:

In[1] : tup = (4, 5 ,6)
In[2] : tup
out[2] : (4, 5, 6)

也可以用tuple函数将任意序列或迭代器转换为元组:

In[3] : tuple([4, 0, 2])
out[3] : (4, 0, 2)

In[4] : tup = tuple('hello')

In[5] : tup
out[5] : ('h', 'e', 'l', 'l', 'o')
访问元组

访问元组的元素的方法,与“访问列表”的方法一致,可参考上方的“访问列表”。

修改元组

由于元组是不可变的,因此不能对元组中的单个元素进行修改。

可以使用+号连接元组来生成更长的元组:

In[6] : (4, None, 'foo') + (6, 0) + ('bar')
out[6] : (4, None, 'foo', 6, 0, bar')

将元组乘以整数,将生成含有多分拷贝的元组:

In[7] : ('foo', 'bar') * 3
out[7] : ('foo', 'bar', 'foo', 'bar', 'foo', 'bar')
删除元组

元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组

In [8]: del tup

In [9]: tup
NameError: name 'tup' is not defined
元组内置函数

使用len函数获取元组中元素的总数;
使用max函数获取元组元素最大值;
使用min函数获取元组元素最小值。

In [10]:  tup = tuple('hello')

In [11]: len(tup)
Out[11]: 5

In [12]: max(tup)
Out[12]: 'o'

In [13]: min(tup)
Out[13]: 'e'

字典

字典也可以称为哈希表或关联数组。字典的大小是可变的,其中包含的每一个元素以键值对形式呈现。字典的值可以是任何Python对象,但字典的键不可变,因此可以用数字、字符串、元组作为键。

创建字典

创建字典最简单的办法就是在大括号中添加元素,并使用逗号隔开,每个元组的键和值用冒号隔开:

In[1] : d1 = {'a' : 'some value', 'b' : [1 ,2, 3, 4]}
In[2] : d1
out[2] : {'a' : 'some value', 'b' : [1 ,2, 3, 4]}

从已有的两个序列生成字典

In[3]: list1 = ['a', 'b', 'c']
In[4]: list2 = [198, 236, 701]

In[5]: d2 = dict(zip(list1, list2))

In[16]: d2
Out[6]: {'a': 198, 'b': 236, 'c': 701}
访问字典

根据键获取值

In[7]: d1['b']
Out[7]: [1, 2, 3, 4]

如果访问一个字典中不存在的键,那么会报告KyeError异常,为避免这类错误,可以使用in运算符检查字典中是否含有一个键。

In[8]: 'c' in d1
Out[8]: False

另外,使用get方法可以在字典中不包含某个键时,返回默认值

In [9]: d1.get('c')  # 未指定默认值
Out[9]: None

In [10]: d1.get('c', 0)  # 指定默认值
Out[10]: 0

使用keys方法能获得字典所有的键,使用values能获得字典所有的值

In[11]: list(d1.keys())
Out[11]: ['a', 'b']

In[12]: list(d1.values())
Out[12]: ['some value', [1, 2, 3, 4]]
修改字典

字典不允许同一个键出现两次(同一个字典中的键都是唯一的),因此,当字典中不存在某键时,会给字典增加新的键值对;如果该键已经存在,那么就会修改它的值。

In[13]: d1['c'] = 17  # 添加新键
In[13]: d1
Out[14]: {'a' : 'some value', 'b' : [1 ,2, 3, 4], 'c' : 17}

In[15]: d1['c'] = 23  # 修改已有键的值
Out[15]: {'a': 'some value', 'b': [1, 2, 3, 4], 'c': 23}

使用update方法可以合并两个字典。注意:如果原字典中有相同的键,那么它的值将被覆盖,否则,就增加新的键值。

In[16]: d1.update({'b': 'foo', 'd' : 3.1415})

In[17]: d1
Out[17]: {'a': 'some value', 'b': 'foo', 'c': 23, 'd': 3.1415}
删除字典

使用del删除字典

In [18]: del d1['d']

In [19]: d1
Out[19]: {'a': 'some value', 'b': 'foo', 'c': 23}

使用clear方法删除字典内所有元素

In [20]: d1.clear()

In [21]: d1
Out[21]: {}
字典内置函数

使用len函数返回字典中键值对的个数

In[22]: d1 = {'a': 198, 'b': 236, 'c': 701}

In[23]: len(d1)
Out[23]: 3

使用str函数返回字典的字符串形式

In [24]: str(d3)
Out[24]: "{'a': 198, 'b': 236, 'c': 701}"

集合

集合是一种内容唯一的序列,而列表、元组中的内容是可以重复的。

创建集合

直接创建集合

In [1]: s1 = {'张三', '李四', '李四', '王五', '杨六', '王五'}

In [2]: s1
Out[2]: {'张三', '李四', '杨六', '王五'}

使用set函数创建集合

In [3]: s2 = set([2, 2, 2, 1, 3, 3])

In [4]: s2
Out[4]: {1, 2, 3}
访问集合

如果想象访问列表、元组那样,使用索引来访问集合,是不行的。会报告TypeError异常

In [5]: s1[2]
TypeError: 'set' object does not support indexing

如确实需要获得集合中的元素,可参考如下代码

In [6]: l1 = list(s1)  # 把集合转换为列表,然后访问

In [7]: l1[2]
Out[7]: '李四'

In [8]: for item in s1:  # 或者采用遍历的方式访问
    ...:     print(item)
    ...:
王五
张三
李四
杨六
修改集合

添加单个元素

In [9]: s1.add('赵七')

In [10]: s1
Out[10]: {'张三', '李四', '杨六', '王五', '赵七'}

添加多个元素

In [11]: s1.update(['孙九', '孔八'])

In [12]: s1
Out[12]: {'孔八', '孙九', '张三', '李四', '杨六', '王五', '赵七'}
删除集合

使用remove方法删除集合中的某个元素

In [13]: s1.remove('孔八')

In [14]: s1
Out[14]: {'孙九', '张三', '李四', '杨六', '王五', '赵七'}

此外还有一个方法也是移除集合中的元素,且如果元素不存在,不会发生错误

In [15]: s1.discard('孔八')

使用clear方法删除集合内所有元素

In [16]: s1.clear()

In [17]: s1
Out[17]: set()
集合运算

使用union方法得到两个集合的并集

In [18]: s1 = {1, 3, 5, 6, 8}
In [19]: s2 = {2, 4, 5, 6, 7, 9}

In [20]: s1.union(s2)
Out[20]: {1, 2, 3, 4, 5, 6, 7, 8, 9}

使用intersection方法得到两个集合的交集

In [21]: s1.intersection(s2)
Out[21]: {5, 6}

使用difference方法得到两个集合的差集

In [22]: s1.difference(s2)
Out[22]: {1, 3, 8}

使用symmetric_difference方法得到两个集合不重复的元素的集合

In [23]: s1.symmetric_difference(s2)
Out[23]: {1, 2, 3, 4, 7, 8, 9}

使用issubset方法判断一个集合是否是另一个集合的子集

In [24]: s3 = {3, 8}

In [25]: s3.issubset(s1)
Out[25]: True

使用issuperset方法判断一个集合是否包含另一个集合

In [26]: s1.issuperset(s3)
Out[26]: True

使用isdisjoint方法判断两个集合是否没有交集

In [27]: s2.isdisjoint(s3)
Out[27]: True
集合内置函数

使用len函数返回集合中元素的个数

In[28]: len(s1)
Out[28]: 5

使用str函数返回字典的字符串形式

In [24]: str(s1)
Out[24]:  '{1, 3, 5, 6, 8}'

内建序列函数

拆包

Python提供拆包操作,可以将等号右边的序列包含的元素赋值给变量:

In [1]: lst = [4, 5, 6]

In [2]: a, b, c = lst

In [3]: b
Out[3]: 5

拆包的一个常用场景就是遍历元组或列表中的元素,请参考下方的示例:

In [4]: seq = ((1, 2, 3), (4, 5, 6), (7, 8, 9))

In [5]: 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

注意:拆包操作不仅仅适用于元组,同样适用于列表、集合。上面的示例还演示了元组是可以嵌套的,这同样适用于列表,即可以构建多维序列。

如果你想从序列中“部分拆包”,可以用如下的方法

In [6]: s1 = {1, 3, 5, 6, 8}

In [7]: a, b, *other = s1

In [8]: b
Out [8]: 3

In [9]: other
Out[9]: [5, 6, 8]

# other部分有时是想丢弃的部分,很多Python编程者会使用下划线表示不想要的变量
In [10]: a, b, *_ = s1
enumerate

我们经常需要在遍历一个序列的同时,追踪当前元素的索引,一种老套的方法如下

i = 0
for value in collection:
    # 使用value做点事
    i+=1

由于这种场景很常见,所以Python内建了enumerate函数,通过返回(i, value)元组的方法来简化代码

for i, value in enumerate(collection):
    # 使用value做点事
zip

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

推导式是最受欢迎的Python语言特性之一。它用一种简明的表达式完成从某个列表过滤元素、从而生成一个新的列表。
列表推导式的基本形式为:[expr for val in collection if condition]
集合推导式的基本形式为:{expr for val in collection if condition}
字典推导式的基本形式为:{key-expr : val-expr for val in collection if condition}

列表推导式案例:从给定的字符串列表、选出长度大于3的字符串,并将字符改为大写:

In [1]: strs = ['c', 'python', 'c++', 'java', 'c#']
In [2]: result = [x.upper() for x in strs if len(x)>=3]

In[3]: result
Out[3]: ['PYTHON', 'C++', 'JAVA']

以上的案例,与下面的for循环伪代码是等价的:

result = []
for val in collection:
    if condition:
        result.append(expr)

嵌套集合推导式案例:从二维列表中,选择姓名包含s、且不重复的名字。

In [4]: all_data = [['Emma', 'Warren', 'Ben', 'Jason', 'Kevin', 'Sophia', 'Joyce', 'Ashley', 'Peter', 'May', 'Abel', 'Ivy', 'Hailey', 'Stella', 'Gloria', 'Denny'], ['Amy', 'Jessie', 'Lucy', 'Johnny', 'Amanda', 'Jennifer', 'Hailey', 'Abby', 'Albert', 'Bruce', 'Paul', 'Charles'], ['Denny', 'Andrew', 'Amanda', 'Abel', 'Kenny', 'Ben', 'Evan', 'Bill', 'Peter', 'Jessie', 'Jason', 'Gloria']]

In [5]: result = {name for names in all_data for name in names if (name.lower()).count('s')>=1}

In [6]: str(result)
Out[6]: "{'Charles', 'Ashley', 'Jessie', 'Stella', 'Jason', 'Sophia'}"

请牢记:推导式for表达式的嵌套顺序,应当和你写嵌套for循环的顺序一致

函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

def 函数名(参数列表):
    函数体
    return xx

定义Python函数的规则:

  1. 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()、冒号。
  2. 任何传入参数和自变量必须放在圆括号中间。
  3. 函数内容要缩进。
  4. return 关键字返回一个值给调用方。没有return相当于返回 None。

返回多个值

使用Python编程时,你一定会喜欢上它可以从函数返回多个值。在数据分析和其它科研应用中,会经常需要返回多个值。Python的函数实质上是返回了一个元组对象,而后又拆包为多个结果变量。

In [1]: def f():
                a=5
                b=6
                c=7
                return a,b,c

In [2]: x,y,z = f()

In [3]: x
Out[3]: 5

函数是对象

在Python中,函数也是个对象。这种特性使Python可以非常容易的实现在其它语言中较难实现的代码构造。

字符串数据清洗案例:假设有一些姓名数据,要去除每个字符串收尾的空格、去除每个字符串中的特殊符号,并把每个字符串的首字母大写。

传统的编程思路是:编写一个函数、完成既定功能的封装,如下所示:

In [1]: import  re  # 引入正则表达式模块

In [2]: def clean_strings(strings):
            result = []
            for value in strings:
               value = value.strip()  # 去除空格
               value = re.sub('[!#?]', '', value)  # 使用正则表达式模块,替换特殊字符
               value = value.title()  # 把首字母转换为大写
               result.append(value)
           return result

In [3]: names = [' emma   ', 'Warren!', '#ben', '   jason', '??Kevin', 'sophia    ', '?joyce!']

In [4]: clean_strings(names)
Out[4]: ['Emma', 'Warren', 'Ben', 'Jason', 'Kevin', 'Sophia', 'Joyce']

在Python中,实现以上的需求,还可以这样写:

In [1]: import  re  # 引入正则表达式模块

In [2]: def remove_punctuation(value):
            return re.sub('[!#?]', '', value)  # 使用正则表达式模块,替换特殊字符

In [3]: def clean_strings(strings, ops):
            result = []
            for value in strings:
                for function in ops:
                    value = function(value)
                result.append(value)
            return result

In [4]: clean_ops = [str.strip, remove_punctuation, str.title]  # 把函数名组装在列表中

In [5]: names = [' emma   ', 'Warren!', '#ben', '   jason', '??Kevin', 'sophia    ', '?joyce!']

In [6]: clearn_strings(names, clean_ops)
Out[6]: ['Emma', 'Warren', 'Ben', 'Jason', 'Kevin', 'Sophia', 'Joyce']

因为“在Python中,函数也是个对象”,所以,在上面的例子中,可以把函数名组装在列表中,然后遍历调用。像这种更为函数化的模式,使clearn_strings函数具有更强的复用性和通用性。

匿名(Lambda)函数

匿名(Lambda)函数是一种通过单个语句生成函数的方式。和用def关键字声明一个函数不同,匿名函数对象自身没有一个显式的__name__属性,这就是lambda函数被称为匿名函数的原因。

In [3]: f = lambda x: x * 2
In [4]: ints = [4, 0, 1, 5, 7]

In [5]: [f(x) for x in ints]
Out[5]: [8, 0, 2, 10, 14]

匿名函数的代码量小、意图清晰,在功能不是很复杂的前提下,比写一个完整的函数更加高效。

Lambda应用案例:根据字符串中不同字母的数量进行排序

In [6]: strings = ['foo', 'card', 'bar', 'aaaa', 'abab']

In [7]: strings.sort(key = lambda x: len(set(list(x))))

In [8]: strings
Out[8]: ['aaaa', 'foo', 'abab', 'bar', 'card']

错误和异常处理

优雅地处理错误或异常是构建稳定程序的重要工作。Python对异常处理的伪代码如下:

tyr:
    # 要执行的代码
    raise ValueError(10)  # 抛出一个指定的异常
except ValueError as e:
    # 捕获到异常时,对异常进行处置的代码
    print(e.value)
finally:
    # 无论是否有异常,都要执行的善后代码

另外,except语句可以捕获指定的一个或多个异常,方法是将异常类型写成元组(封装在小括号中),并跟在except关键字后。

except (TypeError, ValueError):

上面的TypeErrorValueErorr,都是Python内置的异常类。更多的内置异常类、创建自定义异常类,请参考Python官方文档。

文件I/O

Python中处理文件非常简单,这也是Python能够在文本和文件处理领域如此流行的原因。

打开并读取文件

Python使用open函数打开文件,默认情况下,文件是以只读模式"r"打开的。在结束操作时,关闭文件是非常重要的。如下的伪代码:

f  = open('readme.txt')

for line in f:
    print(line)

f.close()

另一种更简单的关闭文件的方式是使用with语句,文件会在with代码块结束后自动关闭。

with open('readme.txt') as f:
    for line in f:
        print(line)

打开并写入文件

Python操作文件的模式有如下几种:

  • r:只读模式
  • w:只写模式,创建新文件(路径下的同名文件中的数据会被清除)
  • x:只写模式,创建新文件(路径下有同名文件会创建失败)
  • a:添加到已存在的文件(如果不存在就创建)
  • r+:读写模式
  • b:二进制文件模式,与别模式搭配使用(如'rb'或'wb')
  • t:文本文件模式,与别的模式搭配使用(如'rt'或'wt')

下面的伪代码示例写入文件:

with open('readme.txt', 'w') as f:
    f.writelines(strings)  # 将字符串序列写入文件

你可能感兴趣的:(第3章 Python数据结构、函数)