2.1 Python的内置类型
Python具有许多数据类型,解决问题的方式不止一种。了解数据类型是深入了解的关键一步。
2.1.1 字符串与字节
Python3中用bytes对象处理字符串,这一点类似于C语言中的ASCII值。bytes将字符转化为数字进行处理,数字则依次排列成为串,即为字符串的处理。
bytes只能使用字节作为序列值,即 .
书中示例:
在bytes类型转换为其他序列类型时可显示其本来面目:
一串字符没有前缀都是Unicode。因此,所有单引号(')、双引号(")、成组的三个引号(单引号或双引号)包围且没有前缀的值都表示str数据类型:
Unicode需要编码为二进制数据进行保存与传播。将字符串编码为字节序列的方法有两种:
- 利用str.encide(encoding, errors)方法,用注册编解码器(registered codec)对字符串进行编码。
- 利用bytes(source, encoding, errors)构造函数,创建一个新堆字节序列。
类似的方法可以将bytes表示的二进制数据转换成字符串: - 利用str.encide(encoding, errors)方法,用注册编解码器(registered codec)对字符串进行编码。
- 利用bytes(source, encoding, errors)构造函数,创建一个新堆字节序列。
1. 实现细节
Python字符串是不变的。字节字符也是如此。而bytearray是bytes的可变版本。
2. 字符串拼接
介绍了字符串拼接的四种方法:
for substring in substrings: s1 += substring
s2 = '{}'.format(substrings)
s3 = "".join(substrings)
s4 = "%s"%substrings
运行结果如下:
2.1.2 集合类型
Python具有多种集合类型,本章重点介绍列表、元组、字典和集合四种。
1. 列表与元组
列表与元组是Python最基本的两种集合类型,其根本区别在于:列表是动态的,大小可变;而元组不可变。
(1)实现细节
在CPython中,list被实现为长度可变的数组,事实上,它肯本不是列表。与其他语言标准库的常见的链表容易引起混淆。Python中的列表是由其他对象引用组成的连续数组。在某些操作中复杂度较高。
操作 | 复杂度 |
---|---|
复制 | O(n) |
添加元素 | O(1) |
插入元素 | O(n) |
获取元素 | O(n) |
修改元素 | O(1) |
删除元素 | O(1) |
遍历 | O(n) |
获取长度为k的切片 | O(k) |
删除切片 | O(n) |
修改长度为k的切片 | O(k+n) |
列表扩展 | O(k) |
乘以k | O(nk) |
测试元素是否在列表中 | O(n) |
min()/max() | O(n) |
获取列表长度 | O(1) |
(2)列表推导
在C语言中,为了获取10以下的偶数集合,我们可能这样写:
int a[10]; //最多10个元素 int k = 0, i = 0; for(i = 0; i < 10; i++) { if(i % 2 == 0) { a[k] = i; k++; } }
Python中:
event = [] for i in range(10): if i % 2 == 0: events.append(i)
在C语言中这种写法很高效,但在Python中实际很慢。而Python给出另一种高效的解决方法:
(3)其他习语
举出如下例子:
对序列进行枚举,较容易理解,在Python可替换为:
运用zip()函数,可以对列表进行合并:
对zip()返回结果再次调用zip(),会恢复原状:
Python中可以一次性赋多个值:
这称之为序列解包。
星号可以获取多个元素:
- 获取剩余部分:
- 获取中间部分:
- 获取前部分:
嵌套解包:
2. 字典
字典将唯一键映射到对应的值,例如:
根据之前的例子可以写出如下推导式:
遍历字典的三种方法:
- keys():返回dict_keys对象,查看字典所有键。
- values():返回dict_values对象,查看所有值。
- items():返回dict_items对象,查看所有二元元组。
这些返回的都是视图对象,因此查看到是动态字典内容:
(1)实现细节
CPython使用伪随机探测的散列表作为字典的底层数据结构。并且,不可变的内置类型才能成为字典的键。
两个对象相等,散列值一定相等,反之则不一定。因此可以看作函数,散列值相等不一定对象相等。开放定址法可以解决散列值冲突的问题。
下面是字典操作的复杂度:
操作 | 平均复杂度 | 平摊最坏情况复杂度 |
---|---|---|
获取元素 | O(1) | O(n) |
修改元素 | O(1) | O(n) |
删除元素 | O(1) | O(n) |
复制 | O(n) | O(n) |
遍历 | O(n) | O(n) |
Ps:最坏情况的n指的是字典曾达到的最大元素数量,因此在一个字典需要删除过多元素时,应该选择创建一个新字典。
(2)缺点和替代方案
(在Python3.5及以前版本中)字典并不是以添加元素的顺序来保存元素,然而在3.6以后版本中,字典有序。
如图所示,字典将以元素添加顺序保存元素。
关于OrderedDict:
由于目前最新版本的Python中字典已经是有序的了,OrderedDict的有序字典功能可以被替代,其其他功能详见 Python文档
3. 集合
集合类似于数学中的集合,集合分两种,set()和frozenset().
- set():可变、无序、有限集合,元素是唯一不可变的。
- frozenset():不可变、无序的集合,元素是唯一不可变的。
由于set()可变,不能用作字典、set()、frozenset()的元素;而相反的,frozenset()可以。例如:
集合有三种方法调用: - 调用set()。
- 使用{}。
- 集合推导。
实现细节
集合与字典十分类似。实际上,集合被实现为带空值的字典,只有键没有值。此外,集合对没有值的映射进行优化。
因此,集合可以快速添加、删除、检查元素,复杂度均为O(1)。由于集合实现依赖于类似的散列表结构,因此最坏情况复杂度为O(n),n为当前集合大小。
4. 超越基础集合类型——collections模块
collections模块为集合类型提供了更多的选择。下面是最重要几种集合类型:
- namedtuple():用于创建元组子类的工厂函数,可通过属性名访问他的元索引。
- deque:双端队列,类似列表,是栈和队列的一般化,可以在两端快速添加获取出元素。
- ChainMap:类似字典的类,用于创建多个映射的单一视图。
- Counter:字典子类,由于对可哈希对象进行计数。
- OrderedDict:字典子类,可以保存元素的添加顺序。
- defaultdict:字典子类,可以通过调用用户自定义的工厂函数来设置缺失值。
Ps:十二章将具体介绍collections模块。《Python高级编程(第二版)》第十二章读书笔记
2.2 高级语法
介绍四种高级语法元素:
- 迭代器(iterator)
- 生成器(generator)
- 装饰器(decorator)
- 上下文管理器(context manager)
2.2.1 迭代器
迭代器是类似于指针的容器,可以返回自身及下一元素。基于以下两种方法:
- next:返回容器下一元素。
- iter:返回迭代器本身。
迭代器利用iter函数和一个序列创建:
如图所示,遍历字符串'abc'后,引发了迭代器的停止StopIteration,raise StopIteration可以引发迭代器停止。运用以下代码可以自定义迭代器:class Number_Iter: def __init__(self, n): self.n = n def __next__(self): #根据字节需求迭代 if self.n <= 0: raise StopIteration self.n -= 1 return self.n def __iter__(self): return self for element in Number_Iter(4): print(element)
以下是运行结果:
迭代器能为生成器提供基础。
2.2.2 yield语句
yield类似于return,但是能暂停函数并返回一个中间结果。例如:
def Fibonacci(): a, b = 0, 1 while True: yield b a, b = b, a+b # 赋值从右向左 fin = Fibonacci() print([next(fin) for i in range(100)])
得出结果:
由于每次返回一个值,节省了空间,提高了整体性能。
另举一例,使用tokenize生成令牌:
我创建了一个名叫hh的txt文件,并把“hh”保存在文件中,由图可看出代码将文件的每一行遍历。
双重函数也同样能使用yield:
def power(values): for value in values: print('powering %s' % value) yield value def adder(values): for value in values: print('adding to %s' % value) if value % 2 == 0: yield value + 3 else: yield value + 2 elements = [1, 2, 3, 4, 5, 6] results = adder(power(elements)) r = [next(results) for i in range(6)] print(r)
得出结果:
生成器可以利用next函数与调用代码进行交互。yield成为表达式,值通过send传递:
def psychologist(): print("Please tell me your problem") while True: answer = (yield) if answer is not None: if answer.endswith('?'): print("Don't ask yourself too much QUESTION") elif 'good' in answer: print("Ahh that's good, go on") elif 'bad' in answer: print("Don't be so negative")
交互结果如下:
除了send之外,throw和close也能根据客户端代码改变自身行为。
Ps:生成器是协程、异步并发等等的基础,这些将在十三章介绍。 《Python高级编程(第二版)》第十三章读书笔记
2.2.3 装饰器
刚刚看到装饰器的一些例子时,懵逼很久很久。。大概了解到装饰器是一个函数,能将其他函数作为“参数”,返回它的增强函数。例如在定义类方法或静态方法时:
class WithoutDecorators: def some_static_method(): print("this is a static method") some_static_method = staticmethod(some_static_method) def some_class_method(cls): print("this is class method") some_class_method = classmethod(some_class_method)
运用装饰器,可以将以上代码简化:
class WithDecorators: @staticmethod def some_static_method(): print("this is a static method") @classmethod def some_class_method(cls): print("this is class method")
1. 一般语法和可能实现
装饰器可以有用户自己定义,以下将介绍四种装饰器的通用模板。
(1) 作为一个函数
用函数可以自定义一个装饰器,例如:
def repeat(function): def wrapped(*args, **kwargs): result = None for i in range(5): result = function(*args, **kwargs) return result return wrapped @repeat def printf(): print("nice") printf()
以上是一个连续操作五次的装饰器,result调用了函数五次。能得到结果:
模板如下:
def mydecorator(function): def wrapped(*args, **kwargs): # 在调用原始函数之前,做点什么 result = function(*args, **kwargs) # 在调用函数后,做点什么 # 并返回结果 return result # 返回wrapper作为装饰函数 return wrapped
(2) 作为一个类
举个例子:
class repeatClass: def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): for i in range(3): result = self.function(*args, **kwargs) return result @repeatClass def print2(): print("good") print2()
同样的,这也是一个重复3次的装饰器,与前一种比较相像。结果如下:
通用模板如下:
class repeatClass: def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): # 在调用原始函数之前,做点什么 result = function(*args, **kwargs) # 在调用函数后,做点什么 # 并返回结果 return result
(3)参数化装饰器