小甲鱼Python学习笔记之魔法方法
- 什么是魔法方法
- 构造和析构
- __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学习笔记之魔法方法相关推荐
- Python学习笔记Task11.魔法方法
Python学习笔记Task11.魔法方法 魔法方法格式__init__ 1.基本 init(self[,-]) new(cls[,-]) del(self) str(self) repr(self) ...
- B站小甲鱼python学习笔记
000 愉快的开始 视频地址: https://www.bilibili.com/video/BV1xs411Q799?p=1 python跨平台 应用范围: 操作系统.WEB.3D动画.企业应用.云 ...
- 小甲鱼Python学习笔记之函数(三)
递归 定义 递归的两个特点(重中之重!!!) 递归的应用 能够用递归实现的功能都能够用循环迭代来实现 用递归实现斐波那契数列 用其他方式实现斐波那契数列 用递归实现汉诺塔 定义 函数之间是可以相互调用 ...
- 小甲鱼Python学习笔记之函数(四)
函数注释 类型注释 Python自省 函数注释 通过help来查看函数的注释 自定义函数时编写函数注释 >>> def exchang(dollar,rate=6.32):" ...
- 小甲鱼python入门笔记(一)(全)
目录 一.变量和字符串 1.python转义字符 2.交换x,y变量的值 3.原始字符串,忽略转义字符 4.长字符串两种方式 5.字符串加法和乘法 二.是时候讲讲代码了 1.python比较运算符 三 ...
- 【Python】小甲鱼Python学习总结——代码版
看了50P左右的小甲鱼B站的Python视频,按需学习,因此有些P可能会有遗漏.以下是自己跟着视频敲的代码,一些知识点和自己的理解写在了注释里.注释要遵守PEP8的规范. 同时Python的另一个教程 ...
- 小甲鱼Python3学习笔记之第三讲(仅记录学习)
第三讲:小插曲变量和字符串 一.知识点: 1.变量的概念:在别的编程语言中,变量是把一个值赋给一个名字时,值会存储到内存中,这个名字就叫变量. 在python中,变量更像把名字贴到值的上边,所以说py ...
- 小甲鱼Python学习知识点记录(003讲)
一.插曲之变量 1.变量名就像我们现实社会的名字,把一个值赋值给一个名字时,它会存储在内存中,称之为变量(variable),在大多数语言中,都把这种行为称为"给变量赋值"或&qu ...
- 小甲鱼Python3学习笔记之第二十八讲(仅记录学习)
第二十八讲:文件:因为懂你,所以永恒 一.知识点: 0.file对象利用open函数来创建. 1.file文件的打开模式:f = open('文件地址','r/w/x/a等') 'r':只读模式,以只 ...
最新文章
- 【c语言】蓝桥杯基础练习 数列特征
- php 直接定义json,PHP json_dncode()函数定义与使用方法
- 今天看到这篇新闻之后,决定休息一下咯
- 给定一组查找关键字(19,14,23,1,65,20,84,27,55,11,10,79) 哈希函数为:H(key)=key % 13, 哈希表长为m=15,设每个记录的查找概率相等。【MOOC】
- 音视频技术开发周刊 | 217
- 疫情蔓延让这项CV技术突然火了,盘点开源代码
- 工具使用-----Jmeter的基础用法
- Spark写入MySQL报错乱码+报错
- 保护站点子目录的文件
- kali工具中文手册_黑客系统指南-在安卓手机上安装kali分步教程
- 一、MySQL数据库优化策略
- VUE使用echarts实现中国地图航线动态展示
- [MATLAB 在科学计算中的应用] 使用MATLAB 进行非线性拟合
- docker--镜像
- 电脑视频怎么录制?好用的电脑录屏方法
- repo sync error.GitError: manifests rev-list : fatal: revision walk setup failed
- 隐马尔可夫模型HMM
- 手机编程python可以实现吗?有哪些软件值得推荐?
- MySQL~Java的数据库编程:JDBC(JDBC的环境配置以及使用)
- matlab命令行清,如果需要清除MATLAB命令行窗口的以往输出结果,可以通过在命令行窗口中输入clear命令实现。 答案:错...
热门文章
- weixuan -小老弟做鸭(函数)
- 信号调理方式(放大、滤波、隔离、调制解调等)
- 解决File.Delete()删除不掉文件
- mysql的基础命令之更改密码
- mysql忘记初始密码与更改密码
- 【flask】Blueprint蓝图
- oracle 11g open_cursors 修改,修改open_cursors和session_cached_cursors的参数值
- 计算机课 - 计算机科学导论
- 计算几何入门 1.4:凸包的构造——Jarvis March算法
- 三种数据库的 SQL 注入详解