Python 学习 第二册 第9章 Python魔法方法、特性和迭代器

2024-06-14 10:52

本文主要是介绍Python 学习 第二册 第9章 Python魔法方法、特性和迭代器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

----用教授的方法学习

目录

9.1 构造函数

9.1.1重写普通方法和特殊的构造函数

9.1.2使用函数 super

9.2元素访问

9.2.1 基本的序列和映射协议

9.3特性

9.3.1 函数 property

9.3.2 静态方法和类方法

9.3.3 __getattr__、__setattr__等方法

9.4 迭代器

9.4.1 迭代器协议

9.4.2 从迭代器创建序列

9.5 生成器

9.5.1 创建生成器

9.5.2 递归式生成器

9.5.3 通用生成器

9.5.4 生成器的方法


9.1 构造函数

你可能从未听说过构造函数(constructor),它其实就是本书前面一些示例中使用的初始化方法,只是命名为__init__。然而,构造函数不同于普通方法的地方在于,将在对象创建后自动调用它们。

在Python中,创建构造函数很容易,只需将方法init的名称从普通的init改为魔法版__init__即可。

class FooBar: 

        def __init__(self): 

              self.somevar = 42 

>>> f = FooBar() 

>>> f.somevar 

42 

9.1.1重写普通方法和特殊的构造函数

请看下面两个类:

class A: 

def hello(self): 

print("Hello, I'm A.") 

class B(A): 

pass 

类A定义了一个名为hello的方法,并被类B继承。下面的示例演示了这些类是如何工作的:

>>> a = A() 

>>> b = B() 

>>> a.hello() 

Hello, I'm A. 

>>> b.hello() 

Hello, I'm A. 

由于类B自己没有定义方法hello,因此对其调用方法hello时,打印的是消息"Hello, I'm A."。

B可以重写方法hello,如下述修改后的类B定义所示:

class B(A): 

def hello(self): 

print("Hello, I'm B.") 

这样修改定义后,b.hello()的结果将不同。

>>> b = B() 

>>> b.hello() 

Hello, I'm B. 

9.1.2使用函数 super

对其返回的对象调用方法时,调用的将是超类(而不是当前类)的方法。可像通常那样(也就是像调用关联的方法那样)调用方法__init__。代码:

class Bird: 
def __init__(self): self.hungry = True 
def eat(self): if self.hungry: print('Aaaah ...') self.hungry = False else: print('No, thanks!') class SongBird(Bird): def __init__(self): super().__init__()self.sound = 'Squawk!' def sing(self): print(self.sound)

>>> sb = SongBird() 

>>> sb.sing() 

Squawk! 

>>> sb.eat() 

Aaaah ... 

>>> sb.eat() 

No, thanks! 

9.2元素访问

本节将介绍一组很有用的魔法方法,让你能够创建行为类似于序列或映射的对象。

9.2.1 基本的序列和映射协议

序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需要实现2个方法,而可变对象需要实现4个。

(1)__len__(self):这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键值对数。如果__len__返回零(且没有实现覆盖这种行为的__nonzero__),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。

(2) __getitem__(self, key):这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数(也可以是负数,这将在后面说明),其中n为序列的长度。对映射来说,键可以是任何类型。

(3)__setitem__(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取。当然,仅当对象可变时才需要实现这个方法。

(4) __delitem__(self, key):这个方法在对对象的组成部分使用__del__语句时被调用,应删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。

def check_index(key): """ 指定的键是否是可接受的索引?键必须是非负整数,才是可接受的。如果不是整数,将引发TypeError异常;如果是负数,将引发Index Error异常(因为这个序列的长度是无穷的)""" if not isinstance(key, int): raise TypeError if key < 0: raise IndexError class ArithmeticSequence: def __init__(self, start=0, step=1): """ 初始化这个算术序列start -序列中的第一个值step -两个相邻值的差changed -一个字典,包含用户修改后的值""" self.start = start # 存储起始值self.step = step # 存储步长值self.changed = {} # 没有任何元素被修改def __getitem__(self, key): """ 从算术序列中获取一个元素""" check_index(key) try: return self.changed[key] # 修改过?except KeyError: # 如果没有修改过,return self.start + key * self.step # 就计算元素的值def __setitem__(self, key, value): """ 修改算术序列中的元素""" check_index(key) self.changed[key] = value # 存储修改后的值

这些代码实现的是一个算术序列,其中任何两个相邻数字的差都相同。第一个值是由构造函数的参数start(默认为0)指定的,而相邻值之间的差是由参数step(默认为1)指定的。你允许用户修改某些元素,这是通过将不符合规则的值保存在字典changed中实现的。如果元素未被修改,就使用公式self.start + key * self.step来计算它的值。

下面的示例演示了如何使用这个类:

>>> s = ArithmeticSequence(1, 2) 

>>> s[4] 

>>> s[4] = 2 

>>> s[4] 

>>> s[5] 

11 

请注意,我要禁止删除元素,因此没有实现__del__:

>>> del s[4] 

Traceback (most recent call last): 

File "<stdin>", line 1, in ? 

AttributeError: ArithmeticSequence instance has no attribute '__delitem__' 

9.3特性

Python能够替你隐藏存取方法,让所有的属性看起来都一样。通过存取方法定义的属性通常称为特性(property)。

9.3.1 函数 property

函数property使用起来很简单。如果你编写了一个类,如前一节的Rectangle类,只需再添加一行代码。

class Rectangle: def __init__ (self): self.width = 0 self.height = 0 def set_size(self, size): self.width, self.height = size def get_size(self): return self.width, self.height size = property(get_size, set_size)

在这个新版的Rectangle中,通过调用函数property并将存取方法作为参数(获取方法在前,设置方法在后)创建了一个特性,然后将名称size关联到这个特性。这样,你就能以同样的方式对待width、height和size,而无需关心它们是如何实现的。

>>> r = Rectangle() 

>>> r.width = 10 

>>> r.height = 5 

>>> r.size 

(10, 5) 

>>> r.size = 150, 100 

>>> r.width 

150

实际上,调用函数property时,还可不指定参数、指定一个参数、指定三个参数或指定四个参数。如果没有指定任何参数,创建的特性将既不可读也不可写。如果只指定一个参数(获取方法),创建的特性将是只读的。第三个参数是可选的,指定用于删除属性的方法(这个方法不接受任何参数)。第四个参数也是可选的,指定一个文档字符串。这些参数分别名为fget、fset、fdel和doc。如果你要创建一个只可写且带文档字符串的特性,可使用它们作为关键字参数来实现。

9.3.2 静态方法和类方法

静态方法

和类方法是这样创建的:将它们分别包装在staticmethod和classmethod类的对象中。静态方的定义中没有参数self,可直接通过类来调用。类方法的定义中包含类似于self的参数,通常被命名为cls。

class MyClass: def smeth():print('This is a static method') smeth = staticmethod(smeth) def cmeth(cls): print('This is a class method of', cls)cmeth = classmethod(cmeth)

引入了一种名为装饰器的新语法,可用于像这样包装方法。(实际上,装饰器可用于包装任何可调用的对象,并且可用于方法和函数。)可指定一个或多个装饰器,为此可在方法(或函数)前面使用运算符@列出这些装饰器(指定了多个装饰器时,应用的顺序与列出的顺序相反)。

class MyClass: @staticmethod def smeth(): print('This is a static method') @classmethod def cmeth(cls): print('This is a class method of', cls)

定义这些方法后,就可像下面这样使用它们(无需实例化类):

>>> MyClass.smeth() 

This is a static method 

>>> MyClass.cmeth() 

This is a class method of <class '__main__.MyClass'> 

9.3.3 __getattr__、__setattr__等方法

(1)__getattribute__(self, name):在属性被访问时自动调用(只适用于新式类)。

(2)__getattr__(self, name):在属性被访问而对象没有这样的属性时自动调用。

(3)__setattr__(self, name, value):试图给属性赋值时自动调用。

(4)__delattr__(self, name):试图删除属性时自动调用。

class Rectangle: def __init__ (self): self.width = 0 self.height = 0 def __setattr__(self, name, value): if name == 'size': self.width, self.height = value else: self. __dict__[name] = value def __getattr__(self, name): if name == 'size': return self.width, self.height else: raise AttributeError()

9.4 迭代器

这里只介绍__iter__,它是迭代器协议的基础。

​​​​​​​

9.4.1 迭代器协议

迭代(iterate)意味着重复多次,就像循环那样。方法__iter__返回一个迭代器,它是包含方法__next__的对象,而调用这个方法时可不提供任何参数。当你调用方法__next__时,迭代器应返回其下一个值。如果迭代器没有可供返回的值,应引发StopIteration异常。你还可使用内置的便利函数next,在这种情况下,next(it)与it.__next__()等效。

这个“列表”为斐波那契数列,表示该数列的迭代器如下:

class Fibs: def __init__(self): self.a = 0 self.b = 1 def __next__(self): self.a, self.b = self.b, self.a + self.b return self.a def __iter__(self): return self

注意到这个迭代器实现了方法__iter__,而这个方法返回迭代器本身。在很多情况下,都在另一个对象中实现返回迭代器的方法__iter__,并在for循环中使用这个对象。但推荐在迭代器中也实现方法__iter__(并像刚才那样让它返回self),这样迭代器就可直接用于for循环中。

首先,创建一个Fibs对象。

>>> fibs = Fibs() 

然后就可在for循环中使用这个对象,如找出第一个大于1000的斐波那契数。

>>> for f in fibs: 

... if f > 1000: 

... print(f) 

... break 

... 

1597

这个循环之所以会停止,是因为其中包含break语句;否则,这个for循环将没完没了地执行。

9.4.2 从迭代器创建序列

使用构造函数list显式地将迭代器转换为列表。

>>> class TestIterator: 

... value = 0 

... def __next__(self): 

... self.value += 1 

... if self.value > 10: raise StopIteration 

... return self.value 

... def __iter__(self): 

... return self 

... 

>>> ti = TestIterator() 

>>> list(ti) 

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

9.5 生成器

生成器是一个相对较新的Python概念。由于历史原因,它也被称为简单生成器(simple generator)。生成器是一种使用普通函数语法定义的迭代器。

9.5.1 创建生成器

这个函数将一个类似于下面的列表作为参数:

nested = [[1, 2], [3, 4], [5]] 

函数应按顺序提供这些数字,下面是一种解决方案:

def flatten(nested): for sublist in nested: for element in sublist: yield element

这个函数首先迭代所提供嵌套列表中的所有子列表,然后按顺序迭代每个子列表的元素。倘若最后一行为print(element),这个函数将容易理解得多。

包含yield语句的函数都被称为生成器。这可不仅仅是名称上的差别,生成器的行为与普通函数截然不同。差别在于,生成器不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。

为使用所有的值,可对生成器进行迭代。

>>> nested = [[1, 2], [3, 4], [5]] 

>>> for num in flatten(nested): 

... print(num) 

... 

5

>>> list(flatten(nested)) 

[1, 2, 3, 4, 5] 

9.5.2 递归式生成器

对于每层嵌套,都需要一个for循环,但由于不知道有多少层嵌套,你必须修改解决方案,使其更灵活。该求助于递归了。

def flatten(nested): try: for sublist in nested: for element in flatten(sublist): yield element except TypeError: yield nested

调用flatten时,有两种可能性(处理递归时都如此):基线条件和递归条件。在基线条件下,要求这个函数展开单个元素(如一个数)。在这种情况下,for循环将引发TypeError异常(因为你试图迭代一个数),而这个生成器只生成一个元素。

然而,如果要展开的是一个列表(或其他任何可迭代对象),你就需要做些工作:遍历所有的子列表(其中有些可能并不是列表)并对它们调用flatten,然后使用另一个for循环生成展开后的子列表中的所有元素。这可能看起来有点不可思议,但确实可行。

>>> list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8])) 

[1, 2, 3, 4, 5, 6, 7, 8] 

9.5.3 通用生成器

生成器是包含关键字yield的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器。

生成器由两个单独的部分组成:生成器的函数和生成器的迭代器。生成器的函数是由def语句定义的,其中包含yield。生成器的迭代器是这个函数返回的结果。用不太准确的话说,这两个实体通常被视为一个,通称为生成器。

>>> def simple_generator(): 

yield 1 

... 

>>> simple_generator 

<function simple_generator at 153b44> 

>>> simple_generator() 

<generator object at 1510b0> 

9.5.4 生成器的方法

这个通信渠道包含如下两个端点。

外部世界:外部世界可访问生成器的方法send,这个方法类似于next,但接受一个参数(要发送的“消息”,可以是任何对象)。

生成器:在挂起的生成器内部,yield可能用作表达式而不是语句。换而言之,当生成器重新运行时,yield返回一个值——通过send从外部世界发送的值。如果使用的是next,yield将返回None。

请注意,仅当生成器被挂起(即遇到第一个yield)后,使用send(而不是next)才有意义。要在此之前向生成器提供信息,可使用生成器的函数的参数。

下面的示例很傻,但说明了这种机制:

def repeater(value): while True: new = (yield value) if new is not None: value = new

下面使用了这个生成器:

>>> r = repeater(42) 

>>> next(r) 

42 

>>> r.send("Hello, world!") 

"Hello, world!" 

生成器还包含另外两个方法。

方法throw:用于在生成器中(yield表达式处)引发异常,调用时可提供一个异常类型、一个可选值和一个traceback对象。

方法close:用于停止生成器,调用时无需提供任何参数。

方 法close( 由Python垃圾收集器在需要时调用)也是基于异常的:在yield处引发GeneratorExit异常。因此如果要在生成器中提供一些清理代码,可将yield放在一条try/finally语句中。如果愿意,也可捕获GeneratorExit异常,但随后必须重新引发它(可能在清理后)、引发其他异常或直接返回。对生成器调用close后,再试图从它那里获取值将导致RuntimeError异常。

-----end

这篇关于Python 学习 第二册 第9章 Python魔法方法、特性和迭代器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1060200

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操