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

什么是魔法方法

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

构造和析构

__init__和__new__方法是Python的构造器

__init__方法

python in的魔法方法_Python

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

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

# 求长方形(x,y)的周长和面积
>>> class Rectangle:
	def __init__(self,x,y):
		self.x=x   # self.x是类实例化之后的局部变量,等式右边的x是传入的参数
		self.y=y
	def getPeri(self):
		return (self.x+self.y)*2
	def 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__方法

python in的魔法方法_Test_02

它的第一个参数是class(这个类),__new __在__init__之前被调用,如果new后面有参数,会将参数原样传给__init__方法。极少重写new方法,但是当继承不可变类型且需要进行修改时,需要通过重写new方法实现

如将不可变的str类型实现全部大写的功能

python in的魔法方法_python in的魔法方法_03

__del__方法

python in的魔法方法_描述符_04

当对象被销毁时,就会自动调用该方法,_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

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

python in的魔法方法_描述符_05

举一个神奇的“栗子”:

>>> 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

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

python in的魔法方法_python_06

对于上述错误的改进:

python in的魔法方法_描述符_07

反运算方法

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

>>> 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

实现一个计时器的类

详细需求如下图:

python in的魔法方法_python in的魔法方法_08

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

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() 方法。 第一步:实现最基本最主要的三个方法,代码如下所示:

python in的魔法方法_python_09

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

python in的魔法方法_python in的魔法方法_10

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

python in的魔法方法_python_11

修改如下:

python in的魔法方法_python_12

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

python in的魔法方法_python in的魔法方法_13

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

import time as t

class MyTimer():
    def __init__(self):
        self.unit=['年','月','天','小时','分钟','秒']
        self.prompt='未开始计时'
        self.lasted=[]
        self.begin=0
        self.end=0

    
    def __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 = 0
        self.end=0
        print(self.prompt)

属性访问

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

python in的魔法方法_python_14

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

>>> 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的属性赋值,那么说明这是一个正方形,值就是正方形的边长,此时宽和高都应该等于边长。

python in的魔法方法_python_15

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

class Rectangle:
    def __init__(self,width=0,height=0):
        self.width=width
        self.height = height

    def __setattr__(self,name,value):
        if name=='square':
            self.width=vlaue
            self.height=value
        else:
            super().__setattr__(name,value)

    def getArea(self):
        return self.width*self.height

python in的魔法方法_python in的魔法方法_16

给一个特殊属性dict赋值

class Rectangle:
    def __init__(self,width=0,height=0):
        self.width=width
        self.height = height

    def __setattr__(self,name,value):
        if name=='square':
            self.width=value
            self.height=value
        else:
            self.__dict__[name]=value   # 法二
            # 法一:super().__setattr__(name,value)

            # self.name=value

    def getArea(self):
        return self.width*self.height

python in的魔法方法_python in的魔法方法_17

描述符(property属性)

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

python in的魔法方法_python in的魔法方法_18

>>> 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] +=1
        return 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:
		break
	print(each)

	
P
y
t
h
o
n

详见 Python 魔法方法详解