本文继续为大家介绍内建的抽象基类。
Mapping
是只读映射容器的抽象,它继承自Collection
。除了Collection
所必须的__len__
(Sized
),__iter__
(Iterable
)和__contains__
(Container
)之外,Mapping
还定义了一个抽象方法__getitem__
,用于从映射中通过一个key获取value。Mapping
中还给出了一些具体方法,包括:
get
keys
values
items
__eq__
特别得,Mapping
也实现了__contains__
方法:
def __contains__(self, key):
try:
self[key]
except KeyError:
return False
else:
return True
get
方法同我们熟悉的字典的get
方法一样,可以获得一个key对应的value,如果不存在,则返回一个默认值而不是抛出KeyError
异常。
keys
,values
和items
均返回一个新的抽象基类,分别是KeysView
,ValuesView
和ItemsView
,三者的定义在后文介绍。
MutableMapping
是可变映射的抽象,它集成自Mapping
,并定义了两个新的抽象方法:__setitem__
和__delitem__
,分别用于设置键值对和删除键值对。除此之外,它还实现了一些常用的修改方法:
pop
popitem
clear
update
setdefault
这样看来,Python的字典类型dict
和MutableMapping
语义是一致的。没错,dict
被注册成为了MutableMapping
的虚拟子类。那Mapping
有没有对应的内建类型呢?答案是有,但是这一类型真的是“内建”,它并没有成为一个可供用户使用的类型,即——mappingproxy
。在哪里能见到它?类的__dict__
就是mappingproxy
类型的(注意是类的):
class A:
def m(self):
self.a = 1
a = A()
print(type(A.__dict__))
<class 'mappingproxy'>
print(type(a.__dict__))
<class 'dict'>
这里A.__dict__
就是一个只读的映射类型,所以直接为A.__dict__
赋值是不允许的:
A.__dict__['m'] = 1
TypeError: 'mappingproxy' object does not support item assignment
这样设计的原因在于可以限定类的__dict__
的键为字符串,从而加速类属性的访问。
如果我们希望使用mappingproxy
该怎么做呢?直接通过type(A.__dict__)
来创建:
d = {
'a': 1,
'b': 2
}
class A: pass
mp = type(A.__dict__)(d)
print(mp)
{'a': 1, 'b': 2}
print(type(mp))
<class 'mappingproxy'>
mp['c'] = 3
TypeError: 'mappingproxy' object does not support item assignment
另一种方法是从types
标准库中使用MappingProxyType
类型,两种方式是一样的:
import types
d = {
'a': 1,
'b': 2
}
mp = types.MappingProxyType(d)
print(type(mp))
<class 'mappingproxy'>
mp['c'] = 3
TypeError: 'mappingproxy' object does not support item assignment
事实上,types.MappingProxyType = type(type.__dict__)
。
和Mapping
密切相关的一个抽象基类是MappingView
,它继承自Sized
,定义了对于映射的观测抽象。MappingView
通常不会被直接使用到。会用到的是它的三个子类KeysView
,ValuesView
和ItemsView
。顾名思义,三者分别对应于映射中键、值、键值对观测的抽象。在映射中由于键是唯一的,所以为了保证唯一性,KeysView
和ItemsView
也混入了Set
抽象,而ValuesView
则仅仅混入了Collection
。三个抽象基类通过Mapping
的三个方法返回,均可以直接进行遍历等操作。之所以称之为“观测”,是因为当底层Mapping
改变时,观测的值也随之改变:
d = {
'a': 1,
'b': 2
}
class UserMapping(MutableMapping):
def __init__(self, mappings):
self._mappings = mappings
def __getitem__(self, key):
return self._mappings[key]
def __setitem__(self, key, value):
self._mappings[key] = value
def __delitem__(self, key):
del self._mappings[key]
def __iter__(self):
return iter(self._mappings)
def __len__(self):
return len(self._mappings)
um = UserMapping(d)
kv, vv, iv = um.keys(), um.values(), um.items()
print(len(kv))
2
print(2 in vv)
True
for k, v in iv:
print(k, v)
# a 1
# b 2
um['b'] = 3
print(2 in vv)
False
dict
对象通过keys()
,values()
和items()
返回的对象的类型分别被注册为KeysView
,ValuesView
和ItemsView
的虚拟子类:
keytype = type({}.keys())
valuetype = type({}.values())
itemtype = type({}.items())
print(issubclass(keytype, KeysView))
print(issubclass(valuetype, ValuesView))
print(issubclass(itemtype, ItemsView))
True
True
True
来到了我们最熟悉的序列的抽象了。Sequence
表示不可变序列抽象,自然得,MutableSequence
是可变的。Sequence
继承自Reversible
和Collection
,自身定义了一个抽象方法__getitem__
。有趣的是,Sequence
实现了__contains__
,__iter__
和__reversed__
三个方法,所以,继承于Sequence
只需要定义__len__
和__getitem__
即可。Sequence
还实现了两个操作序列的方法:index
和count
,用于索引和计数。MutableSequence
继承自Sequence
,它增加了三个抽象方法,分别是__setitem__
,__delitem__
和insert
。除此之外,我们在使用list
时经常使用的方法也都在MutableSequence
中实现了,例如append
,pop
等,甚至,MutableSequence
还实现了__iadd__
方法,允许对其进行+=
操作。在内建类型中,tuple
,str
,range
和memoryview
被注册为Sequence
的虚拟子类,而list
则被注册为MutableSequence
虚拟子类。
我们来自定义一个直线的整数坐标序列:
class Line(MutableSequence):
def __init__(self, k, b, start=0, end=0):
self._x = list(range(start, end))
self.k, self.b = k, b
self._y = [self._l(x) for x in self._x]
def _l(self, x):
return int(self.k*x + self.b)
def __len__(self):
return len(self._x)
def __getitem__(self, ind):
return self._x[ind], self._y[ind]
def __setitem__(self, index, value):
self._x[index] = value
self._y[index] = self._l(value)
def __delitem__(self, index):
del self._x[index]
del self._y[index]
def insert(self, index, value):
self._x.insert(index, value)
self._y.insert(index, self._l(value))
l1 = Line(k=2, b=1, start=-3, end=3)
l1.append(5)
l1.pop(0)
for point in l1:
print(point, end=' ')
(-2, -3) (-1, -1) (0, 1) (1, 3) (2, 5) (5, 11)
d = (0, 1)
print(l1.index(d))
2