python collections 专用容器数据类型(2)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: python collections 专用容器数据类型(2)

5. 有序字典-OrderedDict

使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。如果要保持Key的顺序,可以用OrderedDict。

有序词典就像常规词典一样,但有一些与排序操作相关的额外功能,popitem() 方法有不同的签名。它接受一个可选参数来指定弹出哪个元素。move_to_end() 方法,可以有效地将元素移动到任一端。


有序词典就像常规词典一样,但有一些与排序操作相关的额外功能。由于内置的 dict 类获得了记住插入顺序的能力(在 Python 3.7 中保证了这种新行为),它们变得不那么重要了。


一些与 dict 的不同仍然存在:


常规的 dict 被设计为非常擅长映射操作。 跟踪插入顺序是次要的。

OrderedDict 旨在擅长重新排序操作。 空间效率、迭代速度和更新操作的性能是次要的。

算法上, OrderedDict 可以比 dict 更好地处理频繁的重新排序操作。 这使其适用于跟踪最近的访问(例如在 LRU cache 中)。

对于 OrderedDict ,相等操作检查匹配顺序。

OrderedDict 类的 popitem() 方法有不同的签名。它接受一个可选参数来指定弹出哪个元素。

OrderedDict 类有一个 move_to_end() 方法,可以有效地将元素移动到任一端。

Python 3.8之前, dict 缺少 __reversed__() 方法。

1671083337932.png

5.1 popitem

语法:popitem(last=True)

功能:有序字典的 popitem() 方法移除并返回一个 (key, value) 键值对。 如果 last 值为真,则按 LIFO 后进先出的顺序返回键值对,否则就按 FIFO 先进先出的顺序返回键值对。

from collections import OrderedDict
d = OrderedDict.fromkeys('abcde')
d.popitem()
print(d)
#OrderedDict([('a', None), ('b', None), ('c', None), ('d', None)])
d = OrderedDict.fromkeys('abcde')
a = ''.join(d.keys())
print(a)
#abcde
d.popitem(last=False)
b = ''.join(d.keys())
print(b)
#bcde

5.2 move_to_end

可以有效地将元素移动到任一端。

from collections import OrderedDict
d = OrderedDict.fromkeys('abcde')
d.move_to_end('b')   #最右端
e = ''.join(d.keys())
print(e)
#acdeb
print(d)
OrderedDict([('a', None), ('c', None), ('d', None), ('e', None), ('b', None)])
d.move_to_end('b', last=False)
g = ''.join(d.keys())
print(g)
#'bacde'

5.3 reversed()

相对于通常的映射方法,有序字典还另外提供了逆序迭代的支持,通过reversed()

from collections import OrderedDict
d = OrderedDict.fromkeys('abcde')
j = list(reversed(d))
print(j)
#['e', 'd', 'c', 'b', 'a']

6. 命名元组-namedtuple

Python 的collections模块提供了一个名为的工厂函数namedtuple(),该函数专门设计用于在处理元组时使您的代码更加Pythonic 。使用namedtuple(),您可以创建不可变的序列类型,允许您使用描述性字段名称和点表示法而不是不清楚的整数索引来访问它们的值。

1、参数介绍

namedtuple(typename,field_names,*,verbose=False, rename=False, module=None)

typename:该参数指定所创建的tuple子类的类名,相当于用户定义了一个新类。

field_names:该参数是一个字符串序列,如 [‘x’,‘y’]。此外,field_names也可直接使用单个字符串代表所有字段名,多个字段名用空格、逗号隔开,如 ‘x y’ 或 ‘x,y’。任何有效的 Python标识符都可作为字段名(不能以下画线开头)。有效的标识符可由字母、数字、下画线组成,但不能以数字、下面线开头,也不能是关键字(如 return、global、pass、raise 等)。

rename:如果将该参数设为 True,那么无效的字段名将会被自动替换为位置名。例如指定['abc','def','ghi','abc'],它将会被替换为 ['abc', '_1','ghi','_3'],这是因为 def字段名是关键字,而 abc 字段名重复了。

verbose:如果该参数被设为 True,那么当该子类被创建后,该类定义就被立即打印出来。

module:如果设置了该参数,那么该类将位于该模块下,因此该自定义类的 __module__ 属性将被设为该参数值。

6.1 与类对比

namedtuple是一个非常简单的元类,通过它我们可以非常方便地定义我们想要的类。

它的用法很简单,我们直接来看例子。比如如果我们想要定义一个学生类,这个类当中有name、score、age这三个字段,那么这个类会写成:

class Student:
    def __init__(self, name=None, score=None, age=None):
        self.name = name
        self.score = score
        self.age = age

namedtuple代码:

from collections import namedtuple
# 这个是类,columns也可以写成'name score age',即用空格分开
Student = namedtuple('Student', ['name', 'score', 'age'])
# 这个是实例
student = Student(name='xiaoming', score=99, age=10)
print(student.name)

6.2 加法

from collections import namedtuple
# 定义命名元组类:Point
Point = namedtuple('Point', ['x', 'y'])
# 初始化Point对象,即可用位置参数,也可用命名参数
p = Point(11, y=22)
# 像普通元组一样用根据索引访问元素
print(p[0] + p[1]) 
33
#执行元组解包,按元素的位置解包
a, b = p
print(a, b) 
11, 22
#根据字段名访问各元素
print(p.x + p.y) 
33
print(p) 
Point(x=11, y=22)

备注:在Python中,带有前导下划线的方法通常被认为是“私有的”。但是,namedtuple提供的其他方法(如._asdict()、._make()、._replace()等)是公开的。


除了继承元组的方法,命名元组还支持三个额外的方法和两个属性。为了防止字段名冲突,方法和属性以下划线开始。

6.3 三个方法

6.3.1 _make(iterable)

from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
t = [14, 55]
Point._make(t)

6.3.2 _asdict()

返回一个新的 dict ,它将字段名称映射到它们对应的值:

from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(x=11, y=22)
p._asdict()
#OrderedDict([('x', 11), ('y', 22)])

6.3.3 _replace(**kwargs)

返回一个新的命名元组实例,并将指定域替换为新的值

p = Point(x=11, y=22)
p._replace(x=33)
Point(x=33, y=22)

6.4 两个属性

6.4.1 _fields

字符串元组列出了字段名。用于提醒和从现有元组创建一个新的命名元组类型。

6.4.2 _field_defaults

字典将字段名称映射到默认值。

Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
Account._field_defaults
{'balance': 0}
Account('premium')
Account(type='premium', balance=0)

6.5 defaults参数

>>> from collections import namedtuple
>>> # Define default values for fields
>>> Person = namedtuple("Person", "name job", defaults=["Python Developer"])
>>> person = Person("Jane")
>>> person
Person(name='Jane', job='Python Developer')
>>> # Create a dictionary from a named tuple
>>> person._asdict()
{'name': 'Jane', 'job': 'Python Developer'}
>>> # Replace the value of a field
>>> person = person._replace(job="Web Developer")
>>> person
Person(name='Jane', job='Web Developer')

6.6 商取余divmod()函数

为了正确看待代码可读性问题,请考虑divmod(). 这个内置函数接受两个(非复数)数字并返回一个元组,其中包含输入值的整数除法得到的商和余数:

>>> divmod(12, 5)
(2, 2)

namedtuple

>>> from collections import namedtuple
>>> def custom_divmod(x, y):
...     DivMod = namedtuple("DivMod", "quotient remainder")
...     return DivMod(*divmod(x, y))
...
>>> result = custom_divmod(12, 5)
>>> result
DivMod(quotient=2, remainder=2)
>>> result.quotient
2
>>> result.remainder
2

6.7 两个坐标

创建Point具有两个坐标 (x和y)的示例 2D 的不同方法namedtuple():

>>> from collections import namedtuple
>>> # Use a list of strings as field names
>>> Point = namedtuple("Point", ["x", "y"])
>>> point = Point(2, 4)
>>> point
Point(x=2, y=4)
>>> # Access the coordinates
>>> point.x
2
>>> point.y
4
>>> point[0]
2
>>> # Use a generator expression as field names
>>> Point = namedtuple("Point", (field for field in "xy"))
>>> Point(2, 4)
Point(x=2, y=4)
>>> # Use a string with comma-separated field names
>>> Point = namedtuple("Point", "x, y")
>>> Point(2, 4)
Point(x=2, y=4)
>>> # Use a string with space-separated field names
>>> Point = namedtuple("Point", "x y")
>>> Point(2, 4)
Point(x=2, y=4)

7. 默认字典-defaultdict

在Python字典中收集数据通常是很有用的。

在字典中获取一个 key 有两种方法, 第一种 get , 第二种 通过 [] 获取.

使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict

>>> favorites = {"pet": "dog", "color": "blue", "language": "Python"}
>>> favorites["fruit"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'fruit'
>>> favorites = {"pet": "dog", "color": "blue", "language": "Python"}
>>> favorites.setdefault("fruit", "apple")
'apple'
>>> favorites
{'pet': 'dog', 'color': 'blue', 'language': 'Python', 'fruit': 'apple'}
>>> favorites.setdefault("pet", "cat")
'dog'
>>> favorites
{'pet': 'dog', 'color': 'blue', 'language': 'Python', 'fruit': 'apple'}

在此示例中,您使用.setdefault()为 生成默认值fruit。由于该键在 中不存在favorites,.setdefault()因此创建它并为其分配 的值apple。如果您.setdefault()使用现有键调用,则调用不会影响字典,并且您的键将保留原始值而不是默认值。


当我使用普通的字典时,用法一般是dict={},添加元素的只需要dict[element] =value即,调用的时候也是如此,dict[element] = xxx,但前提是element字典里,如果不在字典里就会报错


这时defaultdict就能排上用场了,defaultdict的作用是在于,当字典里的key不存在但被查找时,返回的不是keyError而是一个默认值

>>> from collections import defaultdict
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1'] # key1存在
'abc'
>>> dd['key2'] # key2不存在,返回默认值
'N/A'

7.1 int()

int()您可以创建一个合适的计数器来计算不同的对象:

示例1

>>> from collections import defaultdict
>>> counter = defaultdict(int)
>>> counter
defaultdict(<class 'int'>, {})
>>> counter["dogs"]
0
>>> counter
defaultdict(<class 'int'>, {'dogs': 0})
>>> counter["dogs"] += 1
>>> counter["dogs"] += 1
>>> counter["dogs"] += 1
>>> counter["cats"] += 1
>>> counter["cats"] += 1
>>> counter
defaultdict(<class 'int'>, {'dogs': 3, 'cats': 2})

示例2

from collections import defaultdict
s = 'mississippi'
d = defaultdict(int)
for k in s:
    d[k] += 1
j = sorted(d.items())
print(j)
#[('i', 4), ('m', 1), ('p', 2), ('s', 4)]

7.2 set

from collections import defaultdict
s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
d = defaultdict(set)
for k, v in s:
    d[k].add(v)
y = sorted(d.items())
print(y)
#[('blue', {2, 4}), ('red', {1, 3})]

7.3 list()

>>> from collections import defaultdict
>>> pets = [
...     ("dog", "Affenpinscher"),
...     ("dog", "Terrier"),
...     ("dog", "Boxer"),
...     ("cat", "Abyssinian"),
...     ("cat", "Birman"),
... ]
>>> group_pets = defaultdict(list)
>>> for pet, breed in pets:
...     group_pets[pet].append(breed)
...
>>> for pet, breeds in group_pets.items():
...     print(pet, "->", breeds)
...
dog -> ['Affenpinscher', 'Terrier', 'Boxer']
cat -> ['Abyssinian', 'Birman']

8. 映射链-ChainMap

1、ChainMap是什么

ChainMap最基本的使用,可以用来合并两个或者更多个字典,当查询的时候,从前往后依次查询。


ChainMap:将多个字典视为一个,解锁Python超能力。


ChainMap是由Python标准库提供的一种数据结构,允许你将多个字典视为一个。换句话说:ChainMap是一个基于多dict的可更新的视图,它的行为就像一个普通的dict。


ChainMap类用于快速链接多个映射,以便将它们视为一个单元。它通常比创建新字典和多次调用update()快得多。


我知道的用例包括:


通过多个字典搜索

提供链缺省值

经常计算字典子集的性能关键的应用程序

>>> from collections import ChainMap
>>> cmd_proxy = {}  # The user doesn't provide a proxy
>>> local_proxy = {"proxy": "proxy.local.com"}
>>> global_proxy = {"proxy": "proxy.global.com"}
>>> config = ChainMap(cmd_proxy, local_proxy, global_proxy)
>>> config["proxy"]
'proxy.local.com'

ChainMap允许您为应用程序的代理配置定义适当的优先级。键查找搜索cmd_proxy,然后local_proxy,最后global_proxy,返回手头键的第一个实例。在此示例中,用户未在命令行中提供代理,因此您的应用程序使用local_proxy.

8.1 maps

通常,ChainMap对象的行为类似于常规dict对象。但是,它们具有一些附加功能。例如,它们有一个.maps包含内部映射列表的公共属性:

>>> from collections import ChainMap
>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}
>>> alpha_nums = ChainMap(numbers, letters)
>>> alpha_nums.maps
[{'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'}]

instance 属性.maps使您可以访问内部映射列表。此列表是可更新的。您可以手动添加和删除映射、遍历列表等等。

8.2 new_child() 与parents

另外,ChainMap提供一个.new_child()方法和一个.parents属性:

>>> from collections import ChainMap
>>> dad = {"name": "John", "age": 35}
>>> mom = {"name": "Jane", "age": 31}
>>> family = ChainMap(mom, dad)
>>> family
ChainMap({'name': 'Jane', 'age': 31}, {'name': 'John', 'age': 35})
>>> son = {"name": "Mike", "age": 0}
>>> family = family.new_child(son)
>>> for person in family.maps:
...     print(person)
...
{'name': 'Mike', 'age': 0}
{'name': 'Jane', 'age': 31}
{'name': 'John', 'age': 35}
>>> family.parents
ChainMap({'name': 'Jane', 'age': 31}, {'name': 'John', 'age': 35})

使用.new_child(),您可以创建一个新ChainMap对象,其中包含一个新映射 ( son),后跟当前实例中的所有映射。作为第一个参数传递的映射成为映射列表中的第一个映射。如果您不传递地图,则该方法使用空字典。


该parents属性返回一个新ChainMap对象,其中包含当前实例中除第一个之外的所有地图。当您需要跳过键查找中的第一个映射时,这很有用。

8.3 ChainMap 变异操作

最后一个要强调的特性ChainMap是变异操作,例如更新键、添加新键、删除现有键、弹出键和清除字典,作用于内部映射列表中的第一个映射:

>>> from collections import ChainMap
>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}
>>> alpha_nums = ChainMap(numbers, letters)
>>> alpha_nums
ChainMap({'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})
>>> # Add a new key-value pair
>>> alpha_nums["c"] = "C"
>>> alpha_nums
ChainMap({'one': 1, 'two': 2, 'c': 'C'}, {'a': 'A', 'b': 'B'})
>>> # Pop a key that exists in the first dictionary
>>> alpha_nums.pop("two")
2
>>> alpha_nums
ChainMap({'one': 1, 'c': 'C'}, {'a': 'A', 'b': 'B'})
>>> # Delete keys that don't exist in the first dict but do in others
>>> del alpha_nums["a"]
Traceback (most recent call last):
  ...
KeyError: "Key not found in the first mapping: 'a'"
>>> # Clear the dictionary
>>> alpha_nums.clear()
>>> alpha_nums
ChainMap({}, {'a': 'A', 'b': 'B'})

9. 自定义内置插件:UserString、UserList和UserDict

有时您需要自定义内置类型,例如字符串、列表和字典,以添加和修改某些行为。从Python 2.2开始,您可以通过直接对这些类型进行子类化来做到这一点。但是,您可能会在使用这种方法时遇到一些问题,稍后您会看到。


Pythoncollections提供了三个方便的包装类来模拟内置数据类型的行为:


UserString

UserList

UserDict

通过结合常规方法和特殊方法,您可以使用这些类来模拟和自定义字符串、列表和字典的行为。

>>> class LowerDict(dict):
...     def __setitem__(self, key, value):
...         key = key.lower()
...         super().__setitem__(key, value)
...
>>> ordinals = LowerDict({"FIRST": 1, "SECOND": 2})
>>> ordinals["THIRD"] = 3
>>> ordinals.update({"FOURTH": 4})
>>> ordinals
{'FIRST': 1, 'SECOND': 2, 'third': 3, 'FOURTH': 4}
>>> isinstance(ordinals, dict)
True

[]当您使用带方括号 ( )的字典式赋值插入新键时,此字典可以正常工作。但是,当您将初始字典传递给类构造函数或使用.update(). 这意味着您需要覆盖.init(), .update(),可能还有其他一些方法才能使您的自定义字典正常工作。


现在看一下相同的字典,但UserDict用作基类:

>>> from collections import UserDict
>>> class LowerDict(UserDict):
...     def __setitem__(self, key, value):
...         key = key.lower()
...         super().__setitem__(key, value)
...
>>> ordinals = LowerDict({"FIRST": 1, "SECOND": 2})
>>> ordinals["THIRD"] = 3
>>> ordinals.update({"FOURTH": 4})
>>> ordinals
{'first': 1, 'second': 2, 'third': 3, 'fourth': 4}
>>> isinstance(ordinals, dict)
False

有用!您的自定义字典现在将所有新键转换为小写字母,然后再将它们插入字典。请注意,由于您不dict直接继承自,因此您的类不会返回dict上面示例中的实例。


UserDict将常规字典存储在名为 的实例属性中.data。然后它围绕该字典实现其所有方法。UserList并UserString以相同的方式工作,但它们的.data属性分别包含一个list和一个str对象。


如果您需要自定义这些类中的任何一个,那么您只需要覆盖适当的方法并根据需要更改它们的功能。


通常,您应该使用UserDict, UserList, 并且UserString当您需要一个行为几乎与底层包装的内置类相同的类并且您想要自定义其标准功能的某些部分时。


使用这些类而不是内置等效类的另一个原因是访问底层.data属性以直接操作它。


直接从内置类型继承的能力在很大程度上取代了UserDict,UserList和UserString. 然而,内置类型的内部实现使得很难在不重写大量代码的情况下安全地继承它们。在大多数情况下,使用来自collections. 它将使您免于几个问题和奇怪的行为。

10. 结论

在 Python 的collections模块中,您可以使用几种专门的容器数据类型来解决常见的编程问题,例如计数对象、创建队列和堆栈、处理字典中缺少的键等等。

✈推荐阅读:



相关文章
|
3天前
|
Python
【Python操作基础】——数据类型
【Python操作基础】——数据类型
|
3天前
|
存储 Python
python字符串和字节明确数据类型
【5月更文挑战第6天】python字符串和字节明确数据类型
10 2
|
3天前
|
存储 程序员 Python
Python 数据类型转换详解
Python 数据类型转换详解
17 0
|
3天前
|
Python 容器
Python数据类型转换
Python数据类型转换
|
3天前
|
索引 Python
【Python21天学习挑战赛】集合 & 数据类型补充
【Python21天学习挑战赛】集合 & 数据类型补充
|
3天前
|
存储 JSON 运维
第三章 Python丰富的数据类型
第三章 Python丰富的数据类型
|
3天前
|
Python
Python从入门到精通——1.2.2学习基础语法和数据类型之控制结构
Python从入门到精通——1.2.2学习基础语法和数据类型之控制结构
|
3天前
|
机器学习/深度学习 存储 数据挖掘
Python从入门到精通——学习基础语法和数据类型 1.2.1变量、整数、浮点数、字符串、布尔值、列表、元组、字典和集合。
Python从入门到精通——学习基础语法和数据类型 1.2.1变量、整数、浮点数、字符串、布尔值、列表、元组、字典和集合。
|
3天前
|
存储 Python
Python从入门到精通——1.2.3学习基础语法和数据类型之函数定义和调用
Python从入门到精通——1.2.3学习基础语法和数据类型之函数定义和调用
|
3天前
|
存储 Python
Python的变量与数据类型详解
Python的变量与数据类型详解
13 0