3 python的数据结构、函数与文件
3.1 数据结构与序列
3.1.1 元组
如果元组中的某个对象是可变的,比如列表,可以在原位进行修改。
python
tup = tuple(['foo', [1, 2], True])
tup[1].append(3)
tup #('foo', [1, 2, 3], True)
一次设置多个变量并赋值。
python
a,b = 1,2
# a=b,b=a 错误示范
a,b=b,a
Python最近新增了更多高级的元组拆分功能,允许从元组的开头"摘取"几个元素。它使用了特殊的语法*rest
,这也用在函数签名中以抓取任意长度列表的位置参数。rest
的部分是想要舍弃的部分,rest的名字不重要。作为惯用写法,许多Python程序员会将不需要的变量使用下划线。
python
values = 1, 2, 3, 4, 5
a, b, *rest = values
a,b #输出(1,2)
rest #输出[3,4,5]
a, b, *_ = values
3.1.2 列表
列表的常见操作,append、insert、pop、remove、extend。
python
b_list.append('dwarf')
b_list.insert(1, 'red')
a = b_list.pop(2) #删除的同时赋值
b_list.remove('foo')
x = [4, None, 'foo']
x.extend([7, 8, (2, 3)])
x #输出[4, None, 'foo', 7, 8, (2, 3)]
3.1.3 sort函数
sort
有一些选项,有时会很好用。其中之一是二级排序key,可以用这个key进行排序。例如,我们可以按长度对字符串进行排序。(不加参数默认按文本字母顺序或数字大小排序)
python
a = [7, 2, 5, 1, 3]
a.sort()
a #输出[1, 2, 3, 5, 7]
b = ['saw', 'small', 'He', 'foxes', 'six']
b.sort(key=len)
b #输出['He', 'saw', 'six', 'small', 'foxes']
sorted
函数可以从任意序列的元素返回一个新的排好序的列表,sorted
函数可以接受和sort
相同的参数。
python
In [87]: sorted([7, 1, 2, 6, 0, 3, 2])
Out[87]: [0, 1, 2, 2, 3, 6, 7]
In [88]: sorted('horse race')
Out[88]: [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']
In [89]: sorted(b,key=len) # b = ['saw', 'small', 'He', 'foxes', 'six']
Out[89]: ['He', 'saw', 'six', 'foxes', 'small']
3.1.4 切片
在第二个冒号后面使用step
,可以隔一个取一个元素。一个聪明的方法是使用-1
,它可以将列表或元组颠倒过来。
python
seq = [7, 2, 3, 7, 5, 6, 0, 1]
seq[3:4] = [6, 3]
seq #输出[7, 2, 3, 6, 3, 5, 6, 0, 1]
seq[::2] #输出[7, 3, 3, 6, 1]
seq[::-1] #输出[1, 0, 6, 5, 3, 6, 3, 2, 7]
3.1.5 zip方法
zip
可以将多个列表、元组或其它序列成对组合成一个元组列表。
python
In [89]: seq1 = ['foo', 'bar', 'baz']
In [90]: seq2 = ['one', 'two', 'three']
In [91]: zipped = zip(seq1, seq2)
In [92]: list(zipped)
Out[92]: [('foo', 'one'), ('bar', 'two'), ('baz', 'three')]
zip
可以处理任意多的序列,元素的个数取决于最短的序列:
python
In [93]: seq3 = [False, True]
In [94]: list(zip(seq1, seq2, seq3))
Out[94]: [('foo', 'one', False), ('bar', 'two', True)]
给出一个"被压缩的"序列,zip
可以被用来解压序列。也可以当作把行的列表转换为列的列表。这个方法看起来有点神奇:
python
In [96]: pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'),
....: ('Schilling', 'Curt')]
In [97]: first_names, last_names = zip(*pitchers)
In [98]: first_names
Out[98]: ('Nolan', 'Roger', 'Schilling')
In [99]: last_names
Out[99]: ('Ryan', 'Clemens', 'Curt')
3.1.6 range
range(10)
的类型是 range
对象。它在 Python 中属于内置的不可变序列类型,表示一个从 0
到 9
的整数序列(不包含上限 10
)。它不会像列表那样立即生成所有元素,而是一个惰性的序列对象,只有在需要时才会计算具体的值,这使得它在需要大范围数字时也可以节省内存。
python
range(10) # range(0, 10)
list(range(10)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
tuple(range(10)) # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
3.1.7 字典
字典的一些用法(del、pop、update)
python
In [111]: d1
Out[111]:
{'a': 'some value',
'b': [1, 2, 3, 4],
7: 'an integer',
5: 'some value',
'dummy': 'another value'}
In [112]: del d1[5] #根据key删除字典中某条元素
In [114]: ret = d1.pop('dummy') # 删除并把key对应的value赋值给变量
In [115]: ret
Out[115]: 'another value'
In [119]: d1.update({'b' : 'foo', 'c' : 12}) # update的用法
In [120]: d1
Out[120]: {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}
keys和values 的一些需要注意的。
python
list(d1.keys()) # ['a', 'b', 7]
d1.keys() # dict_keys(['a', 'b', 7])
list(d1.values()) # ['some value', [1, 2, 3, 4], 'an integer']
d1.values() # dict_values(['some value', [1, 2, 3, 4], 'an integer'])
字典的值可以是任意Python对象,而键通常是不可变的标量类型(整数、浮点型、字符串)或元组(元组中的对象必须是不可变的) 。这被称为"可哈希性"。可以用hash
函数检测一个对象是否是可哈希的(可被用作字典的键)。
python
In [127]: hash('string')
Out[127]: 5023931463650008331
In [128]: hash((1, 2, (2, 3)))
Out[128]: 1097636502276347782
In [129]: hash((1, 2, [2, 3])) # fails because lists are mutable
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-129-800cd14ba8be> in <module>()
----> 1 hash((1, 2, [2, 3])) # fails because lists are mutable
TypeError: unhashable type: 'list'
3.1.8 集合
集合是无序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值 。 所以集合(set
)里的元素必须是可哈希的,因为集合本质上是一个哈希表结构。哈希表要求元素的哈希值固定,以便能够快速判断元素的唯一性和执行查找操作。所以,像数字、字符串、元组(里面不包含可变对象)等可哈希的类型可以作为集合的元素,而列表、字典等不可哈希的类型不能直接放入集合中。
diff
In [127]: {[1,2,3]}
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-173-f70e4b6b8fd1> in <module>()
----> 1 {[1,2,3]}
TypeError: unhashable type: 'list'
In [128]: set([1,2,3,2,1])
Out[128]: {1, 2, 3} #上面是把列表放到集合里,所以报错;下面是把列表转换为集合,不报错。
In [129]: ([1,2,3])
Out[128]: (1, 2, 3) #这里把列表转化成元组,就不会报错;没有集合的要求(集合里面的元素必须可哈希)
3.1.9 列表推导式
列表推导式是Python最受喜爱的特性之一。它允许用户方便的从一个集合过滤元素,形成列表,在传递参数的过程中还可以修改元素。map
函数可以进一步简化,形式如下:
diff
#列表推导式 [expr for val in collection if condition]
In [154]: strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
In [155]: [x.upper() for x in strings if len(x) > 2]
Out[155]: ['BAT', 'CAR', 'DOVE', 'PYTHON']
# map函数可以进一步简化
In [158]: set(map(len, strings))
Out[158]: {1, 2, 3, 4, 6}
#字典推导式 dict_comp = {key-expr : value-expr for value in collection if condition}
In [159]: loc_mapping = {val : index for index, val in enumerate(strings)}
In [160]: loc_mapping
Out[160]: {'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}
嵌套列表推导式
diff
In [164]: some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
In [165]: flattened = [x for tup in some_tuples for x in tup]
In [166]: flattened
Out[166]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
In [167]: [[x for x in tup] for tup in some_tuples]
Out[167]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
3.2 函数
3.2.1 global关键字
用global关键字声明全局变量。
diff
In [168]: a = None
In [169]: def bind_a_variable():
.....: global a
.....: a = []
.....: bind_a_variable()
.....:
In [170]: print(a)
[]
3.2.2 return的不同表现
在Jupyter Notebook和PyCharm中,return
语句的行为在交互式模式和脚本模式下表现有所不同。
(1) Jupyter Notebook
- 在Jupyter中,最后一个表达式的值会被自动打印出来,因此如果在单元格中调用一个返回值的函数,Jupyter会直接显示结果。
- 例如,如果你在一个单元格中写了
my_function()
,并且my_function
有返回值,那么该返回值会被自动显示出来。 - 这让Jupyter非常适合交互式数据分析,因为你可以快速看到每个步骤的输出。
(2) PyCharm
- PyCharm采用的是脚本模式 ,不会自动打印
return
的结果,除非显式调用print()
函数。 - 当执行代码时,Python解释器只是运行整个脚本,而不会自动显示任何返回值,除非用
print()
命令来显示返回值。 - 所以在PyCharm中,如果你希望看到某个函数的返回结果,应该使用
print(my_function())
。
python
def add(a, b):
return a + b
# jupyter notebook
add(2, 3) # 在Jupyter中直接写这行代码会显示结果 5
# pycharm
add(2, 3) # 运行代码不会显示结果
print(add(2, 3)) # 使用print()才能看到输出 5
3.2.3 lambda匿名函数
Python支持一种被称为匿名的、或lambda函数。lambda函数之所以会被称为匿名函数,与def声明的函数不同,原因之一就是这种函数对象本身是没有提供名称name属性。
python
# 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)
# lambda匿名函数作为sort函数的key值选取。
In [177]: strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
In [178]: strings.sort(key=lambda x: len(set(list(x))))
In [179]: strings
Out[179]: ['aaaa', 'foo', 'abab', 'bar', 'card']
3.2.4 柯里化:部分参数应用
python
def add_numbers(x, y):
return x + y
add_five = lambda y: add_numbers(5, y)
add_five(6) # 输出11(5+6=11)
3.2.5 可迭代对象
在Python中,可迭代对象是指可以逐一访问其元素的对象,常见的有列表、元组、字符串、字典、集合等容器类型,以及生成器和 range
对象等。只要对象实现了 __iter__()
方法或 __getitem__()
方法,就可以用 for
循环进行遍历,称为可迭代对象。
能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是Python的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的,一个原生的使对象可迭代的方法。比如说,对字典进行迭代可以得到其所有的键:
python
In [180]: some_dict = {'a': 1, 'b': 2, 'c': 3}
In [181]: for key in some_dict:
.....: print(key)
a
b
c
In [182]: dict_iterator = iter(some_dict)
In [183]: dict_iterator
Out[183]: <dict_keyiterator at 0x7fbbd5a9f908>
# 迭代器是一种特殊对象,它可以在诸如for循环之类的上下文中向Python解释器输送对象。
# 大部分能接受列表之类的对象的方法也都可以接受任何可迭代对象。
# 比如min、max、sum等内置方法以及list、tuple等类型构造器。
In [184]: list(dict_iterator)
Out[184]: ['a', 'b', 'c']
3.2.6 生成器相关
**生成器(generator)**是构造新的可迭代对象的一种简单方式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。要创建一个生成器,只需将函数中的return替换为yeild即可:
python
def squares(n=10):
print('Generating squares from 1 to {0}'.format(n ** 2))
for i in range(1, n + 1):
yield i ** 2
In [186]: gen = squares()
In [187]: gen
Out[187]: <generator object squares at 0x7fbbd5ab4570>
直到你从该生成器中请求元素时,它才会开始执行其代码:
python
In [188]: for x in gen:
.....: print(x, end=' ')
Generating squares from 1 to 100
1 4 9 16 25 36 49 64 81 100
另一种更简洁的构造生成器的方法是使用生成器表达式 (generator expression)。这是一种类似于列表、字典、集合推导式的生成器。其创建方式为,把列表推导式两端的方括号改成圆括号
python
In [189]: gen = (x ** 2 for x in range(100))
In [190]: gen
Out[190]: <generator object <genexpr> at 0x7fbbd5ab29e8>
In [191]: sum(x ** 2 for x in range(100)) #生成器表达式也可以取代列表推导式,作为函数参数
Out[191]: 328350
In [192]: sum([x ** 2 for x in range(100)])
Out[192]: 328350
In [193]: ((i, i **2) for i in range(5))
Out[193]: <generator object <genexpr> at 0x000001BBCD199A20>
标准库itertools模块中有一组用于许多常见数据算法的生成器。例如,groupby可以接受任何序列和一个函数。它根据函数的返回值对序列中的连续元素进行分组。(类似于mysql的groupby)下面是一个例子:
python
In [193]: import itertools
In [194]: first_letter = lambda x: x[0]
In [195]: names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
# lamdba函数作为groupby的参数
In [196]: 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']
3.2.7 map函数用法
map(function, iterable),举个例子,值得注意的是,map 返回的是一个迭代器,我们可以将它转换为列表以便查看结果。
python
# 定义一个函数,用于返回数字的平方
def square(x):
return x ** 2
# 创建一个包含数字的列表
numbers = [1, 2, 3, 4, 5]
# 使用 map 函数,将 square 函数应用到每个数字上
squared_numbers = map(square, numbers)
# map 返回的是一个迭代器,我们可以将它转换为列表以便查看结果
print(list(squared_numbers))
下面举例中,函数用的是lambda匿名函数。
python
In [189]: map(lambda x: x * 2, [1, 2, 3, 4, 5])
Out[189]: <map at 0x18550f02be0>
In [190]: list(map(lambda x: x * 2, [1, 2, 3, 4, 5]))
Out[190]: [2, 4, 6, 8, 10]
3.2.8 try-except-else-finally结构
python
f = open(path, 'w')
try:
write_to_file(f)
except: #try失败了才执行
print('Failed')
else: #try成功了才执行
print('Succeeded')
finally: #不管try成功与否都执行
f.close()
3.3 文件的打开与关闭
python
In [207]: path = 'examples/segismundo.txt'
In [208]: f = open(path)
In [211]: f.close()
模式 | 说明 |
---|---|
r | 只读模式 |
w | 只写模式。创建新文件(删除同名的任何文件【译注12】) |
a | 附加到现有文件(如果文件不存在则创建一个) |
r+ | 读写模式 |
b | 附加说明某模式用于二进制文件,即rb 或wb |
U | 通用换行模式。单独使用'U'或附加到其他读模式(如rU ) |
用with语句可以可以更容易地清理打开的文件:
python
In [212]: with open(path) as f:
.....: lines = [x.rstrip() for x in f]
这样可以在退出代码块时,自动关闭文件。
read()
读取整个文件为字符串,readlines()
逐行读取文件并返回一个列表。
write()
用于将字符串写入文件,而 writelines()
则用于将多个行(如列表或元组中的元素)一次性写入文件。注意,write()
和 writelines()
都不会自动添加换行符,需要手动添加。
这里的行指的是换行符,可以是手动输入的"/n",更多指的是按enter在文本文件形成的换行符。
python
# 要写入的多行文本,每行末尾手动加上换行符
lines_to_write = [
'This is the first line.\n',
'This is the second line.\n',
'This is the third line.\n'
]
# 打开文件,写入模式 'w' 表示如果文件存在会覆盖,如果不存在则创建
with open('example.txt', 'w') as file:
file.writelines(lines_to_write)
print("写入完成!")
最后加一点第二章的知识。
3.4 其他
.py文件的执行
在pycharm和jupyter里运行某个.py文件的不同方法。
python
# 文件是C:\Users\13642\PycharmProjects\pythonProject\pycharmproject\666\draft.py
def a():
return 1
a() # 用于对比jupyter和pycharm对return的处理方式
print(a())
在终端执行用python关键字。(cmd进的终端或pycharm里的都可以)
python
C:\Users\13642>python C:\Users\13642\PycharmProjects\pythonProject\pycharmproject\666\draft.py
1
魔术命令是 Jupyter Notebook 中的特有功能,它们提供了增强 Jupyter 功能的快捷方式,魔术命令(Magic Commands)并不完全等同于 Linux 命令加上 %
,但它们有一些相似的作用和用途。
在jupyter notebook执行用%run魔法命令。(%run
命令只能在 Jupyter Notebook 或 IPython 环境中使用)
python
In [207]: %run C:\Users\13642\PycharmProjects\pythonProject\pycharmproject\666\draft.py
Out[207]: 1
在 PyCharm 的 Python Console 中,可以使用**exec()
**来执行 Python 文件,或者使用 PyCharm 内置的运行功能。
python
exec(open(r'C:\Users\13642\PycharmProjects\pythonProject\pycharmproject\666\draft.py').read())
1
PyCharm Console 和 Jupyter Notebook 确实有一些相似的地方,它们都提供了交互式编程 的体验,允许用户实时输入代码、执行代码并查看结果。,但差异也有很多(比如**%run
** 命令和**exec()
**函数执行.py文件)
jupyter最后一行代码返回值
tip:这里为什么jupyter执行的draft.py没有返回两个1?
-----------------------------------------------应该比较重要-----------------------------------------------------------
在 Jupyter Notebook 中,每个单元格只会自动显示最后一行代码的返回值 。虽然你在中间调用了 a()
,但它不是最后一行,所以 Jupyter 不会自动显示它的返回值。
----------------------------------------------------------------------------------------------------------------------------
上面提到的return相关:
python
def add(a, b):
return a + b
# jupyter notebook
add(2, 3) # 在Jupyter中直接写这行代码会显示结果 5
# pycharm
add(2, 3) # 运行代码不会显示结果
print(add(2, 3)) # 使用print()才能看到输出 5
接着进一步尝试,发现返回的两个"1"亦有区别:
首先是print(a())放在a()之前,我们会发现,print出来的1不在out输出里面,而在in输入方框下面,out输出的1是由于a()return的。
然后是a()放在print(a())之前,由于a()不是最后一行代码,所以out没有输出,只有print(a())的输出。
isinstance、isiterable
isinstance判断类型,isiterable判断是否可迭代。
python
In [21]: a = 5
In [22]: isinstance(a, int)
Out[22]: True
In [23]: a = 5; b = 4.5
In [24]: isinstance(a, (int, float))
Out[24]: True
In [25]: isinstance(b, (int, float))
Out[25]: True
In [29]: isiterable('a string')
Out[29]: True
In [30]: isiterable([1, 2, 3])
Out[30]: True
字符串的replace()方法
Python的字符串是不可变的,不能修改字符串。可以用replace()方法把一个字符串里部分字符替换成其他字符,并赋值给新变量,但原本的字符串不会变化。
python
In [58]: b = a.replace('string', 'longer string')
In [59]: b
Out[59]: 'this is a longer string'
datetime模块
Python内建的datetime
模块提供了datetime
、date
和time
类型。datetime
类型结合了date
和time
,是最常使用的:
python
In [102]: from datetime import datetime, date, time
In [103]: dt = datetime(2011, 10, 29, 20, 30, 21)
In [104]: dt.day
Out[104]: 29
In [105]: dt.minute
Out[105]: 30
In [106]: dt.date()
Out[106]: datetime.date(2011, 10, 29)
In [107]: dt.time()
Out[107]: datetime.time(20, 30, 21)
# strftime方法可以将datetime格式化为字符串。
In [108]: dt.strftime('%m/%d/%Y %H:%M')
Out[108]: '10/29/2011 20:30'
# strptime可以将字符串转换成datetime对象。
In [109]: datetime.strptime('20091031', '%Y%m%d')
Out[109]: datetime.datetime(2009, 10, 31, 0, 0)
In [110]: dt.replace(minute=0, second=0)
Out[110]: datetime.datetime(2011, 10, 29, 20, 0)
In [111]: dt2 = datetime(2011, 11, 15, 22, 30)
In [112]: delta = dt2 - dt
In [113]: delta
# 结果timedelta(17, 7179)指明了timedelta将17天、7179秒的编码方式.
Out[113]: datetime.timedelta(17, 7179)
In [114]: type(delta)
Out[114]: datetime.timedelta
In [115]: dt
Out[115]: datetime.datetime(2011, 10, 29, 20, 30, 21)
In [116]: dt + delta
Out[116]: datetime.datetime(2011, 11, 15, 22, 30)
三元表达式
python
value = true-expr if condition else false-expr
In [126]: x = 5
In [127]: 'Non-negative' if x >= 0 else 'Negative'
Out[127]: 'Non-negative'