python入门系列:自定义序列类型

序列类型的分类

按照序列存储数据的类型

容器序列:list, tuple, deque

扁平序列:str, bytes, array.array, bytearray

按照序列是否可变

可变序列:list, deque, array.array, bytearray

不可变序列:str, tuple, bytes

序列的abc继承关系

引言

collections.abc模块中有很多内置的抽象基类

序列主要和两个抽象基类有关:Sequence 和 MutableSequence

细节

Sequence

"""

Sequence 抽象基类

它继承了Sized Iterable Contain 这三个抽象基类

"""

class Sequence(Sized, Iterable, Container):

"""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 == value:

return True

return False

def __reversed__(self): # 该魔法函数使得序列类型可以反转

for i in reversed(range(len(self))):

yield self[i]

class Sized(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod

def __len__(self): # 这个魔法函数使得序列类型可以使用 len()获得长度

return 0

@classmethod

def __subclasshook__(cls, C):

if cls is Sized:

if any("__len__" in B.__dict__ for B in C.__mro__):

return True

return NotImplemented

class Iterable(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod

def __iter__(self): # 这个魔法函数使得序列类型可以进行 for 循环

while False:

yield None

@classmethod

def __subclasshook__(cls, C):

if cls is Iterable:

if any("__iter__" in B.__dict__ for B in C.__mro__):

return True

return NotImplemented

class Container(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod

def __contains__(self, x): # 这个魔法函数使得序列类型可以使用 in 操作符

return False

@classmethod

def __subclasshook__(cls, C):

if cls is Container:

if any("__contains__" in B.__dict__ for B in C.__mro__):

return True

return NotImplemented

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

def __iadd__(self, values): # 使得序列可以使用 += 运算符号进行计算

self.extend(values)

return self

# ......只列出几个有代表意义的魔法函数......

# 这些魔法函数使得序列类型可以改变

序列的+、+=和extend

引言

对于可变序列,我们有三种方式来扩充原始序列

三种方式有原理效率上的不同,我们要根据实际情况选择合适的方式来使用

使用范例

"""

普通的加法会在内存中产生一个新的序列对象

时间 空间开销相对比较大

必须要是类型完全一致才能够使用这种合并方式

"""

a, b, c = [1, 2], [3, 4], (5, 6)

d = a + b # [1, 2] + [3, 4]

print(d)

# result:

# [1, 2, 3, 4]

d = a + c # [1, 2] + (5, 6)

print(d)

# result:

# TypeError: can only concatenate list (not "tuple") to list

"""

+= 可以理解为原地加,单纯地在原对象上进行修改,效率比 + 要高很多

这个功能背后有一个魔法函数,__iadd__(self, values)

上一小节中有这个函数具体的实现,我们可以看到,它是调用了 extend()方法

所以本质上,它和调用 extend()函数产生的效果是一模一样的

"""

a, b, c = [1, 2], [3, 4], (5, 6)

a += b

print(a)

# result:

# [1, 2, 3, 4]

a += c

print(a)

# result:

# [1, 2, 3, 4, 5, 6]

鸭子类型那一小节,我们分析了extend()函数的参数要求,只要是可迭代类型都能够作为参数扩充列表。

+=操作本质上就是调用 extend() 函数,所以能将列表元组进行合并也就不奇怪了,因为他们本质上都是可迭代的类型。

实现可切片的对象

引言

切片介绍

# 使用模式:[start:end:step]

"""

start: 切片开始的位置,默认是 0

end: 切片截止(不包括)的位置,默认是列表长度

step: 切片的步长,默认是 1

start和end为默认值时可以省略不写,step为默认值时可以连同最后的冒号一起省略

step为负数时,表示反向切片,此时需满足: start > end

"""

切片举例

li = [8, 4, 3, 2, 1, 7]

li[::] # 返回原序列

li[::-1] # 返回原序列的逆序列表

li[::2] # 隔一个取一个,所有偶数位置

li[1::2] # 隔一个取一个,所有奇数位置

li[3:6] # 取[3, 6)区间内的所有元素,左包含,右不包含

li[0:666] # 结束位置大于列表长度,从尾部截断,返回原列表

li[666:] # 开始位置大于列表长度,直接返回空列表

li = [6, 6]

li[:0] = [1, 2] # 列表头部插入元素 [1, 2, 6, 6]

li[1:1] = [1, 1] # 列表的某一位置插入元素 [6, 1, 1, 6]

li[:1] = [1, 1] # 替换列表元素 [1, 1, 6]

li[1:] = [1, 1] # 替换列表元素 [6, 1, 1]

使用案例

class Group:

def __init__(self, group_name, staffs):

self.group_name = group_name

self.staffs = staffs

def __reversed__(self): # reversed(group)

self.staffs.reverse()

"""

在使用切片和索引访问操作时,相关的参数会传递到 __getitem__(self, item) 中

我们通常希望,切片返回的对象和原对象是同一种类型

使用切片的话,传入的item是一个slice类型

使用索引访问,传入的item是一个int类型

"""

def __getitem__(self, item): # 对象可切片的关键

cls = type(self) # 获得该实例的类型

if isinstance(item, slice):

return cls(self.group_name, self.staffs[item]) # 委托给列表实现切片

elif isinstance(item, int):

return self.staffs[item]

def __len__(self):

return len(self.staffs)

def __iter__(self):

return iter(self.staffs) # 后面会详细讲解

def __contains__(self, item):

return item in self.staffs # 同样委托给列表的in操作来实现

def __str__(self):

return "name:{name}staffs:{staffs}".format(name=self.group_name, staffs=self.staffs)

staffs = ["MetaTian0", "MetaTian1", "MetaTian2"]

group = Group("HIT", staffs)

print(group[1]) # 索引访问

print(group[0:2]) # 切片

reversed(group) # 反转

print(group)

print(len(group)) # 长度

print("MetaTian0" in group, "MetaTian6" in group) # in 操作符

# resutl:

# MetaTian1

# name:HIT; staffs:['MetaTian0', 'MetaTian1']

# name:HIT; staffs:['MetaTian2', 'MetaTian1', 'MetaTian0']

# 3

# True False

bisect模块

引言

如果我们在一个有序序列中需要增加一个元素,但任然要维持序列的有序性,可以使用append()添加元素,再调用sort()来重新排序。

bisect模块用来维持已排序序列(升序)的顺序,效率更高。

两个关键函数insort()和bisect

使用案例

import bisect

int_list = []

"""

在插入过程中维护序列的有序性

"""

bisect.insort(int_list, 2)

bisect.insort(int_list, 6)

bisect.insort(int_list, 1)

bisect.insort(int_list, 3)

bisect.insort(int_list, 5)

print(int_list)

# result:

# [1, 2, 3, 5, 6]

"""

查询一个元素在序列中应该插入的位置

bisect(): 同值元素默认插在右侧

bisect_left(): 同值元素默认插在左侧

"""

print(bisect.bisect(int_list, 3))

print(bisect.bisect_left(int_list, 3))

# result:

# 3

# 2

列表一定是最好的吗

引言

Python中的array,只可以存储指定类型的元素

相当与是C语言中的数组,操作效率比列表更高

官方文档:https://docs.python.org/3.7/library/array.html?highlight=array

使用案例

import array

"""

使用前要引入array模块,要申明存储的对象类型

"""

my_array = array.array("i")

my_array.append(666)

my_array.append(4)

my_array.extend([1, 2, 3])

my_list = my_array.tolist()

# 大部分基本的操作函数和序列差不多,这里就不做过多介绍

print(my_array)

# result:

# array('i', [666, 4, 1, 2, 3])

# [666, 4, 1, 2, 3]

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

引言

推导表达式是一种更加简洁高效,生成相关对象的一种方式

使用案例

"""

列表推导

生成1-20中的奇数

"""

# 普通方式

odd_list = []

for i in range(1, 21):

if i%2 == 1:

odd_list.append(i)

# 列表推导

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

"""

生成器表达式

后面部分会详细介绍其原理

"""

odd_list = (i for i in range(1, 21) if i%2 == 1)

print(type(odd_list))

# result:

#

for item in odd_list:

print(item, end=' ')

# result:

# 1 3 5 7 9 11 13 15 17 19

"""

字典推导式

"""

my_dict = {"a":1, "b":2, "c":3}

reversed_dict = {val:k for k, val in my_dict.items()}

print(reversed_dict)

# result:

# {1: 'a', 2: 'b', 3: 'c'}

喜欢python + qun:839383765 可以获取Python各类免费最新入门学习资料!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容