列表是一个可以放置任意数据类型的集合
举例:
l = [1, 2, 'hello', 'world'] # 列表中同时含有int和string类型的元素
l
[1, 2, 'hello', 'world']
举例:
l = [1, 2, 3, 4]
l[3] = 40 # 和很多语言类似,python中索引同样从0开始,l[3]表示访问列表的第四个元素
l
[1, 2, 3, 40]
l.append(5) # 添加元素5到原列表的末尾
l
[1, 2, 3, 40, 5]
举例:
l = [1, 2, 3, 4]
l[-1]
4
举例:
l = [1, 2, 3, 4]
l[1:3] # 返回列表中索引从1到2的子列表
[2, 3]
举例:
l = [[1, 2, 3], [4, 5]] # 列表的每一个元素也是一个列表
由于列表是动态的,所以它需要存储指针,来指向对应的元素。另外,由于列表可变,所以需要额外存储已经分配的长度大小(8 字节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间。
为了减小每次增加 / 删减操作时空间分配的开销,Python 每次分配空间时都会额外多分配一些,这样的机制(over-allocating)保证了其操作的高效性:增加 / 删除的时间复杂度均为 O(1)。
举例:
l = []
l.__sizeof__() # 空列表的存储空间为40字节,在3.7.4版本后空列表是20字节
40
l.append(1)
l.__sizeof__()
72 # 加入了元素1之后,列表为其分配了可以存储4个元素的空间 (72 - 40)/8 = 4
l.append(2)
l.__sizeof__()
72 # 由于之前分配了空间,所以加入元素2,列表空间不变
l.append(3)
l.__sizeof__()
72 # 同上
l.append(4)
l.__sizeof__()
72 # 同上
l.append(5)
l.__sizeof__()
104 # 加入元素5之后,列表的空间不足,所以又额外分配了可以存储4个元素的空间
内置函数
l = [3, 2, 3, 7, 8, 1]
l.count(3) # count(item) 表示统计列表中 item 出现的次数
2
l.index(7) # index(item) 表示返回列表中 item 第一次出现的索引
3
l.reverse() # list.reverse() 和 list.sort() 分别表示原地倒转列表和排序
l
[1, 8, 7, 3, 2, 3]
l.sort()
l
[1, 2, 3, 3, 7, 8]
性能
Python 会在后台,对静态数据做一些资源缓存(resource caching)。通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,Python 就会回收它们所占用的内存,返还给操作系统,以便其他变量或其他应用使用。
但是对于一些静态变量,比如元组,Python 会暂时缓存这部分内存。这样,下次我们再创建同样大小的元组时,Python 就可以不用再向操作系统发出请求,去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。
举例:
# 计算初始化一个相同元素的列表和元组分别所需的时间
python3 -m timeit 'x=(1,2,3,4,5,6)'
20000000 loops, best of 5: 9.97 nsec per loop
python3 -m timeit 'x=[1,2,3,4,5,6]'
5000000 loops, best of 5: 50.1 nsec per loop
# 索引操作的话,两者的速度差别非常小,几乎可以忽略不计
python3 -m timeit -s 'x=[1,2,3,4,5,6]' 'y=x[3]'
10000000 loops, best of 5: 22.2 nsec per loop
python3 -m timeit -s 'x=(1,2,3,4,5,6)' 'y=x[3]'
10000000 loops, best of 5: 21.9 nsec per loop
list 内部实现,使用的是 Array 形式,又因为可变,所以是一个 over-allocate 的 Array。
详细实现,可以参考源码: https://github.com/python/cpython/blob/master/Objects/tupleobject.c
思考题
想创建一个空的列表,我们可以用下面的 A、B 两种方式,请问它们在效率上有什么区别吗?我们应该优先考虑使用哪种呢?
# 创建空列表
# option A
empty_list = list()
# option B
empty_list = []
字符串是由独立字符组成的一个序列,通常包含在单引号('')双引号("")或者三引号之中(''' '''或""" """,两者一样),比如下面几种写法:
name = 'jason'
city = 'beijing'
text = "hello world"
这里定义了 name、city 和 text 三个变量,都是字符串类型。Python 中单引号、双引号和三引号的字符串是一模一样的,没有区别,比如下面这个例子中的 s1、s2、s3 完全一样:
s1 = 'hello'
s2 = "hello"
s3 = """hello"""
s1 == s2 == s3
True
Python 同时支持这三种表达方式,很重要的一个原因就是,这样方便你在字符串中,内嵌带引号的字符串,比如:
"I'm a student"
Python 的三引号字符串,则主要应用于多行字符串的情境:
def calculate_similarity(item1, item2):
"""
Calculate similarity between two items
Args:
item1: 1st item
item2: 2nd item
Returns:
similarity score between item1 and item2
"""
切片和遍历:
name = 'jason'
name[0]
'j'
name[1:3]
'as'
for char in name:
print(char)
在 Python 中,字符串通常是不可变的,当尝试修改字符串时,会报 TypeError:
s = 'hello'
s[0] = 'H'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
若要修改字符串,通常需要重新创建一个新的字符串来完成:
s = 'H' + s[1:]
s = s.replace('h', 'H')
但是,在其他语言中,比如 Java,有可变的字符串类型,比如 StringBuilder,每次添加、改变或删除字符(串),无需创建新的字符串,时间复杂度仅为 O(1)。在 Python 初期,想要改变字符串,往往需要 O(n) 的时间复杂度,其中,n 为新字符串的长度。在不断跟新后,Python 增加了加法操作符'+='的字符串拼接。因为它是一个例外,打破了字符串不可变的特性。
str1 += str2 # 表示str1 = str1 + str2
# 以下代码,在2.5版本前,时间复杂度是O(n^2)
# 2.5以后版本是O(n
s = ''
for n in range(0, 100000):
s += str(n)
# 由于列表的 append 操作是 O(1) 复杂度,字符串同理。因此,这个含有 for 循环例子 的时间复杂度为 n*O(1)=O(n)
l = []
for n in range(0, 100000):
l.append(str(n))
l = ' '.join(l)
字符串切割,主要适用于数据分析
def query_data(namespace, table):
"""
given namespace and table, query database to get corresponding
data
"""
path = 'hive://ads/training_table'
namespace = path.split('//')[1].split('/')[0] # 返回'ads'
table = path.split('//')[1].split('/')[1] # 返回 'training_table'
data = query_data(namespace, table)
常见内置函数: string.strip(str),表示去掉首尾的 str 字符串;
string.lstrip(str),表示只去掉开头的 str 字符串;
string.rstrip(str),表示只去掉尾部的 str 字符串。
# 很多时候,从文件读进来的字符串中,开头和结尾都含有空字符,我们需要去掉它们,就可以用 strip() 函数
s = ' my name is jack.zeng '
s.strip()
'my name is jack.zeng'
print('no data available for person with id: {}, name: {}'.format(id, name))
print(f"no data available for person with id: {id}, name: {name}")
# 装饰器
1. 基础知识
函数相关
* **在 Python 中,函数是一等公民(first-class citizen),函数也是对象,可以把函数赋予变量:**
```python
def func(message):
print('Got a message: {}'.format(message))
send_message = func
send_message('hello world')
# 输出
Got a message: hello world
def get_message(message): return 'Got a message: ' + message
def root_call(func, message): print(func(message)) root_call(get_message, 'hello world')
Got a message: hello world
* **在函数里定义函数,也就是函数的嵌套**:
```python
def func(message):
def get_message(message):
print('Got a message: {}'.format(message))
return get_message(message)
func('hello world')
# 输出
Got a message: hello world
def func_closure(): def get_message(message): print('Got a message: {}'.format(message)) return get_message
send_message = func_closure() send_message('hello world')
Got a message: hello world