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)参数化装饰器