Python 第八章 魔法方法

1.常见特殊方法

1.1 重写__repr__方法
class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price
# 创建一个item对象,赋值给im变量
im = Item('鼠标',29.8)
print(im)
# <__main__.Item object at 0x0000023F3C30A5B0>

print函数只能在控制台打印字符串,而Item实例是内存中的一个对象,实际上该方法输出的是Item对象的_repr_()方法的返回值。也就是说下面两行代码一样:

print(im)
print(im.__repr__())

程序需要将任何对象与字符串进行连接时,都可先调用该方法将对象转换成字符串,然后将两个字符串拼接在一起。

__repr__是一个自我描述的方法,通常用于实现:当程序员打印该对象时,系统会输出该对象的自我描述信息,用来告诉外界该对象的状态信息。

该方法总是返回该对象实现类的“类名+object at +内存地址”值,这个返回值并不能真正实现自我描述的功能,因此用户需要自定义类能实现自我描述功能,重写_repr_()方法:

class Apple:
    def __init__(self,color,weight):
        self.color = color
        self.weight = weight
    def __repr__(self):
        return "Apple[color=" + self.color +\
            ",weight=" + str(self.weight) + "]"
a = Apple("红色",5.68)
print(a)
Apple[color=红色,weight=5.68]

可以看出,通过重写Apple类的__repr__方法打出自我描述信息。

1.2析构方法:_del_

与__init__()对应的是____del__()方法,前者用于初始化Python对象,而__del__用于销毁python对象。任何python对象将被系统回收时,系统会自动调用del方法。

不要以为对一个变量执行del操作,该变量所引用的对象就会被回收,只有当对象的引用计数变成0才会被回收。因此,如果一个对象有多个变量引用它,那么del其中一个变量是不会回收该对象的。

class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price
    def __del__(self):
        print('删除对象')
im = Item('鼠标',22.8)
x = im
del im
print('---------')
#---------
删除对象

im 和x两个变量指向Item对象,删除了im对象还有x变量引用Item对象,所以不会回收Item对象。

如果将8行注释,删除im后无变量引用Item对象,会立即回收该对象,无需等到程序结束。

1.3__dir__方法

用于列出该对象内部的所有属性名,返回序列。

当程序对某个对象执行dir(object)函数时,实际上就是将该对象的__dir__方法返回值进行排序,然后包装成列表。

class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price
    @staticmethod
    def info():
        pass
im = Item('鼠标',23.3)
print(im.__dir__()) # 返回所有属性包括方法组成的列表
print(dir(im))    # 返回所有属性包括方法排序之后组成的列表
1.4__dict__属性

用于查看对象内部存储的所有属性名和属性值组成的字典。

class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price
im = Item('鼠标',22.3)
print(im.__dict__)
print(im.__dict__['name'])
print(im.__dict__['price'])
im.__dict__['name'] ='键盘'
im.__dict__['price'] ='44.5'
print(im.name)
print(im.price)
#{'name': '鼠标', 'price': 22.3}
#鼠标
#22.3
#键盘
#44.5
1.5_getattr_、__setattr__等

当程序操作(包括访问、设置、删除)对象的属性时,python系统同样会执行该对象特定的方法。有以下方法:

  • _getattribute_(self,name):当程序访问对象的name属性时被自动调用。
  • _getattr_(self,name):当程序访问对象的name属性且该属性不存在时被自动调用。
  • _setattr_(self,name,value):当程序对对象的name属性赋值时被自动调用。
  • _delattr_(self,name):当程序删除对象的name属性时被自动调用。

通过重写上面的方法,可以为python类合成属性——当属性不存在时,程序会委托给上面的后三个方法实现:

class Rectangle:
    def __init__(self,width,height):
        self.width = width
        self.height = height
    def __setattr__(self, name, value):
        print('----设置%s属性----' % name)
        if name == 'size':
            self.width,self.height = value
        else:
            self.__dict__[name] = value
    def __getattr__(self, name):
        print('----读取%s属性----' % name)
        if name == 'size':
            return self.width,self.height
        else:
            raise AttributeError
    def __delattr__(self, name):
        print('----删除%s属性----' % name)
        if name == 'size':
            self.__dict__['width'] = 0
            self.__dict__['height'] = 0

rect = Rectangle(3,4)
print(rect.size)
rect.size = 6,8
print(rect.width)
del rect.size
print(rect.size)
# 
----设置width属性----
----设置height属性----
----读取size属性----
(3, 4)
----设置size属性----
----设置width属性----
----设置height属性----
6
----删除size属性----
----读取size属性----
(0, 0)

上面程序实现了_setattr_()和_getattr()方法,并在实现这两个方法时对size属性进行了判断,如果程序正在获取size属性,_getattr()方法将返回self.width和self.height组成的元组,如果获取其他属性直接报错,如果程序正在设置size属性,则转换为对self.width和self.height属性的赋值,如果是对其他属性赋值,则通过对象的__dict__属性赋值。

  • 对于_getattr_()方法:只处理程序访问指定属性且该属性不存在的情形。比如程序访问width和height属性,Rectangle有就不会触发这个方法。所以重写该方法只需处理我们需要合成的属性(比如size),加入程序试图访问其他不存在的属性,当然直接引发异常即可。
  • 对于_setattr_()方法:只要程序试图对指定属性赋值总会触发该方法,所以重写该方法既要处理对size属性赋值的情形,也要处理对width、height属性赋值的情形。尤其是处理对width、height属性赋值时,千万不要在__setattr__方法中再对他们赋值。因为会再次出发该方法进入死循环。

如果程序需要在读取、设置属性之前进行某种拦截处理(比如检查数据是否合法之类),也可通过重写__setattr__或__getattribute__方法来实现。

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 重写__setattr__()方法对设置的属性值进行检查
    def __setattr__(self, name, value):
        # 如果正在设置name属性
        if name == 'name':
            if 2 < len(value) <= 8:
                self.__dict__['name'] = value
            else:
                raise ValueError('name的长度必须在2~8之间')

        elif name == 'age':
            if 10 < value < 60:
                self.__dict__['age'] = value
            else:
                raise ValueError('age值必须在10~60之间')


u = User('fkit', 24)
print(u.name)
print(u.age)
# u.name = 'fk' # 引发异常
u.age = 2  # 引发异常

2.与反射相关的属性和方法

2.1动态操作属性
  • hasattr(obj,name):检查obj对象是否包含名为name的属性或方法。
  • getattr(object,name[,default]):获取object对象中名为name的属性的属性值。
  • setattr(obj,name,value,/):将obj对象的name属性设为value。
class Comment:
    def __init__(self,detail,view_times):
        self.detail = detail
        self.view_times = view_times
    def info():
        print("一条简单的评论,内容是%s" % self.detail)
c = Comment('python讲义不错',20)
# 判断是否包含指定的属性或方法
print(hasattr(c,'detail'))  # True
print(hasattr(c,'view_times'))  # True
print(hasattr(c,'info'))  # True
# 获取指定属性的属性值
print(getattr(c,'detail')) # python讲义不错
print(getattr(c,'view_times'))  # 20
# 获取info属性的值,由于info是方法,因此输出绑定方法(bound method):Comment.info
print(getattr(c,'info','默认值'))#<bound method Comment.info of <__main__.Comment object at 0x000002B1038BA5B0>>

# 为指定属性设置属性值
setattr(c,'detail','天气不错')
setattr(c,'view_times',32)
print(c.detail) # 天气不错
print(c.view_times)  # 32
# 设置不存在的属性,即为对象添加属性
setattr(c,'test','新增的属性')
print(c.test) # 新增的属性

实际上,setattr函数还可以对方法进行设置,在使用时新设置的方法是未绑定方法。

def bar():
    print('一个bar方法')
# 将c的info方法设为bar函数
setattr(c,'info',bar)
c.info() # 一个bar方法

定义了一个bar函数,在该函数中不能定义self参数,否则需要显示的为参数传入参数中,系统不会自动为该参数绑定参数值。接着调用info函数实际是调用bar方法;还可以将info方法变成一个属性,而不是方法。

2.2__call__属性

hasattr函数可以判断指定属性或方法是否存在,要进一步判断是属性还是方法,要判断它是否可调用。程序可以通过判断该属性或方法是否包含__call__属性来确定它是否可被调用。

class User:
    def __init__(self,name,passwd):
        self.name = name
        self.passwd = passwd
    def ValidLogin(self):
        print('验证%s的登陆' % self.name)
u = User('crazyit','leegang')
print(hasattr(u.name,'__call__'))  # False
print(hasattr(u.passwd,'__call__'))  # False
print(hasattr(u.ValidLogin,'__call__'))  # True

事实上,一个函数(甚至类)之所以能执行,关键就在于__call__()方法。实际上x(arg1,arg2....)只是x._call_(arg1,arg2....)的快捷写法。

因此可以为自定义类添加该方法,从而使该类的实例也变成可调用的:

class Role:
    def __init__(self,name):
        self.name = name
    def __call__(self, *args, **kwargs):
        print('执行Role对象')
r = Role('管理员')
# 直接调用Role对象,就是调用__call__方法
r()
执行Role对象

最后一行用调用函数的语法来调用对象,看上去错误,但该Role类提供了__call__方法,因此调用对象的本质就是执行该对象的call方法。

3.与序列相关的特殊方法

3.1序列相关方法
  • _len_(self):该方法的返回值决定序列中元素的个数。
  • _getitem_(self,key):获取指定索引对应的元素。key应该是整数或slice对象,否则报错。
  • _contains_(self,item):判断序列是否包含指定元素。
  • _setitem_(self,key,value):设置指定索引对应的元素。key应该是整数或slice对象,否则报错。
  • _delitem_(self,key):删除指定索引对应的元素。

下面程序会实现一个字符串序列,在该字符串序列默认每个字符串的长度都是3,该序列的元素按AAA、AAB、AAC。。这种格式排列。

def check_key (key):
    '''
    该函数将会负责检查序列的索引,该索引必须是整数值,否则引发TypeError
    且程序要求索引必须为非负整数,否则引发IndexError
    '''
    if not isinstance(key, int): raise TypeError('索引值必须是整数')
    if key < 0: raise IndexError('索引值必须是非负整数')
    if key >= 26 ** 3: raise IndexError('索引值不能超过%d' % 26 ** 3)   
class StringSeq:
    def __init__(self):
        # 用于存储被修改的数据
        self.__changed = {}
        # 用于存储已删除元素的索引
        self.__deleted = []
    def __len__(self):
        return 26 ** 3
    def __getitem__(self, key):
        '''
        根据索引获取序列中元素
        '''
        check_key(key)
        # 如果在self.__changed中找到已经修改后的数据
        if key in self.__changed :
            return self.__changed[key]
        # 如果key在self.__deleted中,说明该元素已被删除
        if key in self.__deleted :
            return None
        # 否则根据计算规则返回序列元素
        three = key // (26 * 26)
        two = ( key - three * 26 * 26) // 26
        one = key % 26
        return chr(65 + three) + chr(65 + two) + chr(65 + one) 
    def __setitem__(self, key, value):
        '''
        根据索引修改序列中元素
        '''
        check_key(key)
        # 将修改的元素以key-value对的形式保存在__changed中
        self.__changed[key] = value
    def __delitem__(self, key):
        '''
        根据索引删除序列中元素
        '''
        check_key(key)
        # 如果__deleted列表中没有包含被删除key,添加被删除的key
        if key not in self.__deleted : self.__deleted.append(key)
        # 如果__changed中包含被删除key,删除它
        if key in self.__changed : del self.__changed[key]
# 创建序列
sq = StringSeq()
# 获取序列的长度,实际上就是返回__len__()方法的返回值
print(len(sq))
print(sq[26*26])
# 打印没修改之后的sq[1]
print(sq[1]) # 'AAB'
# 修改sq[1]元素
sq[1] = 'fkit'
# 打印修改之后的sq[1]
print(sq[1]) # 'fkit'
# 删除sq[1]
del sq[1]
print(sq[1]) # None
# 再次对sq[1]赋值
sq[1] = 'crazyit'
print(sq[1]) # crazyit

3.2实现迭代器
  • __iter__(self):该方法返回一个迭代器(iterator),迭代器必须包含一个__next__()方法,该方法返回迭代器的下一个元素。
  • _reversed_(self):该方法主要为内建的reversed()反转函数提供支持,调用reversed函数对指定迭代器执行反转时,实际上由该方法实现。

下面程序将会定义一个代表斐波那契数列(数列的元素等于前两个元素之和)的迭代器。

class Fibs:
    def __init__(self,len):
        self.first = 0
        self.sec = 1
        self.__len =len

    def __next__(self):
        # __len__属性为0就结束迭代。
        if self.__len == 0:
            raise StopIteration
        # 完成数列计算
        self.first,self.sec = self.sec,self.first + self.sec
        # 数列长度减1
        self.__len -=1
        return self.first
    # 定义iter方法,返回迭代器
    def __iter__(self):
        return self
fibs = Fibs(10)
print(next(fibs))
for el in fibs:
    print(el,end='')

Fibs类,该类实现了__iter__方法,该方法返回self,因此他要求该类必须提供__next__方法,该方法会返回数列的下一个值。程序使用__len属性控制数列的剩余长度,当为0时会停止遍历。

可以使用iter()函数将列表、元组等转换成迭代器:

my_iter = iter([2,'fkit',4])
print(my_iter.__next__()) # 2
print(my_iter.__next__()) #fkit
3.3生成器

生成器和迭代器功能相似,也会提供_next_()方法,这意味着程序同样可以调用next()函数获取生成器的下一个值,也可使用for循环来遍历生成器。

生成器和迭代器的区别在于:迭代器通常先定义一个迭代器类,然后通过创建实例来创建迭代器;生成器则是先定义一个包含yield语句的函数,然后通过调用该函数来创建生成器。

创建生成器:

  • 定义一个包含yield语句的函数。
  • 调用第一步创建的函数得到生成器。
def test(val,step):
    print('开始执行')
    cur = 0
    for i in range(val):
        cur += i*step
        yield cur

yield cur 语句的作用有

  • 每次返回一个值,有点类似于return
  • 冻结执行,程序每次执行到yield语句就会被暂停

在程序被冻结后,当程序再次调用next函数获取生成器的下一个值时,程序才会继续向下执行。

调用包含yield语句的函数并不会立即执行,只是返回一个生成器,只有程序通过next函数调用生成器或遍历生成器时才会真正执行。

程序可使用for循环来遍历生成器,相当于不断地使用next函数获取生成器的下一个值:

for ele in t:

​ print(ele,end = ' ')

生成器的方法:

当生成器运行起来后还可以为生成器提供值,让生成器与外部程序动态交换程序。

为了实现生成器与外部程序动态的交换数据,需要借助于生成器的send方法,该方法的功能与前面的next函数功能非常相似,都用于获取生成器的下一个值并将生成器冻结在yield语句处;但send方法可以接受一个参数,该参数值会被发送给生成器函数。在生成器函数内部,程序可通过yield表达式获取send方法所发送的值,这意味着此时程序应该使用一个变量来接收yield语句的值如果程序依然使用next函数来获取生成器产生的下一个值,那么yield语句返回None。

也就是:外部程序通过send方法发送数据;生成器函数使用yield语句接收数据。

只有等到生成器被冻结之后,外部程序才能使用send方法向生成器发送数据。获取生成器第一次所生成的值,应该使用next函数,如果程序非要使用send方法获取生成器第一次所生成的值,也不能向生成器发送数据,只能为该方法传入None参数。

下面程序是向生成器发送数据。会依次生成每个整数的平方值,但外部程序可以向生成器发送数据,当生成器接受到外部数据之后会生成外部数据的平方值。

def square_gen(val):
    i = 0
    out_val = None
    while True:
        # 使用yield语句生成值,使用out_val接收send()方法发送的参数值
        out_val = (yield out_val **2) if out_val is not None else (yield i**2)
        # 如果程序使用send方法获取下一个值,out_val获取send方法的参数值
        if out_val is not None:print('===%d' % out_val)
        i += 1

sg = square_gen(5)
# 第一次调用send方法获取值,只能传入None作为参数
print(sg.send(None))
print(next(sg))
print('----------')
# 调用send方法获取生成器的下一个值,参数9会被发送给生成器。
print(sg.send(9))
print(next(sg))
#
0
1
----------
===9
81
9

out_val变量用于接收生成器send方法所发送的值。

上面程序第一次使用生成器的send方法获取生成器的下一个值,因此只能为send方法传入None作为参数。程序执行到第六行,此时out_val为None,因此程序执行yield i **2(生成器返回0),程序被冻结,还未对out_val变量赋值,所以第一次是0。

接下来调用next(sg)获取生成器的下一个值,程序从冻结处(对out_val赋值)向下执行。由于此处调用next()函数获取生成器的下一个值,因此out_val被赋值为None,所以程序执行yield**2(生成器返回1),程序再次被冻结。

接下来程序调用sg.send(9)获取生成器的下一个值,程序从冻结处(对out_val赋值)向下执行。由于此处调用send(9)获取下一个生成器的值,因此out_val被赋值为9,所以程序执行yield i**2,生成器返回81,程序再次被冻结。

再次调用next(sg)获取生成器的下一个值,程序从冻结处向下执行,由于此时调用了next函数获取生成器的下一个值,因此out_val被赋值为None,执行yield i**2,此时i递增到3,生成器返回9,程序再次被冻结。

此外,生成器还提供了如下两个常用方法:

close():用于停止生成器。

throw():用于在生成器内部(yield语句内)引发一个异常。

4.与数值运算符相关的特殊方法

python允许为自定义类提供特殊方法,这样就可以让自定义类的对象也支持各种运算符的运算了。

一旦为自定义类提供了特殊方法,程序就可以直接用运算符来操作该类的实例。

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__add__方法,该对象可执行+运算
    def __add__(self, other):
        # 要求参与+运算的另一个运算数必须是Rectangle
        if not isinstance(other, Rectangle):
            raise TypeError('+运算要求目标是Rectangle')
        return Rectangle(self.width + other.width, self.height + other.height)
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r1 = Rectangle(4, 5)
r2 = Rectangle(3, 4)
# 对两个Rectangle执行加法运算
r = r1 + r2
print(r) # Rectangle(width=7, height=9)

5.与比较运算符相关的特殊方法

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__gt__方法,该对象可支持>和<比较
    def __gt__(self, other):
        # 要求参与>运算的另一个运算数必须是Rectangle
        if not isinstance(other, Rectangle):
            raise TypeError('>运算要求目标是Rectangle')
        return True if self.width * self.height > other.width * other.height else False
    # 定义__eq__方法,该对象可支持==和!=比较
    def __eq__(self, other):
        # 要求参与==运算的另一个运算数必须是Rectangle
        if not isinstance(other, Rectangle):
            raise TypeError('==运算要求目标是Rectangle')
        return True if self.width * self.height == other.width * other.height else False
    # 定义__ge__方法,该对象可支持>=和<=比较
    def __ge__(self, other):
        # 要求参与>=运算的另一个运算数必须是Rectangle
        if not isinstance(other, Rectangle):
            raise TypeError('>=运算要求目标是Rectangle')
        return True if self.width * self.height >= other.width * other.height else False 
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r1 = Rectangle(4, 5)
r2 = Rectangle(3, 4)
print(r1 > r2) # True
print(r1 >= r2) # True
print(r1 < r2) # False
print(r1 <= r2) # False
print(r1 == r2) # False
print(r1 != r2) # True
print('------------------')
r3 = Rectangle(2, 6)
print(r2 >= r3) # True
print(r2 > r3) # False
print(r2 <= r3) # True
print(r2 < r3) # False
print(r2 == r3) # True
print(r2 != r3) # False

6.与单目运算符相关的特殊方法

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__neg__方法,该对象可执行求负(-)运算
    def __neg__(self):
        self.width, self.height = self.height, self.width
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4, 5)
# 对Rectangle执行求负运算
-r
print(r) # Rectangle(width=5, height=4)

7.与类型转换相关的特殊方法

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__int__方法,程序可调用int()函数将该对象转成整数
    def __int__(self):
        return int(self.width * self.height)
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4, 5)
print(int(r)) # 20

8.与常见的内建函数相关的特殊方法

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定义setSize()函数
    def setSize (self , size):
        self.width, self.height = size
    # 定义getSize()函数
    def getSize (self):
        return self.width, self.height
    # 使用property定义属性
    size = property(getSize, setSize)
    # 定义__round__方法,程序可调用round()函数将该对象执行四舍五入取整
    def __round__(self, ndigits=0):
        self.width, self.height = round(self.width, ndigits), round(self.height, ndigits)
        return self
    def __repr__(self):
        return 'Rectangle(width=%g, height=%g)' % (self.width, self.height)
r = Rectangle(4.13, 5.56)
# 对Rectangle对象执行四舍五入取整
result = round(r, 1)
print(r) # Rectangle(width=4.1, height=5.6)
print(result) # Rectangle(width=4.1, height=5.6)
posted @ 2021-03-24 21:34  KKKyrie  阅读(149)  评论(0编辑  收藏  举报