疯狂python讲义学习日志07——Python类的特殊方法

引言

在python类中有些方法名、属性名的前后都添加了双下画线,这种方法、属性通常都属于python的特殊方法和特殊属性,开发者可以通过重写或者直接调用这些方法实现特殊功能。比如__ini__可以重写类中的__ini__方法实现自己的初始化逻辑。

1 常见的特殊方法

1.1 重写__repr__方法

当需要将任何对象与字符串进行连接时,都可调用__repr__方法将对象转换成字符串(用于对象的自我描述),然后将两个字符串连接在一起。
每个python对象都具有__repr__方法,python提供的该方法总是返回该对象实现类的“类名+object at+内存地址”值,如果用户需要自定义类能实现自我描述的功能,就必须重写__repr__方法。

class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price
    def __repr__(self):
        return  "Item[name=" +self.name+\
            ", pric =" + str(self.price) +']'
a = Item("abc",8.00)
print(a)

1.2 析构方法__del__

__del__方法用于销毁python对象,python对象要被系统回收时,都会调用该对象的__del__方法。只有当对象的引用计数为0时该对象才会被回收。

class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price
    def __repr__(self):
        return  "Item[name=" +self.name+\
            ", pric =" + str(self.price) +']'
    def __del__(self):
        print("del删除对象")
a = Item("abc",8.00)
x = a  #因为此处引用了所以程序结束才会执行析构
del a
print("----------------")

1.3__dir__方法

对象的__dir__方法用于列出该对象内部的所有属性(包括方法)名,该方法将会返回所有属性(方法)名的序列。当程序对某个对象执行dir(object)时,实际上就是将该对象的__dir__方法返回值进行排序,然后包装成列表。

class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price
    def __repr__(self):
        return  "Item[name=" +self.name+\
            ", pric =" + str(self.price) +']'
    def __del__(self):
        print("del删除对象")
    def info(self):
        pass
a = Item("abc",8.00)
print(a.__dir__()) #返回所有属性(包含方法)组成的列表
print(dir(a)) #返回所有属性(包含方法)排序后组成的列表

1.4 __dic__属性

__dic__属性用于查看对象内部存储的所有属性名和属性值组成的字典,既可以使用__dic__属性查看对象所有的内部状态,也可以通过字典语法来访问或修改指定属性的值。

class Item:
    def __init__(self,name,price):
        self.name = name
        self.price = price
    def __repr__(self):
        return  "Item[name=" +self.name+\
            ", pric =" + str(self.price) +']'
    def __del__(self):
        print("del删除对象")
    def info(self):
        pass
a = Item("abc",8.00)
print(a.__dict__)
print(a.__dict__['name'])
print(a.__dict__['price'])
a.__dict__['name'] = "键盘"
a.__dict__['price'] = 56.9
print(a.name)
print(a.price)

1.5__getattr__、__setattr__等

当程序操作(包括访问、设置、删除)对象的属性时,python系统会执行特定的方法:
1__getattribute__(self,name):当程序访问对象的name属性时被自动调用
2__getattr__(self,name):当程序访问对象的name属性,且该对象不存在时被自动调用
3__setattr__(self,name,value):当程序对对象的name属性赋值时被自动调用
4__delattr__(self,name):当程序删除对象的name属性时被自动挡调用

class Rectangle:
    def __init__(self,width,height):
        self.width = width
        self.heigth = height
    def __setattr__(self,name,value):
        print('---设置%s的属性---' % name)
        if  name == 'size':
            self.width,self.heigth = value
        else:
            self.__dict__[name] = value
    def __getattr__(self, name):
        print('---读取%s的属性---' % name)
        if name == 'size':
            return self.width,self.heigth
        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)

如果程序需要在读取、设置属性之前进行某种拦截处理,也可以通过重写__setattr__()或__getattribute__()方法实现。

class User:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def __setattr__(self, key, value):
        if key == 'name':
            if  2<len(value) <=8:
                self.__dict__['name'] = value
            else:
                raise ValueError('name的长度必须在2-8之间')
        elif key == '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.age = 2

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

这个概念听着还是很高级的(c++没有类似的操作的),其实就是如果在程序运行过程中要动态判断是否包含某个属性(包括方法),甚至动态设置某个属性值,则可通过Python的反射支持来实现。学好这一小节,应用得当在有些时候可能会有很巧妙的应用。

2.1 动态操作属性

检查对象是否包含某些属性相关的函数如下:
1)hasattr(obj,name):检查obj对象是否包含名为name的属性或方法。
2) getattr(obj,name[,default]):获取obj对象中名name的属性的属性值。
3) 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(self):
        print("一条简单的评论,内容是%s" % self.detail)
c = Comment("Python还凑合",20)
print(hasattr(c,'detail'))
print(getattr(c,'detail'))
print(getattr(c,'info','默认值'))
setattr(c,'detail','天气不错')
print(c.detail)
def bar():
    print('hello world')
setattr(c,'info',bar)#setattr还可以对方法进行设置,使用setattr()方法重新设置对象的方法时,新设置的方法就是未绑定方法
c.info()

2.2 __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','xiaoming')
print(hasattr(u.name,'__call__'))
print(hasattr(u.ValidLogin,'__call__'))

实际上一个函数之所以被执行关键在于__call__()方法,甚至可以自定义类添加__call__方法,从而使得该类的实例也可变成可调用的。

class Role:
    def __init__(self,name):
        self.name = name
    def __call__(self):
        print('执行Role对象')
r = Role('管理员')
r()

3 与序列相关的特殊方法

3.1 序列相关方法

与序列有关的特殊方法有如下几个:
1__len__(self):返回序列中元素的个数
2__getitem__(self,key):该方法获取指定索引对应的元素。该方法的Key应该是整数值或slice对象(说明见下图),否则会引发keyError异常
对象说明ce
3__contains__(self,item):该方法判断序列是否包含指定元素
4__setitem__(self,key,value):该方法设置指定索引对应的元素。key也要是整数或slice对象
5__delitem__(self,key):删除指定索引的元素
如果程序要实现只能获取序列中的元素,不能修改,只要实现上面前3个方法就可以,如果要实现可变序列那么5个方法都要实现。
下图是python chr()函数的使用方法
在这里插入图片描述

def check_key(key):
    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, item):
        check_key(item)
        if item in self.__changed:
            return self.__changed[item]
        if item in self.__deleted:
            return  None
        three = item // (26*26)#//向下取整
        two = (item - three *26 * 26) // 26
        one = item % 26
        return  chr(65 + three) + chr(65+two) + chr(65+one)
    def __setitem__(self, key, value):
        check_key(key)
        self.__changed[key] = value
    def __delitem__(self, key):
        check_key(key)
        if key not in self.__deleted: self.__deleted.append(key)
        if key in self.__changed:del  self.__changed[key]
sq = StringSeq()
print(len(sq))
print(sq[26*26])
print(sq[1])

sq[1] = 'fkit'
print(sq[1])

del sq[1]
print(sq[1])

sq[1] = 'crazit'
print(sq[1])

3.2 实现迭代器

1、如果开发者要实现迭代器,只要实现如下两个方法即可:
1)iter(self):该方法返回一个迭代器,迭代器必须包含一个__next__()方法,该方法返回迭代器的下一个元素。
2)__reversed(self):该方法主要为内建的reversed()反转函数提供支持。

class Fibs:
    def __init__(self,len):
        self.first =0
        self.sec = 1
        self.__len = len
    def __next__(self):
        if self.__len == 0:
            raise  StopIteration
        self.first ,self.sec = self.sec , self.sec + self.first
        self.__len-=1
        return  self.first
    def __iter__(self):
        return self
fibs = Fibs(10)
print(next(fibs))
for el in fibs:
    print(el,end=' ')

2、程序可以使用内置的Iter()函数将列表、元组等转换为迭代器。

my_iter = iter([2,3,4])
print(my_iter.__next__())
print(my_iter.__next__())

3.3 扩展列表、元组和字典

如果程序中有需要一个特殊列表、元组或字典类的需求,可以扩展快速实现需求。详细可以参考下面的示例代码:

class ValueDict(dict):
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
    def getkeys(self,val):
        result = []
        for key, value in self.items():
            if value ==val : result.append(key)
        return  result
my_dict = ValueDict(语文=90,数学=89,英语=92)

print(my_dict.getkeys(92))
my_dict['c++']=92
print(my_dict.getkeys(92))

4 生成器

生成器和迭代器功能非常相似,它也会提供__next__()方法。区别在于:迭代器通常是先定义一个迭代器类,然后通过实例创建迭代器类;而生成器先定义一个包含yield语句的函数,然后通过调用该函数创建生成器。

4.1 创建生成器

1、创建生成器需要两个步骤:
1)定义一个包含yield语句的函数。
2)调用第一步创建的函数的到生成器。

def test(val,step):
    print("-----------函数开始执行------------------")
    cur = 0
    for i in range(val):
        cur += i*step
        yield cur

2、yield cur语句的作用有两点:
1)每次返回一个值,类似return
2) 冻结执行,程序每次执行到yield就会被暂停。
3、调用包含yield语句的函数并不会立即执行,它只是返回一个生成器。只有当程序通过next()函数调用生成器遍历生成树时,函数才会真正运行。

def test(val,step):
    print("-----------函数开始执行------------------")
    cur = 0
    for i in range(val):
        cur += i*step
        yield cur
t = test(10,2)
print("==========================")
print(next(t))
print(next(t))

程序可以使用list()函数将生成器能生成的所有值转换为列表,或者使用tuple()函数将生成器生成的所有值转换为元组。

def test(val,step):
    print("-----------函数开始执行------------------")
    cur = 0
    for i in range(val):
        cur += i*step
        yield cur
t = test(10,2)
print(list(t))
t = test(10,3)
print(tuple(t))

3、程序可使用for循环来遍历生成器,相当于不断使用next()函数获取下一个值。示例代码如下:

def test(val,step):
    print("-----------函数开始执行------------------")
    cur = 0
    for i in range(val):
        cur += i*step
        yield cur
t = test(10,2)
print(next(t))
print(next(t))

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

4、总的来说使用生成器有以下几个优势:
1)程序按需获取数据:使用生成器产生数据时,不会一次性就将所有的数据生成出来,而是调用next()获取下一个数据时才会生成该数据。
2)当函数需要返回多个数据时,如果不使用生成器,程序就需要使用列表或元组来收集返回的多个值,这些列表元组会带来一定的内存开销。
3)使用生成器的代码更加简洁(姑妄言之)

4.2 生成器的方法

当生成器运行起来之后,开发者还可以为生成器提供值,通过这种方式让生成器与“外部程序”动态的交换数据。
1、python使用send发送数据,使用yiedl接收数据。
只有当生成器冻结之后,外部程序才可以使用send()方法向生成器发送数据。获取生成器第一次生成的值,应该使用next()函数;如果程序非要使用send()方法获取生成器第一次生成的值,只能为该方法传入None参数。

def square_gen(val):
    i = 0
    out_val = None
    while True:
        out_val = (yield  out_val ** 2) if out_val is not   None else (yield  i**2)
        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(sg.send(9))
print(next(sg))

5 运算符重载的特殊方法

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

开发人员可以为自定义类型提供如下方法:
基本算术运算符
常用运算符
下面给出使用使用这些方法的示例代码

#加法使用示例代码:
class Rectangle:
    def __init__(self,width,height):
        self.width = width
        self.height = height
    def SetSize(self,size):
        self.width,self.height = size
    def getSize(self):
        return  self.width,self.height
    size = property(getSize,SetSize)
    def __add__(self, other):
        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)
r = r1 + r2
print(r)

数值运算方法右操作数方法
在这里插入图片描述

5.2 与比较有关的特殊方法

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值