【python学习笔记】类特殊成员

本文详细介绍了Python中的特殊方法,包括__new__、__init__(构造和初始化对象)、__del__(销毁对象)、__repr__(自定义打印输出)、__call__(使对象可调用)、运算符重载、迭代器、生成器和装饰器的工作原理及使用示例。特别强调了这些方法在类和对象生命周期中的作用,以及如何通过它们扩展和定制对象的行为。
摘要由CSDN通过智能技术生成

__ new__()方法

__ new __ ()方法是一种负责创建类实例的静态方法,它无需使用@staticmethod装饰器修饰,且会优先于__init__()方法被调用。
一般情况下,重写__new __()方法将会使用合适的参数调用其父类的super(). __ new __(),并在返回之前修改实例。

class demoClass:
    instance_created = 0
    def __new__(cls,*args,**kwargs):
        print("__new__():",cls,args,kwargs)
        instance = super().__new__(cls)
        instance.number = cls.instance_created
        cls.instance_created += 1
        return instance
    def __init__(self,attribute):
        print("__init__():",self,attribute)
        self.attribute = attribute
d1 = demoClass("d1")
d2 = demoClass("d2")
print(d1.number,d1.instance_created)
print(d2.number,d2.instance_created)

上面的代码在创建类的实例的时候增加了已创建的实例的数量和当前实例的序号。
__ new__()通常会返回一个类的实例,但有时也会返回其他类的实例,如果发生了这种情况,则会跳过__init__()方法。

class nonZero(int):
    def __new__(cls,value):
        return super().__new__(cls,value) if value != 0 else None
    def __init__(self,skipped_value):
        #此例中会跳过此方法
        print("__init__()")
        super().__init__()
print(type(nonZero(-12)))
print(type(nonZero(0)))

使用__new__()方法,通常是在__init__()方法不够用的时候,比如上面这个例子,对python不可变的内置类型(如int、str、float等)进行了子类化,简单来说就是在我们传入0的时候,实例化nonZero类时返回的不再是一个nonZero对象,而是None。
我感觉重写__new__()方法在实际开发中不会很常用,如果仅仅想要一个类在实例化的时候可以返回多种类的实例时,完全可以使用工厂模式。

__ repr__()方法

在python中,直接打印类的实例对象,打印结果往往是内存地址。如果我们想要在打印对象的时候输出我们想要的信息,可以对__repr__()方法进行重写,个人感觉这个方法类似java中的toString()方法。

class people:
    address = "earth"
    def __init__(self,name):
        self.name = name
    def __repr__(self):
        return "people[name="+self.name+",address="+self.address+"]"
p = people("Andy")
print(p)

__ del__()方法

__ del__()方法是用来销毁对象的方法,在python中无论是手动销毁还是自动销毁,都会调用这个方法。

class demoClass:
    def __init__(self):
        print("调用 __init__() 方法构造对象")
    def __del__(self):
        print("调用__del__() 销毁对象,释放其空间")
demo = demoClass()
#添加一个引用demo对象的实例对象
d1 = demo
del demo
print("***********")
del d1
print("-----------")

从上面的代码可以看出,有时候并不是调用了del方法对象就马上被销毁,这个python的垃圾回收机制有关。
python采用了自动引用计数的方式实现垃圾回收机制,该方法的核心是:每个对象都会配备一个计数器,初始化对象时,计数器都为0。如果有变量引用了该对象,则计数值+1;反之,每当一个变量取消对该对象的引用,计数器会-1。如果一个对象的计数器的值为0,python就会自动销毁它。
在上面的程序中,实际构建对象的方式分为两步,第一步是调用__init__()方法构造出一个对象,我们将此对象成为D,此时计数器为0;并立即用demo变量作为对象的引用,此时计数器为1;在此基础上,又有一个变量d1引用了demo,此时计数器的值为2;这时调用了del demo语句,只会导致对象D的计数器-1,即为1;下面的del d1才会使计数器变为0,从而销毁对象。
另外,如果我们重写了子类的__del__()方法,则必须显式调用父类的__del__()方法,这样才能保证在回收子类的对象时,其占用的资源被彻底释放。比如下面的例子,可以看到执行del d的时候,只调用了子类的__del__()方法。

class D1:
    def __del__(self):
        print("调用父类 __del__() 方法")
class D2(D1):
    def __del__(self):
        print("调用子类 __del__() 方法")
d = D2()
del d

__ dir__()方法

__ dir__()方法可以返回一个包含所有属性名和方法名的有序列表。dir(obj)方法实际是在obj. __ dir__()的基础上做了排序,所以两种用法除了返回结果的顺序不同,其他都是一样的。

print(p.__dir__())

__dict__属性

__dict__属性会返回一个由属性组成的字典,当使用类名调用时,会返回所有类属性组成的字典;当使用实例名调用时,会返回所有实例属性组成的字典。

class demo():
    a = 1
    b = "2"
    def __init__(self,c):
        self.c = c
print(demo.__dict__)
d = demo(6873)
print(d.__dict__)

对于有继承关系的类来说,父类有自己的__dict__,子类也有自己的__dict__属性。也就是说,通过子类调用的__dict__,不会包含父类中的属性。
除此之外,通过实例名调用的__dict__属性,可以对其中的实例属性进行修改,但是通过类变量调用的__dict__,不能对类属性进行修改。

setattr()、getattr()、hasattr()

hasattr()方法用来判断某个对象中是否包含指定名称的属性或方法,返回结果为True或者False。

print(hasattr(d,"a"))
print(hasattr(d,"__init__"))

getattr()方法用来获取实例对象中指定属性的值,该方法的完整语法为getattr(obj,name,default),其中obj表示对象,name表示属性名,default是可选参数,当不传default时,查找不到属性会抛出异常;传入default时,查找不到属性会返回default的值。

print(getattr(d,"a"))
print(getattr(d,"d","3"))

setattr()方法可以修改实例对象中的属性值,也可以为对象动态添加属性或者方法。

def m1(self):
    print(self.c)
#将属性修改为方法
setattr(d,"a",m1)
d.a(d)
#将方法修改为属性
setattr(d,"a",8)
print(d.a)
#添加新属性
setattr(d,"d","ooo")
print(d.d)
#添加新方法
setattr(d,"e",m1)
d.e(d)

issubclass()和isinstance()

这两个方法是python中提供的用来检查类型的方法,完整的格式如下:
issubclass(cls, class_or_tuple):检查 cls 是否为后一个类或元组包含的多个类中任意类的子类。
isinstance(obj, class_or_tuple):检查 obj 是否为后一个类或元组包含的多个类中任意类的实例对象。

h = "str"
print('h是否是str类的实例:',isinstance(h,str))
print('h是否是object类的实例:',isinstance(h,object))
print('str是否是object类的子类: ', issubclass(str, object))
print('h是否是tuple类的实例: ', isinstance(h, tuple))
print('str是否是tuple类的子类: ', issubclass(str, tuple))

可以看出两个方法的使用方式差不多,只不过issubclass()是用来判断是否是子类,isinstance()是用来判断是否是实例对象。
此外,python为所有的类提供了一个__bases__属性,该属性的值是类的所有父类组成的元组。

print(str.__bases__)
print(D2.__bases__)

python还提供了一个__subclasses__()方法,该方法会返回所有子类组成的列表。

print(D1.__subclasses__())

__ call__()方法

通过在类中实现__call__()方法,可以使类对象可以像调用普通函数那样,通过对象名()的方式使用。

class Demo:
    def __call__(self,name):
        print("call:",name)
d = Demo()
d("zs")
d.__call__("zs")

可以看出,上面两种调用方式的结果是一样的,对象名()可以理解为对象名.__ call__()的简写。通过下面的例子可以进行验证。

def testMethod():
    print("测试方法")
testMethod()
testMethod.__call__()

通过__call__()弥补hasattr()方法的短板

上文我们讲了通过hasattr()方法可以判断对象中是否包含指定名称的属性或方法,但是无法判断该名称对应的到底是属性还是方法。通过__call__()方法,我们可以弥补这个缺陷,因为属性是不能通过__call__()调用的。

class Demo:
    def __init__(self,name):
        self.name = name
    def say(self,name):
        print("call:",name)
d = Demo("zs")
if(hasattr(d,"name")):
    print(hasattr(d.name,"__call__"))
if(hasattr(d,"say")):
    print(hasattr(d.say,"__call__"))

从上面的运行结果可以看到,由于name是属性,它没有__call__()方法,而say是方法,是可调用对象,有__call__()方法。

运算符重载

之前的文章里介绍过python中的序列类型,每个类型都有其独有的操作方法,例如列表支持直接用加号运算符添加元素,字符串支持用加号运算符拼接,也就是说,同样的运算符对于不同序列的功能是不一样的。
因为实际上,在python内部,每个序列都是一个类,例如列表是list类,字典是dict类等。在这些类的内部实现了运算符的重载,来达成上文所说的操作。
运算符重载就是指在类中定义并实现一个运算符的实现方法,这样当类对象在进行运算符操作时,就会调用类中相应的方法来处理。

class Demo:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    #用于将值转为字符串形式,等同于str(obj)
    def __str__(self):
        return "name:"+self.name+",age:"+str(self.age)
    #转化为供解释器读取的方式
    __repr__ = __str__
    #重载self<record运算符
    def __lt__(self,record):
        return self.age < record.age
    #重载+运算符
    def __add__(self,record):
        return Demo(self.name,self.age+record.age)
d1 = Demo("Jack",18)
d2 = Demo("Rose",16)
#格式化对象d1
print(repr(d1))
#解释器读取对象d1,调用repr
print(d1)
#格式化对象d1
print(str(d1))
print(d1<d2)
print(d1+d2)

上面的Demo类中,重载了str,repr,<,+运算符。下面的表格列出了python中常用的运算符,以及各自的含义。
在这里插入图片描述

python迭代器

迭代器就是一个用来支持循环的容器,在python中要实现一个迭代器,类中必须包含两个方法:
1.__ next__(self):返回容器的下一个元素。
2.__ iter__(self):该方法返回一个迭代器(iterator)。

class listDemo():
    def __init__(self):
        self.__date = []
        self.__step = 0
    def __next__(self):
        if(self.__step <= 0):
            #终止迭代
            raise StopIteration
        self.__step -= 1
        return self.__date[self.__step]
    def __iter__(self):
        #该对象本身就是迭代器对象,因此返回self即可
        return self
    #添加元素
    def __setitem__(self,key,value):
        self.__date.insert(key,value)
        self.__step += 1
l = listDemo()
l[0] = 1
l[1] = 5
for i in l:
    print(i)

除此之外,python内置的iter()函数也会返回一个迭代器,该函数的的完整语法为:iter(obj[, sentinel])。其中obj必须是一个可迭代对象,而sentinel是一个可选参数,如果使用此参数,要求obj必须是一个可调用对象,也就是实现了__call__()方法。

#将列表转为迭代器
i = iter([1,2,3])
#依次获取迭代器的下一个元素
print(i.__next__())
print(i.__next__())
#使用next()内置方法调用,跟上面的写法是一样的
print(next(i))

下面我们来介绍iter()函数第二个参数的作用,当传入第二个参数时,要求obj必须是一个可调用对象(可以不支持迭代),这样当使用返回的迭代器调用__next__()方法时,它会通过执行obj()的__call__()方法,如果该方法的返回值和第二个参数的值相同,则抛出StopInteration异常,反之,则输出__call__()方法的返回值。

class listDemo():
    def __init__(self):
        self.__date = []
        self.__step = 0
    def __setitem__(self,key,value):
        self.__date.insert(key,value)
        self.__step += 1
    #使该对象成为可调用对象
    def __call__(self):
        self.__step -= 1
        return self.__date[self.__step]
l = listDemo()
l[0] = 1
l[1] = 5
i = iter(l,1)
print(i.__next__())
print(i.__next__())

python生成器

生成器本质上也是一个迭代器,不过迭代器在迭代一组数据时,必须事先将所有的数据存到容器中;而生成器不同,它可以在迭代的同时生成元素。
生成器的创建方式分为两步:
1.定义一个以yield关键字标识返回值的函数。
2.调用刚刚创建的函数,即可创建一个生成器。

def initNum():
    print("开始执行")
    for i in range(5):
        yield i
        print("继续执行")
num = initNum()

这样就创建了一个生成器对象num。和普通函数不同,生成器函数的返回值用的是yield关键字,而不是return,这种函数就叫生成器函数。
和return相比,yield除了可以返回相应的值,还有一个更重要的功能,即每当程序执行完该语句时,程序就会暂停。不仅如此,即便调用生成器函数,函数中的代码也不会被执行,而是创建一个生成器对象。
想要使生成器函数执行,或者执行完yield语句后暂停的程序继续执行,有两种方法:
1.通过生成器对象内置的next()方法或者对象的__next__()方法。
2.通过for循环遍历生成器。

print(next(num))
print(num.__next__())
for i in num:
    print(i)

除了上述的方法外,还可以使用list()函数或者tuple()函数,直接将生成器能生成的所有值存储成列表或者元组的形式。

num = initNum()
print(list(num))

num = initNum()
print(tuple(num))

python装饰器

之前的章节中,我们已经讲过python内置的三种装饰器,@staticmethod、@classmethod 和 @property,其中staticmethod()、classmethod() 和 property() 都是 Python 的内置函数。
现在我们来介绍一下装饰器的工作原理,比如我们想用funA()函数来装饰funB()函数。

#funA()作为装饰器
def funA(fn):
    fn()#执行传入的fn参数
    return '...'

@funA
def funB():
    print("funB")

实际上,上述函数完全等价于下面的写法。

def funA(fn):
    fn()#执行传入的fn参数
    return '...'

def funB():
    print("funB")
funB = funA(funB)

通过上面的代码不难看出,使用函数装饰器funA()去装饰另一个函数funB(),实际上执行了两步操作:
1.将funB作为参数传给funA()。
2.将funA()函数执行完成的结果返回给funB。

def funA(fn):
    print("进入A函数")
    fn()#执行传入的fn参数
    print("执行完B函数")
    return '装饰器的返回值'

@funA
def funB():
    print("funB")

此时如果打印funB,得到的结果就是funA的返回值。

print(funB)

可以看出,被修饰器修饰的函数不再是原来的函数,而是被替换成了装饰器的返回值。如果返回值是普通变量,那么被修饰的函数名就变成了变量名;如果返回值试一个函数名,那么被修饰的函数仍然是一个函数。
装饰器的主要功能就是在不修改原函数的前提下,来对函数的功能进行扩充。

带参数的装饰器

上面作为例子的funA()和funB()函数都是无参的,当被修饰函数funB()有参数时,可以用下面的方法来传值。

def funA(fn):
    #定义一个嵌套函数
    def funC(param):
        print("funC:",param)
    return funC

@funA
def funB(param):
    print("funB:",param)
funB("test")

上面的写法和下面的写法是等价的。

def funA(fn):
    #定义一个嵌套函数
    def funC(param):
        print("funC:",param)
    return funC

def funB(param):
    print("funB:",param)
funB = funA(funB)
funB("test")

可以看出,上面调用的虽然是funB()函数,但是实际执行的funC()函数。
还有一种情况,当程序中有多个函数被同一个装饰器修饰,但是这些函数的参数数量不一样。这种情况就需要用*args和**kwargs作为装饰器内部嵌套函数的参数才行。

def funA(fn):
    #定义一个嵌套函数
    def funC(*args,**kwatgs):
        fn(*args,**kwatgs)
    return funC

@funA
def funB(param):
    print("funB:",param)
funB("test")
@funA
def funD(param,param2):
    print("funB:",param,param2)
funB("test1")
funD("test1","test2")

除此之外,函数的装饰器也是可以嵌套的,比如下面的写法。

def funE(fn):
    print("funE")
    return fn
 
@funA
@funE
def funB(param):
    print("funB:",param)
funB("test")

上面的装饰器执行顺序是从下到上的,相当于funA(funE(funB))。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值