28.8. 08.Python类的特殊方法

28.8.1. 1. 对象转字符串与__repr__方法

* __repr_()是一个非常特殊 的方法,它是一个“ 自我描述” 的方法,
该方法通常用 于实现这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,
用来告诉外界该对象具有的状态信息。
class Apple:
    # 实现构造器
    def __init__(self, color, weight):
        self.color = color
        self.weight = weight

    # 重写tostring方法用于实现Apple对象的“自我描述”
    def __repr__(self):
        return "Apple[color=" + self.color + ",weight =" + str(self.weight) + "]"


a = Apple("红色", 5.68)
# 打印Apple对象
print(a)  # Apple[color=红色,weight =5.68]

28.8.2. 2. 对象的析构方法__del__

* 与__init__()方法对应的是__del__()方法: __init__()方法用于初始化 Python 对象,
而__del__()则用于销毁 Python 对象 在任何 Python 对象将要被系统回收之时,
系统都会自动调用该对象的__del__()方法。
class Item:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    # 定义析构函数
    def __del__(self):
        print('del删除对象')


# 创建一个Item对象,将之赋给im变量
im = Item('鼠标', 29.8)
# x = im
##将im赋值给变量x,此时程序中有两个变量引用Item对象,当del im 代码删除im对象后,由于还有变量引用,
# 程序并不会回收Item对象。只有等到程序执行将要结束时(系统必须收回所有对象),系统才会回收Item对象。
# 打印im所引用的Item对象
del im
print('--------------')

28.8.3. 3.__dir__方法

* __dir__方法用于列出该对象内部的所有属性(包括方法)名,
该方法将会返回包含所有属性(方法)名的序列
#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2020/3/26 11:08
# filename: sample03.py
class Item:
    def __init__(self, name, price):
        self.name = name
        self.price = price

def info():
    pass


# 创建一个Item对象,将之赋给im变量
im = Item('鼠标', 29.8)
print(im.__dir__())  # 返回所有属性(包括方法)组成列表
print(dir(im))  # 返回所有属性(包括方法)排序之后的列表

28.8.4. 4.__dict__属性

* __dict__属性用于查看对象内部存储的所有属性名和属性值组成的字典,
通常程序直接使用该属性即可 。
程序使用__dict__属性既可查看对象的所有内部状态,
也可通过字典语法来访问或修改指定属性的值。
class Item:
    def __init__ (self, name, price):
        self.name = name
        self.price = price
im = Item('鼠标', 28.9)
print(im.__dict__)  # ①
# 通过__dict__访问name属性
print(im.__dict__['name'])
# 通过__dict__访问price属性
print(im.__dict__['price'])
im.__dict__['name'] = '键盘'
im.__dict__['price'] = 32.8
print(im.name) # 键盘
print(im.price) # 32.8

28.8.5. 5.使用__getattr__等方法监听属性访问

当程序操作(包括访问、设置、删除)对象的属性时, Python 系统同样会执行该对象特定的方法 。 这些方法共涉及如下几个 。

__getattribute__(self,name): 当程序访 问对象的name属性时被自动调用。
__getattr__(self,name): 当程序访问对象的name属性且该属性不存在时被自动调用。
__setattr__(self,name,value):当程序对对象的name属性赋值时被自动调用。
__delattr__(self,name):当程序删除对象的name属性时被自动调用。
# 1.
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)

# 2.
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 or 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  # 引发异常

28.8.6. 6.反射相关的动态操作属性和方法

动态操作属性

在动态检查对象是否包含某些属性(包括方法〉相关的函数有如下几个。

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是方法,故下面代码会提示:name 'info' is not defined
#print(getattr(c, info, '默认值'))
# 为指定属性设置属性值
setattr(c, 'detail', '天气不错')
setattr(c, 'view_times', 32)
# 输出重新设置后的属性值
print(c.detail)
print(c.view_times)

# 设置不存在的属性,即为对象添加属性
setattr(c, 'test', '新增的测试属性')
print(c.test) # 新增的测试属性

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

# 将c的info设置为字符串'fkit'
setattr(c, 'info', 'fkit')
c.info()

28.8.7. 7.__call__属性

* 程序可通过判断该属性(或方法)是否包含__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')
# 判断u.name是否包含__call__方法,即判断是否可调用
print(hasattr(u.name, '__call__')) # False
# 判断u.passwd是否包含__call__方法,即判断是否可调用
print(hasattr(u.passwd, '__call__')) # False
# 判断u.validLogin是否包含__call__方法,即判断是否可调用
print(hasattr(u.validLogin, '__call__')) # True

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

def foo ():
    print('--foo函数--')
# 下面示范了2种方式调用foo()函数
foo()
foo.__call__()

28.8.8. 8.与序列相关的特殊方法

* 序列最重要的特征就是可包含多个元素,因此和序列有关的特殊方法有如下几个。

__len__(self) :该方法的返回值决定序列中元素的个数。
__getitem__ (self, key):该方法获取指定索引对应的元素。该方法的 key 应该是整数值或 slice对象,否则该方法会引发 KeyError 异常。
__contains__(self, item): 该方法判断序列是否包含指定元素。 __setitem__ (self, key, valu e) : 该方法设置指定索引对应的元素 。 该方法的 key 应该是整数值或 slice 对象,否则该方法会 引 发 KeyError 异常。
__delitem__( self, key) : 该方法删除指定索引对应的元素。
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

28.8.9. 9.与迭代器相关的特殊方法

for循环遍历列表、元祖和字典。这些对象都是可迭代的,因此它们都属于迭代器。

如果开发者需要实现迭代器,只要实现如下两个方法即可。

* __iter__(self): 该方法返回一个迭代器(iterator),迭代器必须包括一个__next()方法,该方法返回迭代器的下一个元素。

* __reversed__(self):该方法主要为内建的reversed()反转函数提供支持,当程序调用reversed()函数对指定迭代器执行反转时,实际上是由该方法实现的。
# 定义一个代表斐波那契数列的迭代器
class Fibs:
    def __init__(self, len):
        self.first = 0
        self.sec = 1
        self.__len = len
    # 定义迭代器所需的__next__方法
    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 = Fibs(10)
# 获取迭代器的下一个元素
print(next(fibs))
# 使用for循环遍历迭代器
for el in fibs:
    print(el, end=' ')
# 将列表转换为迭代器
my_iter = iter([2, 'fkit', 4])
# 依次获取迭代器的下一个元素
print(my_iter.__next__()) # 2
print(my_iter.__next__()) # fkit

28.8.10. 10.扩展列表、元组和字典

如果程序明确需要一个特殊的列表、元祖或字典类,我们有两种选择:

* 自己实现序列、迭代器等各种方法,自己来实现这个特殊的类。
* 扩展系统已有的列表、元祖或字典。

很明显,第一种方式有点繁琐,因此这意味着开发者要把所有方法都自己实现一遍,第二种方式就简单多了, 只需要继承系统已有的列表、元祖或字典类,然后重写或新增方法即可。

根据value,获取所有key所组成的列表信息

扩展字典类:

# 定义ValueDict类,继承dict类
class ValueDict(dict):
    # 定义构造函数
    def __init__(self, *args, **kwargs):
        # 调用父类的构造函数
        super().__init__(*args, **kwargs)
    # 新增getkeys方法
    def getkeys(self, val):
        result = []
        for key, value in self.items():
            if value == val: result.append(key)
        return result
my_dict = ValueDict(语文 = 92, 数学 = 89, 英语 = 92)
# 获取92对应的所有key
print(my_dict.getkeys(92)) # ['语文', '英语']
my_dict['编程'] = 92
print(my_dict.getkeys(92)) # ['语文', '英语', '编程']

28.8.11. 11.生成器函数和生成器对象

* 生成器与迭代器的区别在于 : 迭代器通常是先定义一个法代器类 , 然后通过创建实例来创建迭代器;而生成器则是先定义一个包含 yield 语句的函数,然后通过调用该函数来创建生成器。
def test(val, step):
    print("--------函数开始执行------")
    cur = 0
    # 遍历0~val
    for i in range(val):
        # cur添加i*step
        cur += i * step
        yield cur
#        print(cur, end=' ')
# 执行函数,返回生成器
t = test(10, 2)
print('=================')
# 获取生成器的第一个值
print(next(t)) # 0,生成器“冻结”在yield处
print(next(t)) # 2,生成器再次“冻结”在yield处

for ele in t:
    print(ele, end=' ')

# 再次创建生成器
t = test(10, 1)
# 将生成器转换成列表
print(list(t))
# 再次创建生成器
t = test(10, 3)
# 将生成器转换成列表
print(tuple(t))

28.8.12. 12.生成器方法和使用生成器

* 当生成器运行起来之后,开发者还可以为生成器提供值,通过这种方式让生成器与“外部程序”动态地交换数据。`
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))  # 0
print(next(sg))  # 1
print('--------------')
# 调用send()方法获取生成器的下一个值,参数9会被发送给生成器
print(sg.send(9))  # 81
# 再次调用next()函数获取生成器的下一个值
print(next(sg))  # 9

# 让生成器引发异常
#sg.throw(ValueError)

# 关闭生成器
sg.close()
print(next(sg)) # StopIteration

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

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)

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

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

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

* object.__neg__(self): 为单目求负(-)运算符提供支持。

* object.__pos__(self): 为单目求正(+)运算符提供支持。

* object.__invert__(self): 为单目取反(~)运算符提供支持。

下面程序为Rectangle类实现了一个__neg__()方法,该方法用于控制将矩形的宽、高交换。

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)

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

Python提供了str()、int()、float()、complex()等函数,可以将其他类型的对象转换成字符串、整数、浮点数和复数, 这些转换同样是由特殊方法在底层提供支持的。如下:

* object.__str__(self): 对应于调用内置的str()函数将该对象转换成字符串。
* object.__bytes__(self): 对应于调用内置的bytes()函数将该对象转换成字节内容。该方法应该返回bytes对象。
* object.__complex__(self): 对应于调用内置的complex()函数将该对象转换成复数。该方法应该返回complex对象。
* object.__int__(self): 对应于调用内置的int()函数将该对象转换成整数。该方法应该返回int对象。
* object.__float__(self): 对应于调用内置的float()函数将该对象转换成浮点数。该方法应该返回float对象。

下面以自定义的Rectangle为例,程序为该类提供了了一个__int__()方法,这样程序就可用int()函数将Rectangle对象转换成整数了。

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

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

Python还提供了一些常见的内建函数,这些内建的函数处理对象时,实际上也是由以下特殊方法来提供支持的。

* object.__format__(self,format_spec):对应于调用内置的format()函数将对象转换成格式化字符串。
* object.__hash__(self):对应于调用内置的hash()函数来获取该对象的hash码。
* object.__abs__(self):对应于调用内置的abs()函数返回绝对值。
* object.__round__(self,\[,ndigits]):对应于调用内置的round()函数执行四舍五入取整。
* object.__trunc__(self):对应于调用内置的trunc()函数执行截断取整。

注意: 内置的int()函数将对象转换成整数时,底层将由__trunc__(self)方法提供支持。

* object.__floor__(self):对应于调用内置的floor()函数执行向下取整。
* object.__ceil__(self):对应于调用内置的ceil()函数执行向上取整。

下面程序示范了为Rectangle类定义一个__round__()方法,程序就可以调用round()函数对Rectangle对象执行四舍五入取整了。

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)