魔法方法

在python中,有一些内置好的特定的方法,这些方法在进行特定的操作时会自动被调用,称之为魔法方法。

构造和析构

魔法方法总是被双下横线包围,例如__init__;
是面向对象的Python的一切;
他们总是能在适当的时候被调用。

  • __init__(self[, …])
    相当于其他编程语言的构造方法,类在实例化对象的时候首先会调用的一个方法。

  • __new__(cls[, …])
    init并不是实例化对象调用的第一个方法,new方法才是,它的第一个参数是cls,通常情况下是返回cls类的对象,也可以返回其他类的对象。
    new方法是极少去重写它的,Python会默认执行,但是当需要继承一个不可变类型又需要修改的时候,那么就需要重写了。

  • __del__(self)
    当对象将要被销毁的时候这个方法会自动被调用,但是

del x
并不等于调用了
x.__del__()

del方法是当垃圾回收机制,即当没有任何变量去引用这个对象的时候,垃圾回收机制会自动销毁,这时才会调用对象的self方法。

注意:内置的__del__()方法并不是发生del操作的时候就会调用,当对象生成后,所有对它的引用都被del后才会启动垃圾回收机制,才会调用__del__()方法。

算数运算

在Python2.2之前类和类型是分开的,类是属性和方法的封装,类型是如整型、浮点型、字符串这些类型,但是在Python2.2后,试图对两者进行统一,做法就是将int、float、string、list、tuple这些内置函数通常转化为工厂函数。

  • 什么是工厂函数

参考



原因: 当a+b,识别到加法会先调用前者a的add,返回self+other,即返回了a+b,就又运行了加法add,进入了无限递归。
所以在实现的时候,一定要注意避免出现无限递归的情况。
稍作修改

  • 一些算数运算
方法 相应二元算数运算符
__add__(self, other) 定义加法的行为:+
__sub__(self, other) 定义减法的行为:-
__mul__(self, other) 定义乘法的行为:*
__truediv__(self, other) 定义真除法的行为:/
__floordiv__(self, other) 定义整数除法的行为://
__mod__(self, other) 定义取模算法的行为:%
__divmod__(self, other) 定义当被divmod()调用时的行为【divmod(a, b)返回值是一个元组(a//b, a%b)】
__pow__(self, other[, modulo]) 定义当被pow()调用或**运算时的行为
__lshift__(self, other) 定义按位左移位的行为:<<
__rshift__(self, other) 定义按位右移位的行为:>>
__and__(self, other) 定义按位与操作的行为:&
__xor__(self,other) 定义按位异或操作的行为:^
__or__(self, other) 定义按位或操作的行为:|

通过对指定魔法方法的重写,可以让Python根据自己的意图来实现程序

魔法方法参考

  • 反运算

    这里是3-1,并不是1-3,如果想让1-3,那么就要互换int.sub()中self, other的位置,举例说明

定制一个简单的类

  • 需要的资源
    (1)使用time模块的localtime方法获取时间

time模块详解


(2)time.localtime返回struct_time的时间格式
(3)表现你的类:重写__str__和__repr__

  • 程序MyTimer
import time as tclass MyTimer():def __str__(self):return self.prompt__repr__ = __str__# 开始计时def start(self):self.start = t.localtime()print("计时开始...")# 停止计时def stop(self):self.stop = t.localtime()self._calc()print("计时结束...")# 内部方法,计算运行时间def _calc(self):self.lasted = []self.prompt = "总共运行了"for index in range(6):self.lasted.append(self.stop[index] - self.start[index])self.prompt += str(self.lasted[index])


但是这里有一个问题就是,如果定以后直接调用,就会报错

因为这时,prompt还没有被定义,这时就需要所有属于实例对象的变量先在init中定义

import time as tclass MyTimer():# 添加init定义def __init__(self):self.prompt = "未开始计时!"self.lasted = []self.start = 0self.stop = 0def __str__(self):return self.prompt__repr__ = __str__# 开始计时def start(self):self.start = t.localtime()print("计时开始...")# 停止计时def stop(self):self.stop = t.localtime()self._calc()print("计时结束...")# 内部方法,计算运行时间def _calc(self):self.lasted = []self.prompt = "总共运行了"for index in range(6):self.lasted.append(self.stop[index] - self.start[index])self.prompt += str(self.lasted[index])


这时在执行虽然不会直接调用t1出错了,但是运行起来却又出现“整型不能被调用”问题,这里是由于在init中将self.start定义为0导致,因为类的方法名和属性名一样时,属性会覆盖方法,这里就认为start是属性。
这时只需要将start和stop改一下名字即可
并且在这里改变一下显示的方式和累加计时

import time as tclass MyTimer():def __init__(self):self.unit = ['年', '月', '天', '小时', '分钟', '秒']self.prompt = "未开始计时!"self.lasted = []self.begin = 0self.end = 0def __str__(self):return self.prompt__repr__ = __str__def __add__(self, other):prompt = "总共运行了"result = []for index in range(6):result.append(self.lasted[index] + other.lasted[index])if result[index]:prompt += (str(result[index]) + self.unit[index])return prompt# 开始计时def start(self):self.begin = t.localtime()self.prompt = "提示:请先调用 stop() 停止计时!"print("计时开始...")# 停止计时def stop(self):if not self.begin:print("提示:请先调用start()进行计时!")else:self.end = t.localtime()self._calc()print("计时结束...")# 内部方法,计算运行时间def _calc(self):self.lasted = []self.prompt = "总共运行了"for index in range(6):self.lasted.append(self.end[index] - self.begin [index])if self.lasted[index]: # 为0不显示  self.prompt += str(self.lasted[index]) + self.unit[index]# 为下一轮计时初始化变量self.begin = 0self.end = 0

  • 代码存在的问题
    (1)生成的时间会存在负数的情况

    (2)精度不够,只能到秒

属性访问

(1)直接访问属性
(2)通过getattr()访问

(3)利用property(),以属性的方式访问属性

  • __getattr__(self, name)
    定义当用户试图获取一个不存在的属性时的行为
  • __getattribute__(self, name)
    定义当该类的属性被访问时的行为
  • __setattr__(self, name, value)
    定义当一个属性被设置时的行为
  • __delattr__(self, name)
    定义当一个属性被删除时的行为
>>> class C:def __getattribute__(self, name):print("getattribute")return super().__getattribute__(name)def __getattr__(self, name):print("getattr")def __setattr__(self, name, value):print("setattr")super().__setattr__(name, value)def __delattr__(self, name):print("delattr")super().__delattr__(name)

  • 练习
class Rectangle:def __init__(self, width=0, height=0):self.width = widthself.height = heightdef __setattr__(self, name, value):if name == 'square':self.width = valueself.height = valueelse:self.name = valuedef getArea(self):  # 获得面积return self.width * self.height
# 输入 r1 = Rectangle(4, 5)
# 这样写会出现一个无限递归,因为执行__init__中的self.width和self.height赋值语句,会触发__setattr__中的else后的语句self.name  = value,再重复调用__setattr__,这样就会无限递归下去
# =====================================================================================================
# 下面进行改进
class Rectangle:def __init__(self, width=0, height=0):self.width = widthself.height = heightdef __setattr__(self, name, value):if name == 'square':self.width = valueself.height = valueelse:super().__setattr__(name, value)def getArea(self):  # 获得面积return self.width * self.height


另一种改进方法就是给一个特殊属性dict,dict是以字典的形式显示出当前对象的所有属性以及对应的值

class Rectangle:def __init__(self, width=0, height=0):self.width = widthself.height = heightdef __setattr__(self, name, value):if name == 'square':self.width = valueself.height = valueelse:self.__dict__[name] = valluedef getArea(self):  # 获得面积return self.width * self.height

描述符

描述符就是将某种特殊类型的类的实例指派给另一个类的属性。

__get__(self, instance, owner)
# 用于访问属性,返回属性的值
__set__(self, instane, value)
# 将在属性分配操作中调用,不返回任何内容
__delete__(self, instance)
# 控制删除操作,不返回任何内容
  • 实例
class MyDescriptordef __get__(self, instance, owner):print("getting...", self, instance, owner)def __set__(self, instance, value):print("setting...", self, instance, value)def __delete__(self, instnce):print("deleting...", self, instance)class Test:x = MyDescriptor()

【ps:这里改用spyder编辑了,输入处显示方式改变,实际操作同python idle相同】
这里就是将某种特殊类型的类(MyDescriptor)的实例(MyDescriptor())指派给另一个类(Test)的属性(x),就说明MyDescriptor就是x的描述符。

实例化对象后,用text.x强制打印
可以看到打印出三个参数,第一个是self的参数描述符类MyDescriptor本身的实例,第二个是instance的参数类的拥有者Test的实例test,第三个就是拥有者类Test本身
验证一下


对实例化对象进行赋值,出现赋值调用set的特殊方法,打印self、instance和value

del同理,打印self和instance

  • 定义一个MyProperty
    之前提到的property其实就是一个描述符
class MyProperty:def __init__(self, fget=None, fset=None, fdel=None):self.fget = fgetself.fset = fsetself.fdel = fdeldef __get__(self, instance, value):return self.fget(instance)def __set__(self, instance, value):self.fset(instance, value)def __delete__(self, instance):self.fdel(instance)class C:def __init__(self):self._x = Nonedef getX(self):return self._xdef setX(self, value):self._x = valuedef delX(self):del self._xx = MyProperty(getX, setX, delX)


同样的这里将MyProperty的实例MyProperty()指派给类C的属性x,对类C的实例对象c的x属性赋值,调用setX返回c._x再进行操作。

  • 练习
class Celsius:def __init__(self, value = 26.0):self.value = float(value)def __get__(self, instance, owner):return self.valuedef __set__(self, instance, value):self.value = float(value)class Fahrenheit:  def __get__(self, instance, owner):return instance.cel * 1.8 +32def __set__(self, instance, value):instance.cel = (float(value) - 32) / 1.8class Temperature:cel = Celsius()fah = Fahrenheit()

定制容器

  • 协议
    协议Protocols相似于接口,规定了必须要定义的方法。而在Python中协议更像是一种指南。
  • 容器类型的协议
    (1)定制不可变容器
    只需定义__len__()和__getitem__()方法
    (2)定制可变容器
    除__len__()和__getitem__()方法外,还需定义__setitem__()和__delitem__()两个方法

Python魔法方法详解

  • 练习
    编写一个不可改变的自定义列表,要求记录列表中每个元素被访问的次数。
class CountList:def __init__(self, *args): # 星号代表参数是可变数量的self.values = [x for x in args] # 依次取列表中元素self.count = {}.fromkeys(range(len(self.values)),0)# fromkeys 用于创建一个新字典def __len__(self):return len(self.values)def __getitem__(self, key):self.count[key] += 1return self.values[key]

迭代器

提供迭代方法的容器称之为迭代器。
通常的迭代器有序列、列表、元组、字符串、字典、文件。

  • for语句迭代
  • 字典迭代
  • 关于迭代操作,Python提供了两个BIF内置函数inter()和next()
    inter() 即iteration
    对于容器对象调用iter就得到它的迭代器,调用next()就会返回下一个值,当迭代器没有值可以返回了,Python就会抛出一个“StopIteration”的异常,此时迭代结束。

    这样就可以知道for语句是如何执行的
    利用while语句来模拟for语句的执行
  • 迭代器的魔法方法
# 两个魔法方法分别对应两个BIF容器的实现
iter()
--> __iter__()   # 返回迭代器本身
next()
--> __next__()   # 决定迭代器的迭代规则
  • 实现斐波那契数列的打印
class Fibs:def __init__(self, n=10):self.a = 0self.b = 1self.n = ndef __iter__(self):return selfdef __next__(self):self.a, self.b = self.b, self.a + self.bif self.a > self.n:raise StopIterationreturn self.a


生成器

生成器并不涉及魔法方法、类和对对象,只通过普通的函数实现。生成器实际上是迭代器的一种实现。
生成器延续了Python简洁的特点,并且使协同程序的概念得以实现,协同程序就是可以运行的独立函数调用,函数可以暂停或挂起,并在需要的时候从程序离开的地方继续或者重新开始。

Generator 实例

def myGen():print("生成器被执行!")yield 1yield 2# 一旦一个函数中出现yield语句# 那么就说明这个函数被定义为生成器# yield就相当于普通函数中的return# 和return的区别:# 出现yield,就将yield后的参数返回,并暂停在yield处

  • 实现斐波那契数列
def libs():a = 0b = 1while True:a, b = b, a + byield a # 由于有yield,所以while True不会变成死循环

  • 推导式
    (1)列表推导式

    (2)字典推导式

    有“:”的是字典,没有的是集合
    (3)集合推导式

    (4)没有字符串推导式

    (5)元组(tuple)推导式

    打印元组e发现e是一个生成器推导式

    生成器推导式如果作为函数的参数,是可以直接写推导式,不需要加括号
    【100以内不能被2整除的整数和】

[扩展阅读] 提高你的 Python:解释 yield 和 Generators(生成器)

Python学习(六)相关推荐

  1. Python学习六:面向对象编程(上)

    文章目录 前言 一.面向对象编程: 1. oop [object oriented programming] 是一种python的编程思路 2. 解释 3. 面向对象 和面向对象编程 二.类和对象 1 ...

  2. python学习六:数据结构

    数据结构 列表 将列表当做堆栈使用 列表方法使得列表可以很方便的作为一个堆栈来使用,堆栈作为特定的数据结构,最先进入的元素最后一个被释放(后进先出).用 append() 方法可以把一个元素添加到堆栈 ...

  3. Python学习(六) Python数据类型:字典(重要)

    字典dict: 字典其实就相当于java里面的Map,用来存储键值对的.其中存储的数据时无序的. 假如有这样的数据: t1=['name','age','sex'] t2=['tom',30,'mal ...

  4. Python学习系列(六)(模块)

    Python学习系列(六)(模块) Python学习系列(五)(文件操作及其字典) 一,模块的基本介绍 1,import引入其他标准模块 标准库:Python标准安装包里的模块. 引入模块的几种方式: ...

  5. 孤荷凌寒自学python第六十三天学习mongoDB的基本操作并进行简单封装2

    孤荷凌寒自学python第六十三天学习mongoDB的基本操作并进行简单封装2 (完整学习过程屏幕记录视频地址在文末) 今天是学习mongoDB数据库的第九天. 今天继续学习mongoDB的简单操作, ...

  6. Python学习笔记六——画小猪佩奇

    目录 Python学习笔记六--画小猪佩奇 画布 画笔 属性设置 操纵命令 运动命令 画笔控制命令 全局控制命令 其他命令 Python学习笔记六--画小猪佩奇 使用Python的turtle库可以绘 ...

  7. python学习笔记(六)字典

    python学习笔记(六)字典 1.字典初见 python中的字典实际上就是一系列的"键-值"对(key-value),它们之间时无序的,每一个键都必须有与之对应的值,这个值可以是 ...

  8. Python 学习第六讲作业2020-12-28

    Python 学习第六讲作业 lis1 =["name", "author", "introduce"] lis2 =["NOWE ...

  9. Python学习第六课-列表

    Python学习第六课-列表 一.序列 1.1 概念 1.2分类 二.列表 2.1 概念 2.2 练习 三.切片 3.1 可切片对象的索引方式 3.2切片操作 3.3 练习 四.通用操作 4.1 序列 ...

  10. Python学习笔记(六)

    1. IO编程 1.1 文件读写 1.2 StringIO和BytesIO 1.3 操作文件和目录 1.4 序列化 2. 进程和线程 2.1 多进程 2.2 多线程 2.3 ThreadLocal 2 ...

最新文章

  1. mysql中如何设置时区_如何设置MySQL的时区?
  2. @Component、@Repository、@Service、@Controller区别
  3. java 回调模式_总结!!!总结!!!java回调以及future模式
  4. 利用Phtoshop去掉图片中的线性渐变背景
  5. SpringMVC处理模型数据
  6. 管理Win2003sp1防火墙的一点小技巧
  7. CakePHP 2.x十分钟博客教程
  8. IIS6.0服务器架站无法访问解决方案总结
  9. 一个设置容器和网格布局的小技巧
  10. tongweb自动部署_将web应用迁到TongWeb
  11. Android之复合按钮CompoundButton
  12. Nodejs页面访问加载静态资源
  13. java8实现map遍历,map转list,list转map
  14. 迈达斯导出html计算书,MIDAS计算书整理正文..doc
  15. version magic 不一致问题
  16. TotalCommander常用操作
  17. 苹果台式电脑怎么使用计算机,MAC电脑连接台式电脑显示器怎么操作
  18. OUTLOOK 邮箱发件人请求已读回执
  19. 使用MicrobiomeAnalyst统计和功能分析微生物组数据
  20. 微信小程序与公众号区别PHP,微信小程序和微信公众号的区别是什么?

热门文章

  1. tracert 命令详解
  2. Python爬虫入门(二):使用requests和xpath爬取论坛发帖列表
  3. IPC 进程间通信方式——消息队列
  4. 还记得四年前的世界杯吗?
  5. WhbtomT(半路出家) 的每日英语 收集 (一)
  6. http/https与websocket的ws/wss的关系
  7. muduo学习简单介绍
  8. 密码锁 java接口_综合PLC实现密码锁设计(完整图纸).doc
  9. 数据库中的sequences
  10. 【转载】产品经理入门案例分析——锤子手机策略