Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >魔法方法(2)

魔法方法(2)

作者头像
不可言诉的深渊
发布于 2019-07-26 09:26:26
发布于 2019-07-26 09:26:26
72700
代码可运行
举报
运行总次数:0
代码可运行

特性

在学习面向对象程序设计时,我们通常会学到存取方法,它们是名称类似于getHeight和setHeight的方法,用于获取和设置属性(这些属性可能是私有的)。如果访问给定的时必须采取特定的措施,那么像这样封装状态变量(属性)很重要。例如,请看下面的Rectangle类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  >>> r = Rectangle()
  >>> r.width = 10
  >>> r.height = 5
  >>> r.get_size()
  (10, 5)
  >>> r.set_size((150, 100))
  >>> r.width
  150

get_size和set_size是假想属性size的存取方法,这个属性是一个由width和height组成的元组。(可以将这个属性替换成更有趣的属性,如矩形的面积或其对角线的长度。)这些代码并非完全错误,但存在缺陷。使用这个类时,程序员应无需关心它是如何实现的(封装)。如果有一天你想修改实现,让size成为真正的属性,而width和height是动态计算出来的,就需要提供访问width和height的存取方法,使用这个类的程序也必须重写。应让客户端代码(使用你所编写代码的代码)能够以同样的方式对待所有的属性。

那么如何解决这个问题呢?给所有的属性都提供存取方法吗?这当然并非不可能,但如果有大量简单的属性,这样做就不现实(而且有点傻),因为需要编写大量这样的存取方法,除了获取和设置属性外什么也不做。这将引入复制并粘贴(重复代码)的坏味。显然很糟糕(虽然在有些语言中,这样的问题很常见)。所幸Python能够替你隐藏存取方法,让所有的属性看起来都一样。通过存取方法定义的属性通常称为特性(property)。

在Python中,实际上有两种创建特定的机制,我将重点介绍较新的那种——函数property,它只能用于新式类。随后,我将简单说明如何使用魔法方法来实现特性。

函数property

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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,而无需关心它们是如何实现的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  >>> r = Rectangle()
  >>> r.width = 10
  >>> r.height = 5
  >>> r.get_size()
  (10, 5)
  >>> r.size = 150, 100
  >>> r.width
  150

如你所见,属性size依然受制于get_size和set_size执行的计算,但看起来就像普通属性一样。


注意 如果特性的行为怪异,务必确保你使用的是新式类(通过直接或间接地继承object或直接设置__metaclass__)。不然,特性的获取方法依然正常,但设置方法可能不正常(是否如此取决于使用的Python版本)。这可能有点令人迷惑。


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

本节虽然很短(旨在说明函数property很简单),却非常重要。这里要说明的是,对于新式类,应使用特性而不是存取方法。


函数property工作原理

你可能很好奇,想知道特性是如何完成其魔法的,下面就来说一说。如果你对此不感兴趣,可跳过这些内容。

property其实并不是函数,而是一个类。它的实例包含一些魔法方法,而所有的魔法都是有这些方法完成的。这些魔法方法为__get__、__set__、__delete__,它们一道定义了所谓描述符协议。只要对象实现了这些方法中的任何一个,它就是一个描述符。描述符的独特之处在于其访问方式。例如,读取属性(具体来说,是实例中访问类中定义的属性)时,如果它关联的是一个实现了__get__的对象,将不会返回这个对象,而是调用方法__get__并将其结果返回。实际上,这是隐藏在特性、关联的方法、静态方法和类方法以及super后面的机制。

有关描述符的详细信息,请参阅Descriptor HowTo Guide(http://docs.python.org/3/howto/descriptor.html)。


__getattr__、__setattr__等方法

可以拦截对对象的所有访问企图,其用途之一是在旧式类中实现特性(在旧式类中,函数property的行为可能不符合预期)。要在属性被访问时执行一段代码,必须使用一些魔法方法。下面四个魔法方法提供了你需要的所有功能(在旧式类中,只需使用后面三个)。

  • __getattribute__(self, name):在属性被访问时自动调用(只适用于新式类)。
  • __getattr__(self, name):在属性被访问而对象没有这样的属性时自动调用。
  • __setattr__(self, name, value):试图给属性赋值时自动调用。
  • __delattr__(self, name):试图删除属性时自动调用。

相比函数property,这些魔法方法使用起来要棘手些(从某种程度上来说,效率也更低),但它们很有用,因为你可在这些方法中编写处理多个特性的代码。然而,在可能的情况下,还是使用函数property吧。

再来看前面的Rectangle示例,但这里使用的是魔法方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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.height, self.width
          else:
              raise AttributeError()

如你所见,这个版本需要处理额外的管理细节。对于这个代码示例,需要注意如下两点。

  • 即便涉及的属性不是size,也将调用方法__setattr__。因此这个方法必须考虑如下两种情形:如果涉及的属性为size,就执行与以前一样的操作;否则就使用魔法属性__dict__。__dict__属性是一个字典,其中包含所有的实例属性。之所以使用它而不是执行常规属性赋值,是因为旨在避免再次调用__setattr__,进而导致无限循环。
  • 仅当没有找到指定的属性时,才会调用方法__getattr__。这意味着如果指定的名称不是size,这个方法将引发AttributeError异常。这在要让类能够正确的支持hasattr和getattr等内置函数时很重要。如果指定的名称为size,就使用前一个实现中的表达式。

注意 前面说过,编写方法__setattr__时需要避开无限循环陷阱,编写__getattribute__时亦如此。由于它拦截对所有属性的访问(在新式类中),因此将拦截对__dict__的访问!在__getattribute__中访问当前实例的属性时,唯一安全的方式是使用超类的方法__getattribute__(使用super)。


迭代器

之前粗略地提及了迭代器(和可迭代对象),本节将更详细地介绍。对于魔法方法,这里只介绍__iter__,它就是迭代器协议的基础。

迭代器协议

迭代(iterate)意味着重复多次,就像循环那样。有些人可能之前只使用for循环迭代过序列和字典,但实际上也可迭代其他对象:实现了方法__iter__的对象。

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


注意 在Python3中,迭代器协议有细微的变化。在以前的迭代器协议中,要求迭代器对象包含方法next而不是__next__。


这有什么意义呢?为何不使用列表呢?因为在很多情况下,使用列表都有点像大炮打蚊子。例如,如果你有一个可逐个计算值的函数,你可能只想逐个的获取值,而不是使用列表一次性获取。这是因为如果有很多值,列表可能占用太多的内存。但还有其他原因:使用迭代器更通用、更简单、更优雅。下面来看一个不能使用列表的示例,因为如果使用,这个列表的长度必须是无穷大的!

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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循环中。


注意 更正规的定义是,实现了方法__iter__的对象是可迭代的,而实现了方法__next__的对象是迭代器


首先,创建一个Fibs对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  >>> fibs = Fibs()

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  >>> for f in fibs:
  ...     if f > 1000:
  ...         print(f)
  ...         break
  ...
  1597

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


提示 通过对可迭代对象调用内置函数iter,可获得一个迭代器。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  >>> it = iter([1, 2, 3])
  >>> next(it)
  1
  >>> next(it)
  2

还可使用它从函数或其他可调用对象创建可迭代对象,详情请参阅库参考手册。


从迭代器创建序列

除了对迭代器和可迭代对象进行迭代(通常这样做)之外,还可将它们转换成序列。在可以使用序列的情况下,大多也可使用迭代器或可迭代对象(诸如索引和切片等操作除外)。一个这样的例子是使用构造函数list显式地将迭代器转换为列表。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  >>> 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]

其他魔法方法

特殊(魔法)名称的用途很多,前面展示的只是冰山一角。魔法方法大多是为非常高级的用途准备的,因此这里不详细介绍。然而,如果你感兴趣,可以模拟数字,让对象像函数一样被调用,影响对象的比较方式,等等。要更详细的了解有哪些魔法方法,可参阅“Python Reference Manual”的Special method names一节。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python机器学习算法说书人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
《Python基础教程》 读书笔记 第九章 魔法方法、属性和迭代器 9.5属性
class Rectangle: def __init__(self): self.width=0 self.height=0 def setSize(self,size): self.width,self.height=size def getSize(self): return self.width,self.height
统计学家
2019/04/10
2690
Python中5对必知的魔法方法
在Python中,我们可以使用下划线、字母和数字来命名函数。单词之间的下划线并没有太大的意义——它们只是通过在单词之间创建空格来提高可读性。这就是众所周知的s蛇形命名风格。例如,calculate_mean_score比calculatemeanscore更容易阅读。你可能知道,除了这种使用下划线的常见方式,我们还在函数名之前加上一个或两个下划线(例如:_func,__func) 来表示类或模块内的私有化函数,那些没有以下划线为前缀的名称被认为是公共 API。
老齐
2020/09/24
5580
Python 黑魔法之属性拦截
我们在昨天的文章(Python 黑魔法之内存优化)讲了一种黑魔法,今天我们来讲另一种:「属性拦截」。当我们访问某个类或者是实例属性的时候,如果它不存在的话,就会出现异常。对于异常,我们总是要处理的,接下来就让我们来看,是怎么处理的。
编程文青李狗蛋
2019/11/07
6450
Python入门学习(二)
1 字典 1.1 字典的创建和访问 字典不同于前述的序列类型,它是一种映射类型。它的引入是为了简化定义索引值和元素值存在特定关系的定义和访问问题。 字典的定义形式为:字典变量名 = {key1:val
闪电gogogo
2018/01/08
1.5K0
Python入门学习(二)
Python3 | 练气期,面向对象、类魔术方法,类修饰器!
如果你接触过 Java、Golang 编程语言,那么你一定知道面向对象编程(OOP)的概念。面向对象编程(OOP)是相对于面向过程编程而言的,面向过程编程是一种以过程为中心的开发模式,而面向对象编程则是以对象为中心的开发模式。
全栈工程师修炼指南
2024/07/29
2040
Python3 | 练气期,面向对象、类魔术方法,类修饰器!
Python学习之property
Property函数创建了一个属性size,此时访问器函数被用做参数(先取值,再赋值)。尽管它们看起来像属性一样,但size的特性依旧取决于getSize和setSize的计算。
py3study
2020/01/19
3490
Python学习之property
剖析 Python 面试知识点(一): 魔法方法、闭包/自省、装饰器/生成器
在Python中用双下划线__包裹起来的方法被成为魔法方法,可以用来给类提供算术、逻辑运算等功能,让这些类能够像原生的对象一样用更标准、简洁的方式进行这些操作。 下面介绍常常被问到的几个魔法方法。
天澄技术杂谈
2019/04/05
7060
Python中的魔法方法
python中的魔法方法是一些可以让你对类添加“魔法”的特殊方法,它们经常是两个下划线包围来命名的
deephub
2023/02/01
4240
Python3笔试实际操作基础3.md
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的; 鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。
全栈工程师修炼指南
2020/10/23
8690
Python3笔试实际操作基础3.md
Python 开发者不得不知的魔术方法(Magic Method)
来源:j_hao104 my.oschina.net/jhao104/blog/779743 介绍 在Python中,所有以“__”双下划线包起来的方法,都统称为“Magic Method”,例如类的初始化方法 __init__,Python中所有的魔术方法均在官方文档中有相应描述,但是对于官方的描述比较混乱而且组织比较松散。很难找到有一个例子。 构造和初始化 每个Pythoner都知道一个最基本的魔术方法, __init__ 。通过此方法我们可以定义一个对象的初始操作。然而,当调用 x = SomeCl
小小科
2018/05/04
9690
Python 开发者不得不知的魔术方法(Magic Method)
以往的Python文章总结
笔记;因为Python不像C语言那样的强结构语言,所以我学完C就开始学Python,脑袋嗡嗡的,不过还好,它的赋值很不一般,像C语言第一条应该是先申请一个变量然后在接收赋值,但Python不一样,直接因为赋值是什么类型就变成什么类型的变量。
天钧
2019/12/25
1.5K0
以往的Python文章总结
Python快速学习第七天
魔法方法、属性和迭代器 本文内容全部出自《Python基础教程》第二版 在Python中,有的名称会在前面和后面都加上两个下划线,这种写法很特别。前面几章中已经出现过一些这样的名称(如__future__),这种拼写表示名字有特殊含义,所以绝不要在自己的程序中使用这样的名字。在Python中,由这些名字组成的集合所包含的方法称为魔法(或特殊)方法。如果对象实现了这些方法中的某一个,那么这个方法会在特殊的情况下(确切地说是根据名字)被Python调用。而几乎没有直接调用它们的必要。 本章会详细
汤高
2018/01/11
2.3K0
Python快速学习第七天
Python 魔术方法,属性,迭代器
魔术方法,属性,迭代器 岁月有你,惜惜相处 阅读本文需要5分钟 1.13.1 魔术方法: 在Python中的面向对象中有很多魔术方法如: __init__: 构造函数,在生成对象时调用 __del__: 析构函数,释放对象时使用 __str__: 使用print(对象)或者str(对象)的时候触发 __repr__: 在使用repr(对象)的时候触发 __setitem__ : 按照索引赋值:每当属性被赋值的时候都会调用该方法:self.__dict__[name] = value
Python知识大全
2020/02/13
5960
Python 魔法函数总结
我将此内容从前一节中拿出来使其单独成节,是因为“比较”操作并不局限于数字。许多数据类型都可以进行比较——字符串、列表,甚至字典。如果要创建自己的类,且对象之间的比较有意义,可以使用下面的特殊方法来实现比较。
为为为什么
2022/08/06
6040
Python魔术方法-Magic Method
目录[-] 介绍 在Python中,所有以“__”双下划线包起来的方法,都统称为“Magic Method”,例如类的初始化方法 __init__ ,Python中所有的魔术方法均在官方文档中有相应描述,但是对于官方的描述比较混乱而且组织比较松散。很难找到有一个例子。 构造和初始化 每个Pythoner都知道一个最基本的魔术方法, __init__ 。通过此方法我们可以定义一个对象的初始操作。然而,当调用 x = SomeClass() 的时候, __init__ 并不是第一个被调用的方法。实际上,还有
jhao104
2018/03/20
8630
从 Python 的魔法方法说开去
如果你对 Python 的魔法方法有所了解,就能发现这里的奇怪之处:popen的对象有__next__()方法,但却不能被next()调用,也就不是个迭代器。还有这种事吗?于是我们来看源码,看看popen()到底返回了个什么对象(省略了无关代码):
岂不美哉Frost
2023/10/19
1700
Python编程思想(26):成员变量
李宁老师已经在「极客起源」 微信公众号推出《Python编程思想》电子书,囊括了Python的核心技术,以及Python的主要函数库的使用方法。读者可以在「极客起源」 公众号中输入 160442 开始学习。
蒙娜丽宁
2020/07/02
6660
python面向对象的多态-类相关内置函数-类内置魔法函数-迭代器协议-上下文管理-04
首先强调,多态不是一种特殊的语法,而是一种状态,特性(多个不同对象可以相应同一个方法,长身不同的结果)
suwanbin
2019/09/26
6880
python面向对象的多态-类相关内置函数-类内置魔法函数-迭代器协议-上下文管理-04
Python 的魔法方法及用途
在Python中,所有以 “_ _“ 双下划包起来的方法称为“魔法方法” 魔法方法Python解释器自动给出默认的,因此除非需要改变其内部功能,其它时刻刻使用默认魔法方法
宇宙之一粟
2020/10/26
4810
Python3入门学习三.md
描述: 我们所知道常用的操作系统有Windows,Mac,LINUX,UNIX,这些操作系统底层对于文件系统的访问工作原理是不一样的,因此您可能要针对不同的操作系统来考虑使用那些系统模块,即修改不同的代码。但是Python中有了OS模块,我们不需要关心什么操作系统下使用什么模块,OS模块会帮你选择正确的模块并调用。
全栈工程师修炼指南
2020/10/23
6220
Python3入门学习三.md
相关推荐
《Python基础教程》 读书笔记 第九章 魔法方法、属性和迭代器 9.5属性
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验