第二章 列表与字典

阅读Effective Python(第二版)的一些笔记


目录


原文地址:https://www.cnblogs.com/-beyond/p/15896478.html


第11条 学会对序列做切片

列表切片的格式:data[start:end],start和end可以理解为两个左右指针,需要满足一下条件:

  1. start和end可以是负数、0、整数,并且start、end的绝对值可以大于列表长度;
  2. start可以认为是左指针,end认为是右指针,左指针必须在右指针之前(可以重合);如果左指针在右指针的右边,始终返回空列表
  3. 截取时,会包含start指向的元素,忽略end所在的元素,也就是[start, end)

切片的使用示例:

data = [2, 4, 6, 8, 10]

print(data)  # [2, 4, 6, 8, 10]
print(data[:])  # [2, 4, 6, 8, 10]
print(data[0:])  # [2, 4, 6, 8, 10]
print(data[:len(data)])  # [2, 4, 6, 8, 10]
print(data[0:2])  # [2, 4]
print(data[2:4])  # [6, 8]
print(data[2: -1])  # [6, 8]
print(data[-4: -1])  # [4, 6, 8]
print(data[-9: 10]) # [2, 4, 6, 8, 10]

start跑到end后面的例子:

data = [2, 4, 6, 8, 10]
print(data[3: 2])  # []
print(data[-3: -4])  # []

切片生成的list与原list无关,修改切片生成的list不会影响原list,示例如下:

data = [2, 4, 6, 8, 10]
# 切片生成的数据与原列表无关
d = data[0: 2]
print(d)  # [2, 4]
d[0] = 10
print(d)  # [10, 4]
print(data)  # [2, 4, 6, 8, 10]

第12条 不要在切片里同时指定起止下标与步进

data[start:end]使用切片生成list时,默认是将start~end之间的元素挨个选出来;如果要中间隔一个选一个,也就是指定步长,切片也是支持的,也就是data[start:end:step]

data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(data[0: len(data)])  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(data[0: len(data): 1])    # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(data[0: len(data): 2])    # [1, 3, 5, 7, 9]
print(data[0: len(data): 3])    # [1, 4, 7, 10]

上面的代码比较好理解,但是当start不为0的时候,就稍微有点绕了,比如下面几个例子:

data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(data[3:9:2])  # [4, 6, 8]
print(data[3:9:3])  # [4, 7]
print(data[-10:100:3])  # [1, 4, 7, 10]
print(data[-4:100:3])  # [7, 10]

从上面例子可以看出,如果指定的start为正数还好,但是如果是start是负数,那就有点绕了。

所以在既指定起止下标,且指定步进时,最好拆分为两步,先根据起止下标做一次拷贝生成list,然后再对生成的list做步长的切片,也就是下面这样:

d1 = data[3: 9]
d2 = d1[::3]
print(d2)  # [4, 7]

当然拆分为两步会增加一下内存和时间的开销,因为需要生成一个中间的临时列表,如果内存够用且时间要求不高的情况下,尽量拆分为两步进行。

第13条 通过带星号的unpacking操作来捕获多个元素,不要用切片

unpacking时,需要左边的参数和右边的数据相匹配,如果不匹配就会异常,示例如下:

data = [1, 2]
a, b = data
print("a:{}, b:{}".format(a, b)) # a:1, b:2

data = [1, 2, 3]

# 接收的数量比提供的数量少,异常
# a, b = data   # ValueError: too many values to unpack

# 接收数量比提供的数量多,异常
# a, b, c, d = data # ValueError: need more than 3 values to unpack

当左边的变量比右边提供的参数少的时候,要想让最后一个变量包含剩余的所有参数,可以使用切片来实现,比如下面这样:

data = [1, 2, 3, 4]
a, b = data[:2]
c = data[2:]
print("a:{}, b:{}, c:{}".format(a, b, c))
# a:1, b:2, c:[3, 4]

上面的做法,在Python3中有另外一种写法:带星号的unpacking,示例如下:

data = [1, 2, 3, 4]

a, b, *c = data
print("a:{}, b:{}, c:{}".format(a, b, c))  # a:1, b:2, c:[3, 4]

a, *b, c = data
print("a:{}, b:{}, c:{}".format(a, b, c))  # a:1, b:[2, 3], c:4

*a, b, c = data
print("a:{}, b:{}, c:{}".format(a, b, c))  # a:[1, 2], b:3, c:4

*a, b, c, d = data
print("a:{}, b:{}, c:{}, d:{}".format(a, b, c, d))  # a:[1], b:2, c:3, d:4

*a, b, c, d, e = data
print("a:{}, b:{}, c:{}, d:{}, e:{}".format(a, b, c, d, e))  # a:[], b:1, c:2, d:3, e:4

*a, b, c, d, e, f = data
print("a:{}, b:{}, c:{}, d:{}, e:{}, f:{}".format(a, b, c, d, e, f))  
# ValueError: not enough values to unpack (expected at least 5, got 4)

使用带星号的unpacking时:

  1. 会先将右边的元素先分给左边不带星号的变量,然后将剩余的元素形成一份list给带星号的变量
  2. 这种形式不能同时有多个带星号的变量,因为剩余元素生成list,怎么分给多个带星号变量时不确定的。

第14条 用sort方法的key参数来表示复杂的排序逻辑

list列表的sort方法默认会将数字按照从小到大排序,示例如下;

data = [4, 2, 5, 1, 8, 7, 9]
data.sort()
print(data)  # [1, 2, 4, 5, 7, 8, 9]

如果列表的元素不是数字,而是一些自定义的对象,那么调用sort方法是没有效果的,示例如下:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return "Person={name:%s, age:%s}" % (self.name, self.age)

    __repr__ = __str__

data = [
    Person("abc", 66),
    Person("xyz", 55),
    Person("qwq", 99),
]

print(data[0])
data.sort()
print(data)
# [Person={name:abc, age:66}, Person={name:xyz, age:55}, Person={name:qwq, age:99}]

对于自定义的对象排序,就需要自定义排序的规则,比如比较对象时,怎么确定哪个对象排在前面,那个对象排在

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return "Person={name:%s, age:%s}" % (self.name, self.age)

    __repr__ = __str__


data = [
    Person("abc", 66),
    Person("xyz", 55),
    Person("qwq", 99),
]

print(data[0])
data.sort()
print(data)
# [Person={name:abc, age:66}, Person={name:xyz, age:55}, Person={name:qwq, age:99}]

# key指定比较的字段
data.sort(key=lambda p: p.age, reverse=True)
print(data)
# [Person={name:qwq, age:99}, Person={name:abc, age:66}, Person={name:xyz, age:55}]

第15条 不要过分依赖给字典添加条目时所用的顺序

Python3之前的dict,默认是不保证存取顺序的,也就是说放入的顺序和遍历dict时获取的元素不一定相同;

Python3.7及以后,dict会保证遍历的顺序和放入的顺序相同;

示例如下:

data_1 = {
    "abc": "1111",
    "xyz": "2222",
    "qwq": "3333"
}

print(data_1)
# Python 2
# {'xyz': '2222', 'abc': '1111', 'qwq': '3333'}

# Python 3
# {'abc': '1111', 'xyz': '2222', 'qwq': '3333'}

需要注意的是,python3.8保证dict元素的顺序,这个顺序始终和放入的顺序相同,即使中间更新了value,顺序也不会变,示例如下:

data_1 = {
    "abc": "1111",
    "xyz": "2222",
    "qwq": "3333"
}

print(data_1)
# Python 3
# {'abc': '1111', 'xyz': '2222', 'qwq': '3333'}

data_1["abc"] = "4444"
print(data_1)
# Python 3
# {'abc': '4444', 'xyz': '2222', 'qwq': '3333'}

Python的collections模块有一个OrderedDict类,这个类可以保证遍历顺序和放入的顺序相同

from collections import OrderedDict

ordered_dict = OrderedDict()
ordered_dict.setdefault("abc", "1111")
ordered_dict.setdefault("xyz", "2222")
ordered_dict.setdefault("qwq", "3333")
print(ordered_dict)
# OrderedDict([('abc', '1111'), ('xyz', '2222'), ('qwq', '3333')])

ordered_dict = OrderedDict()
# 传入的dict内部顺序由Python版本确定,python3.7之前无序,python3.7及以后有序
ordered_dict.update({"bbb": "222", "aaa": "111"})
ordered_dict.update({"ccc": "333", "ddd": "444"})
print(ordered_dict)
# python 2
# OrderedDict([('aaa', '111'), ('bbb', '222'), ('ccc', '333'), ('ddd', '444')])
# python 3
# OrderedDict([('bbb', '222'), ('aaa', '111'), ('ccc', '333'), ('ddd', '444')])

第16条 用get处理键不在字典中的情况,不要使用in与KeyError

python中,dict支持两种形式来访问,dict["key"] 和 dict.get("key")的形式,这两种形式有区别:

  1. 对于dict["key"]这种形式,如果dict中没有key,那么就会报KeyError;这个时候可以使用in或者捕获KeyError异常来处理,示例如下
data = {}
name = data["name"] 
if name:
    print("has name")
else:
    print("empty name")
    
# 报错
# KeyError: 'name'


# 使用in判断dict中是否有key
if "name" in data:
    print("has name")
else:
    print("empty name")
    
    
# 利用捕获KeyError异常来看是否有key
name = None
try:
    name = data["name"]
except KeyError:
    pass

上面这几种形式都不推荐使用,而是推荐使用dict.get()的形式。

对于dict.get("key")这种形式,如果dict中没有key,则会返回None;这种形式可以支持设置默认值,也就是不存在key时,返回的默认值,示例如下:

data = {}

name = data.get("name")
print(name)  # None

name = data.get("name", "default_name_val")
print(name)  # default_name_val

第17条 用defaultdict处理内部状态中缺失的元素,而不要用setdefault

有时候会有这种操作:如果dict中没有key对应的值,就设置一个值;如果有key的value,就返回key对应的value,利用上面的get可以这么做:

data = {}
name = data.get("name")
if name:
    print("name:{}".format(name))
else:
    print("设置默认值")
    data["name"] = "abc"

上面可以用下面两种方法进行优化:

data = {}

# 用赋值表达式来减少一行代码
if name := data.get("name"):
    print("name:{}".format(name))
else:
    data["name"] = "abc"

# setdefault(key, default_value),如果存在则直接返回key对应的value
# 如果key不存在,则将key-default_value加入dict,并返回default_val
name = data.setdefault("name", "abc")
print(name)  # abc
print(data)  # {'name': 'abc'}

上面如果使用setdefault方式时,如果dict的value是集合,比如list类型,那么传入的时候,使用不当的话,可能就会出现问题,比如下面的例子:

data = {}
empty_list = []

order_list = data.setdefault("order", empty_list)
order_list.append(1)
print(order_list)  # [1]

index_list = data.setdefault("index", empty_list)
index_list.append(2)
print(index_list)  # [1, 2]

order_list = data.get("order")
print(order_list)  # [1, 2]

由于上面当key不存在的时候,设置的默认值是一个共用的列表变量,所以就会被多处修改,造成问题;当然可以在setdefault的时候设置默认值为空列表就可以了,比如下面这样:

data = {}

order_list = data.setdefault("order", [])
order_list.append(1)
print(order_list)  # [1]

index_list = data.setdefault("index", [])
index_list.append(2)
print(index_list)  # [2]

order_list = data.get("order")
print(order_list)  # [1]

上面虽然可以解决问题,但是不太方便,因为有时候设置的默认只不是一个空的集合,而是一个有数据的集合或者对象,这样的话就需要在setdefault的时候手动写每个默认值的生成方式,如下:

data = {}

# 构建默认数据的函数
def build_default_list():
    return [1, 2, 3]

order_list = data.setdefault("order", build_default_list())
print(order_list)  # [1, 2, 3]

index_list = data.setdefault("index", build_default_list())
print(index_list)  # [1, 2, 3]

order_list.append(4)
print(order_list)  # [1, 2, 3, 4]
print(index_list)  # [1, 2, 3]

而另外一种方式时使用defaultdict,示例如下:

from collections import defaultdict

data = defaultdict(list)  # 传入list表示key不存在时,返回一个空list
# 使用defaultdict的话,不要使用get的形式,因为这样默认值不会生效,应该使用["key"]的形式
print(data.get("index"))
print(data.get("order"))

# 注意defaultdict使用["key"]访问时,如果key不存在,是不会报异常的
# print(data["index"])  # []
# print(data["order"])  # []

自定义生成默认值的例子如下:

from collections import defaultdict

# 生成默认值的方法
def build_default_value():
    return [1, 2, 3]

data = defaultdict(build_default_value)  # 设置key不存在时,生成value的func
index_list = data["index"]
print(index_list)  # [1, 2, 3]

order_list = data["order"]
print(order_list)  # [1, 2, 3]

order_list.append(4)
print(order_list)  # [1, 2, 3, 4]
print(index_list)  # [1, 2, 3]

第18条 学会利用__missing__构造依赖键的默认值

第17条,使用setdefault或者defaultdict都可以实现当key不存在时设置默认值,给出的示例是设置固定的默认值(所有key对应的value一样),如果要根据key进行动态的设置默认值也是可以的,比如下面这样:

data = {}

def build_default_by_key(k):
    return k * 3

key = "abc"
value = data.setdefault(key, build_default_by_key(key))
print(value)  # abcabcabc

但是使用defaultdict的时候,就会有问题了:

from collections import defaultdict

def build_default_by_key(k):
    return k * 3
    
data = defaultdict(build_default_by_key)
value = data["abc"]  # TypeError: build_default_by_key() takes exactly 1 argument (0 given)
print(value)

使用defaultdict报错了,这是defaultdict接受的是无参func(也就是说调用func时没有入参),但是执行的时候build_default_by_key却需要一个入参,所以导致异常。

所以defaultdict不适合这种场景,setdefault还是勉强可以使用的,另外一种方式可以实现这个目的,就是使用对象的__missing__()方法,

对于__missing__方法,官方手册的介绍:此方法由dict.__getitem__()在找不到字典中的键时调用以实现 dict 子类的 self[key]。可能有点难理解,用下面的示例比较好理解:

# 继承自dict
class MyDict(dict):

    # 重写__missing__方法,key就是dict[key],当key不在dict中时,就会执行__missing__
    def __missing__(self, key):
        print("key:{} missing".format(key))
        value = self.build_default_by_key(key)
        self[key] = value
        return value

    @classmethod
    def build_default_by_key(cls, key):
        return key * 3


d = MyDict()
print(d["abc"])
# key:abc missing
# abcabcabc

print(d["xyz"])
# key:xyz missing
# xyzxyzxyz

print(d["abc"])
# abcabcabc
上一篇:ORM(4) 路由层


下一篇:SQL-外连接的用法(部分)