• 什么是魔法方法
  • 构造和析构
    • __init__方法
    • __new__方法
    • __del__方法
  • 工厂函数
    • 通过对魔法方法的重写,还可以自定义对象间的算术运算
    • 反运算方法
  • 实现一个计时器的类
    • time模块的localtime方法获取时间,time.localtime返回struct_time的时间格式
    • __str__方法
    • __repr__方法实现输入一个对象,就返回一个字符串的功能
  • 属性访问
    • 调用基类的方法避免死循环问题
    • 给一个特殊属性dict赋值
  • 描述符(property属性)
  • 容器的定制
  • 迭代器( 待补充)

什么是魔法方法

  • 魔法方法总是被双下划綫包围,例如__init__
  • 魔法方法是面向对象的Python的一切,功能强大
  • 魔法方法的魔力体现在他们总是能够在适当的时候被调用

构造和析构

__init__和__new__方法是Python的构造器

__init__方法


类似于其他语言的构造方法(C++,Java),即类在实例化对象时首先会调用的方法。

在类定义时,为何有时写__init__方法,有时却没有?
A:根据需求看是否需要传值。没有写__init__时Python会自动的调用它。

# 求长方形(x,y)的周长和面积
>>> class Rectangle:def __init__(self,x,y):self.x=x   # self.x是类实例化之后的局部变量,等式右边的x是传入的参数self.y=ydef getPeri(self):return (self.x+self.y)*2def getArea(self):return self.x*self.y>>> rect=Rectangle(3,4)
>>> rect.getPeri()
14
>>> rect.getArea()
12

__init__不能有返回值,只能返回一个None,否则会抛出TypeError异常

>>> class A:def __init__(self):return 'A'>>> a=A()
Traceback (most recent call last):File "<pyshell#17>", line 1, in <module>a=A()
TypeError: __init__() should return None, not 'str'

__new__方法


它的第一个参数是class(这个类),__new __在__init__之前被调用,如果new后面有参数,会将参数原样传给__init__方法。

极少重写new方法,但是当继承不可变类型且需要进行修改时,需要通过重写new方法实现
如将不可变的str类型实现全部大写的功能

__del__方法

当对象被销毁时,就会自动调用该方法,_del_ 方法是垃圾回收机制,即当变量的引用计数为零时,垃圾回收机制会自动的回收和销毁,这时才会调用该对象的__del__方法

>>> class C:def __init__(self):print('正在调用ini方法。。。')def __del__(self):print('正在调用del方法。。。')>>> c1=C()
正在调用ini方法。。。
>>> c2=c1
>>> c3=c2
>>> del c3
>>> del c2
>>> del c1
正在调用del方法。。。

工厂函数

工厂函数看上去有点像函数,实质上他们是类,当你调用它们时,实际上是生成了该类型的一个实例,就像工厂生产货物一样。当类在定义的时候称为类,定义结束后就成为类对象,而工厂函数实际上就是类对象。

>>> a=int('123')   # 实例化一个int对象,返回一个实例后的对象,将123传进去
>>> a
123
>>> b=int('456')
>>> a+b
579

通过对魔法方法的重写,还可以自定义对象间的算术运算


举一个神奇的“栗子”:

>>> class New_int(int):def __add__(self,other):return int.__sub__(self,other)def __sub__(self,other):return int.__add__(self,other)>>> a=New_int(3)
>>> b=New_int(5)
>>> a+b  # '+'运算符调用__add__方法,实际上是减法,因为在定义时重写了__add__方法
-2
>>> a-b   # '-'运算符调用__sub__方法
8

错误的写法会导致无限递归

对于上述错误的改进:

反运算方法

如果右操作数的类型是左操作数类型的子类,并且该子类提供了操作的反射方法,则该反射方法将在左操作数的非反射方法之前被调用。

>>> class Nint(int):def __radd__(self,other):return int.__sub__(self,other)>>> b=Nint(3)
>>> a=Nint(5)
>>> a+b
8
>>> 1+b  # 这里self是b,other是‘1’
2

重写反运算要注意顺序问题

>>> class Nint(int):def __rsub__(self,other):return int.__sub__(self,other)>>> a=Nint(5)
>>> 3-a   # a是self,other是‘3’
2
>>> class Nint(int):def __rsub__(self,other):return int.__sub__(other,self)>>> a=Nint(5)
>>> 3-a
-2

实现一个计时器的类

详细需求如下图:

要实现以上需求,需要用到的内容如下:

time模块的localtime方法获取时间,time.localtime返回struct_time的时间格式

详见 time 模块详解

__str__方法

>>> class A():def __str__(self):     return 'Python'>>> a=A()
>>> print(a)  # 字符串输出时会自动调用__str__魔法方法,如果返回值,会返回内存地址

__repr__方法实现输入一个对象,就返回一个字符串的功能

>>> class B():def __repr__(self):return 'hello world'>>> b=B()
>>> b
hello world

用 print 函数打印a对象会隐式地调用对象的 str() 方法,直接输出对象会隐式的调用对象的 repr() 方法。
第一步:实现最基本最主要的三个方法,代码如下所示:

第二步:假设计时器对象t1,print(t1)和直接调用t1的显示结果,注意在init方法定义变量,否则可能会抛出AttributeError异常

使用init方法初始化变量需要注意变量命名问题,如果定义的属性名和方法名相同,属性会覆盖方法,抛出TypeError

修改如下:

第三步:当计时器未启动或已经停止计时,调用stop方法会给予提示

第四步:两个计时器对象可以进行相加:t1 + t2

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__  # 把__str__赋值过去,str指向什么,repr就会指向什么# 重写__add__方法,两个计时器相加返回一个计算结果def __add__(self,other):self.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])  # localtime 取前六个位置,将每个位置的值相减,存放在lased列表中if self.lasted[index]:self.prompt += (str(self.lasted[index]) + self.unit[index])# 为下一轮计时初始化变量self.begin = 0self.end=0print(self.prompt)

属性访问

重写Python魔法方法可以控制对象的属性访问

举个例子来更好的理解他们的触发情况:

>>> 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)>>> c=C()
>>> c.x
getattribute  # 获取属性的第一步
getattr    # 获取不存在的属性时才会访问
>>> c.x=1
setattr
>>> c.x
getattribute
1
>>> del c.x
delattr

** 死循环陷阱**

举一个“生动形象的例子”:
写一个矩形类,默认有宽和高两个属性,如果为一个叫square的属性赋值,那么说明这是一个正方形,值就是正方形的边长,此时宽和高都应该等于边长。

调用基类的方法避免死循环问题

class Rectangle:def __init__(self,width=0,height=0):self.width=widthself.height = heightdef __setattr__(self,name,value):if name=='square':self.width=vlaueself.height=valueelse:super().__setattr__(name,value)def getArea(self):return self.width*self.height

给一个特殊属性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]=value   # 法二# 法一:super().__setattr__(name,value)# self.name=valuedef getArea(self):return self.width*self.height

描述符(property属性)

描述符就是实现了以下三种具有描述符属性的方法(至少一种就算描述符), peoperty本质上是一个描述符类。

>>> class MyDescriptor:def __get__(self,instance,owner):print("geting..",self,instance,owner)def __set__(self,instance,value):print("seting...",self,instance,value)def __delete__(self,instance):print("del....",self,instance)>>> class Test:x=MyDescriptor()  # MyDescriptor类是描述符类>>> test=Test()
>>> test.x
geting.. <__main__.MyDescriptor object at 0x0000021E74CEAE50> <__main__.Test object at 0x0000021E74CEAD60> <class '__main__.Test'>
>>> test     # 对应instance
<__main__.Test object at 0x0000021E74CEAD60>
>>> Test      # 对应owner
<class '__main__.Test'>
>>> test.x='X'
seting... <__main__.MyDescriptor object at 0x0000021E74CEAE50> <__main__.Test object at 0x0000021E74CEAD60> X
>>> del test.x

容器的定制

必须知道的容器类型的协议

  • 如果需要定制的容器是不可变的话,只需要定义__len__()和__getitem__()方法
  • 如果需要定制可变容器的话,除了__len__()和__getitem__(),还需要定义__setitem__()和__delitem__()

练习
编写一个不可改变的自定义列表,要求记录列表中每个元素被访问的次数

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]

执行结果如下

=========================== RESTART: F:/Pythonspace/countlist.py ===========================
>>> c1=Countlist(1,3,5,7,9)
>>> c2=Countlist(2,4,6,8,10)
>>> c1[1]
3
>>> c2[1]
4
>>> c1[1]+c2[1]
7
>>> c1.count
{0: 0, 1: 2, 2: 0, 3: 0, 4: 0}    # 1被访问了两次
>>> c1[1]
3
>>> c1.count
{0: 0, 1: 3, 2: 0, 3: 0, 4: 0}

迭代器( 待补充)

列表,元组,字符串,字典都是大家熟知的迭代器,文件也支持迭代操作,通常使用for语句来迭代他们,迭代器主要是通过如下两个BIF来实现的,

  • iter()

  • next() (主要指定迭代器的规则,用迭代器实现斐波那契数列)

>>> string="Python"
>>> it = iter(string)
>>> next(it)
'P'
>>> next(it)
'y'
>>> next(it)
't'
>>> next(it)
'h'
>>> next(it)
'o'
>>> next(it)
'n'
>>> next(it)
Traceback (most recent call last):File "<pyshell#294>", line 1, in <module>next(it)
StopIteration

解析for语句工作原理

>>> string="Python"
>>> it =iter(string)
>>> while True:try:each=next(it)except StopIteration:breakprint(each)P
y
t
h
o
n

详见 Python 魔法方法详解

小甲鱼Python学习笔记之魔法方法相关推荐

  1. Python学习笔记Task11.魔法方法

    Python学习笔记Task11.魔法方法 魔法方法格式__init__ 1.基本 init(self[,-]) new(cls[,-]) del(self) str(self) repr(self) ...

  2. B站小甲鱼python学习笔记

    000 愉快的开始 视频地址: https://www.bilibili.com/video/BV1xs411Q799?p=1 python跨平台 应用范围: 操作系统.WEB.3D动画.企业应用.云 ...

  3. 小甲鱼Python学习笔记之函数(三)

    递归 定义 递归的两个特点(重中之重!!!) 递归的应用 能够用递归实现的功能都能够用循环迭代来实现 用递归实现斐波那契数列 用其他方式实现斐波那契数列 用递归实现汉诺塔 定义 函数之间是可以相互调用 ...

  4. 小甲鱼Python学习笔记之函数(四)

    函数注释 类型注释 Python自省 函数注释 通过help来查看函数的注释 自定义函数时编写函数注释 >>> def exchang(dollar,rate=6.32):" ...

  5. 小甲鱼python入门笔记(一)(全)

    目录 一.变量和字符串 1.python转义字符 2.交换x,y变量的值 3.原始字符串,忽略转义字符 4.长字符串两种方式 5.字符串加法和乘法 二.是时候讲讲代码了 1.python比较运算符 三 ...

  6. 【Python】小甲鱼Python学习总结——代码版

    看了50P左右的小甲鱼B站的Python视频,按需学习,因此有些P可能会有遗漏.以下是自己跟着视频敲的代码,一些知识点和自己的理解写在了注释里.注释要遵守PEP8的规范. 同时Python的另一个教程 ...

  7. 小甲鱼Python3学习笔记之第三讲(仅记录学习)

    第三讲:小插曲变量和字符串 一.知识点: 1.变量的概念:在别的编程语言中,变量是把一个值赋给一个名字时,值会存储到内存中,这个名字就叫变量. 在python中,变量更像把名字贴到值的上边,所以说py ...

  8. 小甲鱼Python学习知识点记录(003讲)

    一.插曲之变量 1.变量名就像我们现实社会的名字,把一个值赋值给一个名字时,它会存储在内存中,称之为变量(variable),在大多数语言中,都把这种行为称为"给变量赋值"或&qu ...

  9. 小甲鱼Python3学习笔记之第二十八讲(仅记录学习)

    第二十八讲:文件:因为懂你,所以永恒 一.知识点: 0.file对象利用open函数来创建. 1.file文件的打开模式:f = open('文件地址','r/w/x/a等') 'r':只读模式,以只 ...

最新文章

  1. 【c语言】蓝桥杯基础练习 数列特征
  2. php 直接定义json,PHP json_dncode()函数定义与使用方法
  3. 今天看到这篇新闻之后,决定休息一下咯
  4. 给定一组查找关键字(19,14,23,1,65,20,84,27,55,11,10,79) 哈希函数为:H(key)=key % 13, 哈希表长为m=15,设每个记录的查找概率相等。【MOOC】
  5. 音视频技术开发周刊 | 217
  6. 疫情蔓延让这项CV技术突然火了,盘点开源代码
  7. 工具使用-----Jmeter的基础用法
  8. Spark写入MySQL报错乱码+报错
  9. 保护站点子目录的文件
  10. kali工具中文手册_黑客系统指南-在安卓手机上安装kali分步教程
  11. 一、MySQL数据库优化策略
  12. VUE使用echarts实现中国地图航线动态展示
  13. [MATLAB 在科学计算中的应用] 使用MATLAB 进行非线性拟合
  14. docker--镜像
  15. 电脑视频怎么录制?好用的电脑录屏方法
  16. repo sync error.GitError: manifests rev-list : fatal: revision walk setup failed
  17. 隐马尔可夫模型HMM
  18. 手机编程python可以实现吗?有哪些软件值得推荐?
  19. MySQL~Java的数据库编程:JDBC(JDBC的环境配置以及使用)
  20. matlab命令行清,如果需要清除MATLAB命令行窗口的以往输出结果,可以通过在命令行窗口中输入clear命令实现。 答案:错...

热门文章

  1. weixuan -小老弟做鸭(函数)
  2. 信号调理方式(放大、滤波、隔离、调制解调等)
  3. 解决File.Delete()删除不掉文件
  4. mysql的基础命令之更改密码
  5. mysql忘记初始密码与更改密码
  6. 【flask】Blueprint蓝图
  7. oracle 11g open_cursors 修改,修改open_cursors和session_cached_cursors的参数值
  8. 计算机课 - 计算机科学导论
  9. 计算几何入门 1.4:凸包的构造——Jarvis March算法
  10. 三种数据库的 SQL 注入详解