Advance Python 04 :自定义序列类型

Introduce

  • python中的序列分类
  • 序列的abc继承关系
  • 序列中+、+=和extend的区别
  • 实现可切片的对象
  • bisect维护已排序序列
  • 什么时候我们不该用列表
  • 列表推导式、生成器表达式和字典推导式

一、Python中的序列分类

序列是 python 中重要的协议

所谓序列,即成员有序排列,可通过下标访问.

常见序列类型包括字符串(普通字符串和unicode字符串),列表和元组.

1、序列分类

按照元素类型是否相同:

  • 容器序列:list、tuple、deque
  • 扁平序列:str、bytes、bytearray、array.array

按照元素是否可变:

  • 可变类型:list、deque、bytearray、array.array
  • 不可变:str、tuple、bytes

2、容器序列和扁平序列的区别?

  • 容器序列

    可以存放不同类型的数据。即可以存放任意类型对象的引用。
    (容器序列可以放任意类型的数据,所以理解为容器)

  • 扁平序列

    只能容纳一种类型

    也就是说其存放的是值而不是引用。换句话说扁平序列其实是一段连续的内存空间,由此可见扁平序列其实更加紧凑。但是它里面只能存放诸如字符、字节和数值这种基础类型。

示例代码:

# 元素类型任意
my_list = list()
my_list.append(100)
my_list.append(True)

# 指定元素类型
import array
my_array = array.array('i')
my_array.append(100)
# 初始化数组需要整型,附加字符串抛异常
my_array.append('abc')

二、序列的abc继承关系

1、概念

python 中内置的 collections.abc 抽象基类,可以帮助我们理解数据类型实现细节

python 是基于协议的语言,结合鸭子类型和魔法函数,就可以达到实现某种类型

**from collections.abc import ***

  • Iterable: iter
  • Reversible: reversed
  • Sized: len
  • Container: contains
  • Collection: Sized, Iterable, Container
  • Sequence: getitem, Reversible, Collection
  • MutableSequence: setitem, delitem, Sequence

不同魔法函数的组合,构建不同的类型

跟容器相关的一些数据结构的抽象基类都是放在collections.abc里,点进去看

__all__ = ["Awaitable", "Coroutine",
           "AsyncIterable", "AsyncIterator", "AsyncGenerator",
           "Hashable", "Iterable", "Iterator", "Generator", "Reversible",
           "Sized", "Container", "Callable", "Collection",
           "Set", "MutableSet",
           "Mapping", "MutableMapping",
           "MappingView", "KeysView", "ItemsView", "ValuesView",
           "Sequence", "MutableSequence",
           "ByteString",
           ]

里面都是抽象基类

"Sequence"(不可变序列)、 “MutableSequence”(可变序列),是序列相关的抽象基类。

2、Sequence(不可变序列)

点击去Sequence看,所有魔法函数构成了序列协议:

class Sequence(Reversible, Collection):

    """All the operations on a read-only sequence.

    Concrete subclasses must override __new__ or __init__,
    __getitem__, and __len__.
    """

    __slots__ = ()

    @abstractmethod
    def __getitem__(self, index):
        raise IndexError

    def __iter__(self):
        i = 0
        try:
            while True:
                v = self[i]
                yield v
                i += 1
        except IndexError:
            return

    def __contains__(self, value):
        for v in self:
            if v is value or v == value:
                return True
        return False

    def __reversed__(self):
        for i in reversed(range(len(self))):
            yield self[i]

Sequence继承了两个类:

  • Reversible是数据的反转
  • Collection

2.1、Reversible

Reversible是数据的反转

2.2、Collection

点进去看Collection

class Collection(Sized, Iterable, Container):

    __slots__ = ()

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Collection:
            return _check_methods(C,  "__len__", "__iter__", "__contains__")
        return NotImplemented

继承了三个抽象基类

  • Sized

    Sized实现了len方法,这样可以计算Collection的长度

  • Iterable

    Iterable实现了迭代器,可以进行for循环

  • Container

    Container实现了contains,可以使用if、in判断。当然如果不实现contains,使用getitem魔法方法,也是可以用if、in判断的。

class Sized(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            return _check_methods(C, "__len__")
        return NotImplemented

Sized实现了len方法,这样可以计算Collection的长度

class Iterable(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterable:
            return _check_methods(C, "__iter__")
        return NotImplemented

Iterable实现了迭代器,可以进行for循环

class Container(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            return _check_methods(C, "__contains__")
        return NotImplemented

Container实现了contains,可以使用if、in判断。当然如果不实现contains,使用getitem魔法方法,也是可以用if、in判断的。

3、MutableSequence(可变数据类型)

再看MutableSequence(可变数据类型):

class MutableSequence(Sequence):

    __slots__ = ()

    """All the operations on a read-write sequence.

    Concrete subclasses must provide __new__ or __init__,
    __getitem__, __setitem__, __delitem__, __len__, and insert().

    """

    @abstractmethod
    def __setitem__(self, index, value):
        raise IndexError

    @abstractmethod
    def __delitem__(self, index):
        raise IndexError
    ......

里面有setitem、delitem魔法函数,分别是添加和删除,还有很多其他的魔法函数,这些魔法函数构成了序列的协议。

三、序列中+、+=和extend的区别

1、综述

综述:

  • 加号 + 会新生成对象,并且 + 两边类型需要一致
  • 加等 += 就地加,只需要可迭代就行
  • append 附加单个元素,extend 扩展多个元素
# + 加新增
l1 = [1, 2]
l2 = l1 + [3, 4]
print("type(l1): %d, and l1: %s" % (id(l1), l1))
print("type(l2): %d, and l2: %s" % (id(l2), l2))

# += 就地加
l1 += ['3', '4']
l1 += ('5', '6')
l1 += range(2)
print("type(l1): %d, and l1: %s" % (id(l1), l1))


# + 两边类型需相同
# += 只需要可迭代的就行,__iadd__ 魔法函数实现

运行结果:

type(l1): 1612537423104, and l1: [1, 2]
type(l2): 1612535559744, and l2: [1, 2, 3, 4]
type(l1): 1612537423104, and l1: [1, 2, '3', '4', '5', '6', 0, 1]

进程已结束,退出代码为 0

2、拓:+=其实是通过__iadd__ 魔法函数实现

+=中可以接任意的序列类型,+=其实是通过一个魔法函数实现的。

MutableSequence(可变序列类型)里面有个__iadd__魔法函数:

def __iadd__(self, values):
    self.extend(values)
    return self

调用的是extend方法:

def extend(self, values):
    'S.extend(iterable) -- extend sequence by appending elements from the iterable'
    if values is self:
        values = list(values)
    for v in values:
        self.append(v)

extend接受一个values,调用for循环,任何序列类型都可以调用for循环,然后依次append进列表。

那么使用extend也是可以的:

示例代码:

a = [1,2]
a += (3,4)
a.extend(range(3))
print(a)

运行结果:

[1, 2, 3, 4, 0, 1, 2]

再说append方法,和extend是不一样的

a = [1,2]
a.append([1,2])
a.append((3,4))
print(a)

运行结果:

[1, 2, [1, 2], (3, 4)]

append是把列表变成了一个元素加入列表中,而不是迭代里面的元素,依次加入列表中。

append不会去做for循环。

四、实现可切片的对象

1、列表切片操作

'''
模式 [start:end:step]

第一个数字 start 表示切片开始位置,默认 0
第二个数字 end 表示切片截止(但不包含)位置,默认列表长度
第三个数字 step 表示切片的步骤,默认为 1

当 start 为 0 时可以省略
当 end 为列表长度时可以省略
当 step 为 1 时可以省略,并且省略步长时可以同时省略最后一个冒号
当 step 为负数时,表示反向切片,这时 start 应该比 end 的值要大才行
'''

a_list = [3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
a_list[::]      # 返回包含原列表中所有元素的新列表
a_list[::-1]    # 返回包含原列表中所有元素的逆向新列表
a_list[::2]     # 隔一个元素取一个,获取偶数位置的元素
a_list[1::2]    # 隔一个元素取一个,获取奇数位置的元素
a_list[3:6]     # 指定切片的开始和结束位置
a_list[0:100]   # 切片结束位置大于列表长度是,从列表尾部截断
a_list[100:]    # 切片开始位置大于列表长度时,返回空列表

a_list[len(a_list):0] = [9]     # 在列表尾部增加元素
a_list[:0] = [1, 2]             # 在列表头部增加元素
a_list[3:3] = [100]             # 在列表中间位置插入元素
a_list[:2] = [100, 200]         # 替换列表元素,等号两边长度相等
a_list[3:] = [4, 5, 6]          # 替换列表元素,等号两边长度可以不相等
a_list[:3] = []                 # 删除列表中前 3 个元素

2、自定义序列类

实现一个支持切片的对象(不可变),根据collections.abc 序列类 Sequence抽象基类里的魔法函数。

  • Group是组的概念

    在生活中很常见,比如学校的学习组,公司的部门;

    对Group实例切片,返回一个Group类型的对象。

  • getitem是切片的关键,item可以是一个slice对象,也可以是一个int。

  • sub_group是一个Group类型对象。

示例代码:

import numbers

class Group:
    # 支持切片操作
    def __init__(self, group_name, company_name, staffs):  # 组名,公司名,员工
        self.group_name = group_name
        self.company_name = company_name
        self.staffs = staffs  # staffs是list

    def __reversed__(self):
        self.staffs.reverse()

    def __getitem__(self, item):
        cls = type(self)  # 获取当前class
        # 返回一个Group类型

        if isinstance(item, slice):
            return cls(group_name=self.group_name,
                       company_name=self.company_name, 
                       staffs=self.staffs[item])
        elif isinstance(item, numbers.Integral):
            return cls(group_name=self.group_name,
                       company_name=self.company_name, 
                       staffs=[self.staffs[item]])

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

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

    def __contains__(self, item):
        if item in self.staffs:
            return True
        else:
            return False

staffs = ["bobby1", "imooc", "bobby2", "bobby3"]
group = Group(company_name="imooc", group_name="user", staffs=staffs)
# 切片
sub_group = group[:2]
# 求长度
print(len(group))
# is、in判断
if "bobby1" in group:
    print ("yes")
# for 迭代
for user in group:
    print(user)
# 反转
reversed(group)
print(group.staffs)

运行结果:

4
yes
bobby1
imooc
bobby2
bobby3
['bobby3', 'bobby2', 'imooc', 'bobby1']

五、bisect维护已排序序列

1、bisect

bisect

  • bisect 维护一个升序的序列
  • 内部二分查找实现,效率高

(用来处理已排序的序列,用来维持已排序的升序序列。)

示例代码:

import bisect

# 处理已排序 序列 升序
# 内部二分查找算法实现

l1 = list()
bisect.insort(l1, 10)
bisect.insort(l1, 3)
bisect.insort(l1, 2)
bisect.insort(l1, 6)

print(l1)   # [2, 3, 6, 10]

2、详细介绍

点进bisect里看,有:

  • insort
  • bisect

insort是用来插入的。

对数据结构了解的话,对已排序的序列,有一种查找效率很高的算法叫二分查找,bisect就是用二分查找来维持已排序序列,包括插入一个数据(insort)、查找数据应该插入什么位置(bisect)。

insort有两个变种的函数insort_right和insort_left,是insort_right的默认

  • insort_left:如果插入的数据在序列中已存在,则新数据插入已存在数据之前
  • insort_right:如果插入的数据在序列中已存在,则新数据插入已存在数据之后

bisect也有两个变种的函数bisect_left和bisect_right,是bisect_right的默认

  • bisect_left:一个数据插入序列的时候,插入位置做左边开始计算
  • bisect_right:一个数据插入序列的时候,插入位置做右边开始计算
import bisect

#用来处理已排序的序列,用来维持已排序的序列, 升序
#二分查找
inter_list = []
bisect.insort(inter_list, 3)
bisect.insort(inter_list, 2)
bisect.insort(inter_list, 5)
bisect.insort(inter_list, 1)
bisect.insort(inter_list, 6)

print(inter_list)
[1, 2, 3, 5, 6]

返回一个排序的序列,所以如果要维护一个排序好的序列,建议使用bisect插入数据,而且二分查性能很高。

print(bisect.bisect(inter_list, 3))
print(bisect.bisect_left(inter_list, 3))
3
2

可以知道3在序列中应该插入的位置是什么。

六、什么时候我们不该用列表

某些应用场景,除了 list 我们还有其他更好的选择

  • 比 list 更好的 python 内置数据结构
  • array 数组 连续的内存空间,性能高(array是c语言中的数组)
  • deque 双向列表

array 数组

array 与 list 一个重要区别,array 只能存储指定的数据类型数据

import array

list
my_array = array.array('i')
my_array.append(100)
my_array.append('abc')

七、列表推导式、生成器表达式和字典推导式

1、列表推导式

1.1、概念

列表推导式,或列表生成式,通过一行代码生成列表

示例代码:

# 提取出 1-20 之间的奇数
odd_list = [i for i in range(21) if i % 2 == 1]
print(odd_list)


# 逻辑复杂的情况
def handle_item(item):
    return item * item


odd_list = [handle_item(i) for i in range(21) if i % 2 == 1]
print(odd_list)

运行结果:

[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]

进程已结束,退出代码为 0

1.2、特点

  • 列表生成式性能高于列表操作
  • 逻辑过于复杂时,列表生成式可读性降低

2、生成器表达式

可迭代

列表推导 [] -> ()

my_gen = (i for i in range(21) if i % 2 == 1)
print(type(my_gen))     # <class 'generator'>
for i in my_gen:
    print(i)

3、字典推导式

比如反转字典key,value的位置

d1 = {'key1': 'value1', 'key2': 'value2'}
d2 = {v: k for (k, v) in d1.items()}
print(d2)

4、集合推导式

set1 = {v for v in d1.values()}
print(set1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DLNovice

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值