Python学习笔记--容器类型

本文摘自朱雷老师所著《Python工匠》一书内容,作为笔记予以记录。

列表、元组、字典、集合是Python中4中内置容器类型,是Python语言中最为重要的组成部分,在《Python工匠》第三章容器类型中做了重要知识点的介绍,并引申出了对象的可变性、可哈希性等诸多基础概念,在其它的Python书籍中很少读到见底如此之深,特别有用的知识内容,特此记录。

内置容器功能丰富,基于它构建的自定义容器更为强大,能够帮助我们完成许多有趣的事情,在本文后面将摘录作者自定义字典类型,优化了一个日志分析脚本。

虽然无需了解类表的底层实现原理就可以使用列表,但如果能深入了解列表是基于数组实现的,就能避开一些性能陷阱,知道在什么情况下应该选择其它数据机构实现某些需求。

一、《Python工匠》第三章容器类型中的知识总结:

(1)基础知识

  • 在进行函数调用时,传递的不是变量的值或者引用,而是变量所指对象的引用
  • Python内置类型可以分为可变与不可变两种,可变性会影响一些操作的行为,比如+=
  • 对于可变类型,必要时对其进行拷贝操作,能避免产生意料之外的影响
  • 常见的浅拷贝方式:copy.copy、推导式、切片操作
  • 使用copy.deepcopy可以进行深拷贝操作

(2)列表与元组

  • 使用enumerate可以在遍历列表的同时获取下标
  • 函数的多返回值其实是一个元组
  • 不存在元组推导式(表达式),但可以使用tuple函数来将生成器表达式转换为元组
  • 元组经常用来表示一些结构化数据

(3)字典与集合

  • 在Python3.7版本前,字典类型是无序的,之后变为保留数据的插入顺序
  • 使用OrderedDict可以在Python3.7以前的版本里获得有序字典
  • 只有可哈希的对象才能存入集合,或者作为字典的键使用
  • 使用有序字典OrderedDict可以快速实现有序去重
  • 使用fonzenset可以获得一个不可变的集合对象
  • 集合可以方便地进行集合运算,计算交集、并集等
  • 不要通过继承dict来创建自定义字典类型

(4)代码可读性技巧

  • 具名元组比普通元组可读性更强
  • 列表推导式可以更快速地完成遍历、过滤、处理以及构建新列表操作
  • 不要编写过于复杂的推导式,用朴实的代码替代就好
  • 不要把推导式当做代码量更少的循环,写普通循环就好

(5)代码可维护性技巧

  • 当访问的字典键不存在时,可以选择捕获异常或先做判断,优先推荐捕获异常
  • 使用get、setdefault、带参数的pop方法可以简化边界处理逻辑
  • 使用具名元组作为返回值,比普通元组更好扩展
  • 当字典键不存在时,使用defaultdict可以简化处理
  • 继承MutalbeMapping可以方便地创建自定义字典类,封装处理逻辑
  • 用生成器按需返回成员,比直接返回一个列表更灵活,也更省内存
  • 使用动态解包语法可以方便地合并字典
  • 不要在遍历列表的同时修改,否则会出现不可预期的结果

(6)代码性能要点

  • 列表的底层实现决定了它的头部操作很慢,deque类型则没有这个问题
  • 当需要判断某个成员在容器中是否存在时,使用字典/集合更快

二、重要知识点与使用技巧

(1)基本概念

列表(list)是一种非常经典的容器类型,通常用来存放多个同类对象,比如从1到10的所有整数。一般使用中括号[]或者list()内置函数创建,还有列表推导式。

元组(tuple)和列表非常相似,但跟列表不同,它不能够被修改。这意味着元组完成初始化后就没法再改动了(例外的是,元组中有列表元素,这个列表还是可以更改其中元素的)。一般使用圆括号()或者tuple()内置函数创建,无法直接使用推导式生成元组。

字典(dict)类型存放的是一个个键值对(key:value)。一般使用大括号{}或者dict()内置函数创建,还有字典推导式创建。

集合(set)它的特点是成员不能重复,所以经常用来去重(剔除重复元素),使用大括号表示,但是不能直接使用{}来定义一个空集合,因为{}表示一个空字典。需要使用set()函数来创建。

>>> numbers =[1,2,3,2,3,4,5]
>>> set(numbers)    # 实现去重
{1, 2, 3, 4, 5}   
>>> 


(2)理解列表的可变性

Python里的内置数据类型,大致上可以分为可变与不可变两种。

可变(mutable):列表、字典、集合

不可变(immutable):整数、浮点数、字符串、字节串、元组

在学习Python时,理解类型的可变性是非常重要的一课。下面通过2段代码来理解最常见的场景“函数调用”来演示。

示例一:为字符串追加内容

在这个示例里,定义了一个往字符串追加内容的函数add_str(),并在外层用一个字符串参数调用该函数:

def add_str(in_func_obj):
    '''给一个字符串追加内容'''
    print(f'add_str函数内打印信息,追加内容之前: 函数传入变量in_func_obj = "{in_func_obj}"')
    in_func_obj += ' suffix'
    print(f'add_str函数内打印信息,追加内容之后: 函数传入变量in_func_obj = "{in_func_obj}"')

# 原始字符串变量
orig_obj = 'foo'

# 打印出字符串变量orig_obj的初始化值
print(f'执行add_str()函数前,打印信息,字符串变量初始化值 orig_obj = "{orig_obj}"')

# 调用add_str函数,打印的信息
add_str(orig_obj)                                                                 

# 打印出字符串变量orig_obj的值,执行add_str()函数后
print(f'执行add_str()函数后,打印信息,字符串变量初始化值 orig_obj = "{orig_obj}"')  

运行上面代码会发现,创建的orig_obj(字符串)对象,作为参数传入函数add_str()后,值没有变化。

示例二:为列表追加内容

在下面的代码中,保留一模一样的代码逻辑,但是把orig_obj换成了列表对象:

def add_list(in_func_obj):
    '''给一个字符串追加内容'''
    print(f'add_str函数内打印信息,追加内容之前: 函数传入变量in_func_obj = "{in_func_obj}"')
    in_func_obj += ['baz']
    print(f'add_str函数内打印信息,追加内容之后: 函数传入变量in_func_obj = "{in_func_obj}"')

# 原始字符串变量
orig_obj = ['foo','bar']

# 打印出字符串变量orig_obj的初始化值
print(f'执行add_list()函数前,打印信息,字符串变量初始化值 orig_obj = "{orig_obj}"')

# 调用add_str函数,打印的信息
add_list(orig_obj)                                                                 

# 打印出字符串变量orig_obj的值,执行add_list()函数后
print(f'执行add_list()函数后,打印信息,字符串变量初始化值 orig_obj = "{orig_obj}"')  

执行后会发现结果大不一样,列表变量orig_obj传入函数后,值发生了更改。

这是因为Python在进行函数调用传参时,采用的既不是值传参,也不是引用传参,而是传递了“变量所指对象的引用”。

换个角度说,相当于做了一次变量赋值,执行:in_func_obj = orig_obj

所以,在函数内部执行in_func_obj += ...等操作时,是否会影响外部变量,只取决与in_func_obj所指向的对象本身是否可变。

字符串对象在Python中是不可变的数据类型,经过赋值,是生成了一个新对象(值)in_func_obj,包括对其进行操作。

而列表是可变类型数据对象,经过赋值新变量,但是新变量依然是指向原对象(相当于起了一个别名),对其进行+=操作,原对象orig_obj值也自然是变化的。

(3)函数返回多个结果,其实就是返回元组

在Python中,函数可以一次返回多个结果,其实是通过返回一个元组来实现的。

def get_rectangle():
    """返回长方形的宽和高"""
    width = 100
    height = 60
    return width,height

# 获取函数的多个值
regult  = get_rectangle()
print(regult)   # 输出(100,20),是一个元组类型

# 将函数返回值一次赋值给多个变量,其实就是对元组做了一次解包
width,height = get_rectangle()

print(width,height)   # 输出:100 60

你可能感兴趣的:(python,学习,笔记)