《Python工匠》学习笔记----第三章:容器类型

有序:存进去的元素顺序与内存地址顺序相同,即定义什么顺序,取出来还是什么顺序。
可变(动态):可以对容器进行增加、删除等操作
异构:容器内元素类型可以不一致
可重复:容器内元素值可以相等
---------------------------------------------------------------------------------------------------------------------------------

列表:

有序可变、动态、异构、可重复

常用操作:
创建:
#字面量方式,[]直接创建
l1=[1,2,2]

#list(iterable)内置方法,将可迭代对象变为列表
s="abc"
l2=list(s)
print(l2)#['a', 'b', 'c']

#列表推导式
l=[i*2 for i in range(1,10)]
enumerate遍历:

enumerate(),是一个,暂且理解为一个函数,可适用于任何可迭代对象。

l=['a','b','c']
for index,i in enumerate(l):#enumerate()包裹可迭代对象,返回索引及对应值
    print(index,i)
"""
0 a
1 b
2 c
"""
for index,i in enumerate(l,start=10):#start指定起始下标
    print(index,i)
"""
10 a
11 b
12 c
"""
del语句:

列表是一个序列,故于字符串相同,支持切片操作
del语句可直接删除列表元素

l=[1,2,3,4]
del l[3]
print(l)#[1, 2, 3]
del l[1:]
print(l)#[1]
对于可变性的理解:
可变:列表、字典、集合
不可变:元组、字符串、字节串、整数、浮点数

可变类型可进行内容修改,如列表的.append()方法等,对可变对象的修改是在元对象的基础上直接修改。
不可变类型不可进行内容修改,对修改后的内容会生成一个新的不可变对象。

s="123"
print(id(s))#1668721914096
s+="2"
print(id(s))#1668721916656,id改变生成新对象
l=[1,2]
print(id(l))#1668721726912
l.append(3)
print(id(l))#1668721726912,id不变

---------------------------------------------------------------------------------------------------------------------------------

元组:

有序不可变、静态、异构、可重复
元组完成初始化后便不可改变

常用操作:
创建:
# 字面量方式创建
t=(0,1,2)
t2=4,5,6
print(t,t2)#(0, 1, 2) (4, 5, 6)

#tuple()内置函数方式创建
t3=tuple("foo")
print(t3)#('f', 'o', 'o')

#没有元组推导式

函数一次返回多个结果时,结果就是元组
没有“元组推导式”

t=(i*2 for i in range(1,10))
print(t)#t为生成器类型,<generator object <genexpr> at 0x000001C737BFDA80>
tuple2=tuple(t)
print(tuple2)#(2, 4, 6, 8, 10, 12, 14, 16, 18),通过内置函数调用生成器,生成元组

多用于存放结构化数据

具名元组:解决普通元组的数字索引意义不明的问题
from collections import namedtuple

#定义方式一
nameTu=namedtuple('nameTu','width,height')

#定义方式二
nameTu2=namedtuple('nameTu2','width height')

#定义方式三
list=['width', 'height']
nameTu3=namedtuple('nameTu3',list)

#赋值方式一
rect=nameTu(100,200)

#赋值方式二
rect2=nameTu2(width=300,height=400)
print(rect[0])#100
print(rect.width)#100,顺序赋值属性,width=100

Python3.6版本后,可通过继承typing.NameTuple类进行定义具名元组

from typing import NamedTuple

class Rect(NamedTuple):
    age:int
    name:str

r=Rect(21,"小明")
print(r.name,r.age)#小明 21

---------------------------------------------------------------------------------------------------------------------------------

集合:

无序可变、动态、异构、不可重复
集合元素无法重复,可用这个性质进行去重

l=[1,1,2,3]
print(set(l))#{1, 2, 3}

(1)创建

#1.字面量
s={1,2,3}

#2.set()方法
s2=set()#空集合,不能使用{},{}代表空列表

#3.集合推导式
list=[1,2,3,4,5]
s3={n for n in list}

(2)fronzenset

不可变集合,少了所有修改方法

(3)集合运算

交集a & b 或者 a.inersection(b)
并集a | b 或者 a.union(b)
差集a - b 或者 a.difference(b)

(4)集合只能存放可哈希对象

set={1,"iop",2.4,(1,2),[8,9]}#TypeError: unhashable type: 'list'

可哈希类型:所有不可变内置类型、用户自定义的类型
不可哈希类型:可变内置类型,如字典、列表等

注意:只有可哈希类型放进集合或者作为字典的使用

---------------------------------------------------------------------------------------------------------------------------------

字典:

无序(Python 3.6及以后为有序)可变、动态、异构、不可重复(key)
键值对形式存放元素,每个类实例的所有属性都存在名为__dict__的字典里

class M:
    def __init__(self,age):
        self.age=age
m=M(21)
print(m.__dict__,type(m.__dict__))#{'age': 21} <class 'dict'>

(1).items()遍历—同时获取key、value

dic={"name":"xx","age":21}
for key,value in dic.items():
    print(key,value)# name xx
                    # age 21

(2).get(key,default)访问—避免KeyError报错

dic={"name":"xx","age":21}

#正常访问不存在的键,报错KeyError,需要异常捕获或者设置分支判断
print(dic["sex"]) #KeyError: 'sex'

#使用.get()方法访问不存在键值,输出default默认值,不报错
print(dic.get("sex","男"))#男

(3).setdefault(key,default=None)—取值并修改
适用场景:

当需要修改字典内"xxx"键值所对应的内容时,不确定是否存在"xxx"键值,所以需要进行异常捕获或者判断。
例如"xxx"键值对应一个列表 :
(1)当xxx存在,则往列表里添加新内容
(2)当xxx不存在,则初始化键值对,初始化空列表

传统写法:

try:
    dic["xxx"].append("value")
except KeyError:
    dic["xxx"]=[]

.setdefault():

"""
1.若key不存在,则使用default指定的值初始化键值对key:default,写入字典
2.若key存在,则返回对应键值对的值,本例中返回列表对象,所以可直接调用.append()方法
"""
dic.setdefault("xxx",[]).append("value")

(4).pop(key,default)—删除并返回删除值

与del语句的区别;
del dic[key] 当键值不存在时,会报错KeyError
dic.pop(key,None) 当键值不存在时,返回None值,而不报错,ps:可指定其他default返回值

(5)字典推导式
代码很好理解:

dic={"name":"xx","age":21}
dic2={key:value*10 for key,value in dic.items() if key=="age"}
print(dic2)#{'age': 210}

字典无序性的理解

Python3.6版本及以后,优化了字典底层实现,已经变为有序性,Python3.6作为“隐藏属性”,3.7正式通告

字典的无序性是由于底层使用了哈希表,当存放一堆键值对时,Python根据key值通过哈希算法计算出对应的哈希值,并通过这个哈希值决定该键值对在表中的位置,因此字典的具体顺序和定义时的key值顺序就没有必然联系。
OrderedDict有序字典类

from collections import OrderedDict

dic=OrderedDict()
dic['fir']=1
dic['sec']=2

可通过以上方式直接定义有序字典类,注意:
(1)key值顺序将作为OrderedDict实例对象是否相等的判断依据

defaultdict类型

defaudict(fun)是一种特殊的字典类型,初始化时,接受一个**可调用对象fun(个人理解为有返回值的函数)**作为参数。当执行d[key]操作时,若key不存在,则调用fun,并将返回值得结果作为value保存于key键值对应。

from collections import defaultdict

def fun():
    return 1
dic=defaultdict(fun)
print(dic["不存在的key1"])#1,key不存在,调用fun(),得到返回值1,则  “不存在的key1”:1
print(dic["不存在的key2"]+1)#2,原理同上

MutableMapping自定义字典类型

from collections.abc import MutableMapping
from collections import defaultdict

class MyDict(MutableMapping):#为避免逻辑冲突而导致不一致,建议统一继承该抽象类
    def __init__(self):
        self.data={}
        #self.data=defaultdict(int),采用defaultdict类型也合理,也适配性,int()为整型初始化函数,默认返回0

    def __getitem__(self, key):#返回值函数,可自定义处理函数
        return self.data[key]

    def __setitem__(self, key, value):
        self.data[key]=value

    def __delitem__(self, key):
        del self.dat[key]

    def __len__(self):
        return len(self.data)

    def __iter__(self):
        return iter(self.data)

mydic=MyDict()
mydic["name"]="xxx"
print(mydic.get("name", default=None))#xxx

继承MutableMapping抽象类而不是dict的原因是:_ setitem _方法仅重写了d[key]=value这种赋值操作的逻辑,而对d.update()操作则无法覆盖,因此若继承dict类型,对于类似两种操作将会产生行为冲突,d[key]=value会是一个逻辑,而d.update()将会遵循dict的未被重写的逻辑。

---------------------------------------------------------------------------------------------------------------------------------

深拷贝与浅拷贝

a=[1,2,3,4]
b=a
a.pop()
print(b)#[1,2,3]

也就是说,b与a共同指向一个列表对象,当a改变这个共有对象,则b也随之改变,拷贝则是为了将a与b分离,成为两个独立的对象。

浅拷贝

浅拷贝的方式:
(1) copy模块的copy()方法:b=copy.copy(a)
(2) 支持推导式的类型(字典、集合、元组等),使用推导式创建
(3) 各容器类型的内置构造函数:b=list(a)
(4) 支持切片操作的类型,使用全切片:b=a[ : ]
(5) 列表、字典自身提供的copy方法

深拷贝

a=[1,2,["x","y"],3]
b=copy.copy(a)#浅拷贝
a[0]=100
a[2].pop()
print(b)#[1,2,["x"],3]

也就是说,当对象存在多级嵌套时,浅拷贝只能拷贝第一层的对象,对于嵌套在列表中的第二级列表元素a[2]与b[2]指向的仍然是同一个对象,所以要想进行完全的分离,则需要进行深拷贝。
(1) 使用copy模块下的deepcopy方法:b=copy.deepcopy(a)

---------------------------------------------------------------------------------------------------------------------------------

生成器—按需生成

range()是python内置函数,返回数字序列
Python2版本中,range将所定义的数字序列组装成一个列表,一次性返回,因此当序列很大时,将运行非常慢。
Python3版本中,range将返回一个类型为range的惰性计算对象,只有当遍历该对象时,才会按需返回对应数字,大大提升效率。

**生成器(generator)**是一种特殊的数据类型,它会不断给调用方生成内容,自定义生成器如下:

def my_generator(max_number):
    for i in range(0,max_number+1):
        yield i

for i in my_generator(5):
    print(i)#0 1 2 3 4 5

yield与return的区别就是,return是一次性返回,且中断函数执行,而yield可以逐步给调用方返回结果(利用**next()**函数)

def my_generator(max_number):
    for i in range(0,max_number+1):
        yield i

def my_generator2(max_number):
    for i in range(0,max_number+1):
        return i


i=my_generator(5)
print(next(i))#0
print(next(i))#1

i2=my_generator2(5)
print(i2)#0,因为第一次执行到return语句后,函数中断执行

生成器是可迭代对象,可以直接转换为其他容器类型

i=my_generator(5)
list=list(i)
print(list)#[0, 1, 2, 3, 4, 5]
常见代码重构
def my_generator(max_number):
    for i in range(0,max_number+1):
        yield i

def return_list(max_number):
    list=[]
    for i in range(0,max_number+1):
         list.append(i)
    return list

for i in return_list(100000000000):
    if i==2:
        break   #当遍历到i==2时,程序退出,但是由于return一次性返回,所以0--99999999999的列表已经生成完毕,所以消耗了巨大的内存空间资源
    
for i in my_generator(10000000000):
    if i==2:
        break  #当遍历到i==2时,程序退出,生成器也不再生成数字,按需生成,节省了大量空间。

---------------------------------------------------------------------------------------------------------------------------------

常见性能陷阱

列表底层使用数组结构实现,因此:

  1. 对于.append()方法,只在列表后添加元素,无需考虑其他元素,复杂度O(1)
  2. 对于.insert()方法,只在列表中间添加元素,其他元素也要做出调整,复杂度O(n)

使用deque数据结构(queue队列结构)可避免insert方法的性能缺陷

from collections import deque

d=deque()
d.append(1)
d.appendleft(2)#相当于insert(0,2)
print(d)#deque([2, 1])

判断元素是否存在于列表,item in list ,底层原理为遍历列表,直到找到匹配元素,复杂度O(N)
集合与字典底层通过哈希表结构实现,因此:

  1. 判断item是否存在于集合中,底层通过hash(item) 计算出哈希值,再检测哈希表对应位置item是否存在即可,复杂度为O(1)
  2. 判断key值是否存在于字典中道理同上。

因此,判断元素是否存在于列表中时,可以将列表转为集合s=set(list) 再进行判断。

---------------------------------------------------------------------------------------------------------------------------------

字典的快速合并—**解包与*解包

合并两个字典(d1与d2):
(1)d1.update(d2) d1也会随之改变,不完美
(2)d3=d1.copy() d3.update(d2) 过于繁琐,差点意思
(3){"d2":"这是d2" , **d1} 利用**解包字典,完美

**可用于解包字典,*可用于解包任何可迭代对象。

[1,2,3,*range(4,6)]#[1,2,3,4,5]

Pyhton 3.9发布了 | 运算符以进行字典的合并。

d1={"name":"d1"}
d2={"name":"d2" , "age":"18"}

d1|d2 #{"name":"d2" , "age":"18"},相当于d2覆盖d1
d2|d1 #{"name":"d1" , "age":"18"},相当于d1覆盖d2

---------------------------------------------------------------------------------------------------------------------------------

使用有序字典快速去重—OrderedDict

num=[1,2,2,3,4]
from collections import OrderedDict

nums=[1,1,2,3,3,4,5] 

s=set(nums) # 使用集合去重,结果无序

# .formkeys(),参数为可迭代对象,由可迭代对象作为键值,创建列表,默认value值为None
# .keys(),输出字典的键值,因为键不可重且有序
new_list=list(OrderedDict.fromkeys(nums).keys())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值