这两天看了下面向对象中的一些双下方法,虽然平时很不用双下方法,看完之后感觉如果能熟悉并且很好的运用双下方法的话,可以写出很漂亮的代码。
0x00 __str__与__repr__
在打印一个对象的时候其实就是调用其__str__方法,由于没有重写__str__,则会调用父类的__str__方法,如果没有的话,最终调用了Object类的__str__方法。
class Foo(): def __str__(self): return 'This is Class Foo' f = Foo() print(f) #This is Class Foo #如果没有__str__方法的话会打印一个内存地址 # <__main__.Foo object at 0x00000227CFD33128>
__str__方法主要的作用一般是用来对类、对象进行描述;一般情况下都会返回一个字符串。其实在使用str(f)的时候就是调用了类中的__str__方法。
提到__repr__方法呢,肯定会想到内置函数repr()方法。
s = 'test' print(repr(s)) #'test'
从输出结果上看,其可以将字符串的引号也输出。其主要作用是将对象转化为供解释器读取的形式。
class Foo(): def __repr__(self): return 'This is Class Foo repr' f = Foo() print(repr(f)) #This is Class Foo repr
在对类或者对象调用repr()方法的时候,其就是调用了类内部的__repr__方法。
格式化输出中的应用
class Foo(): def __str__(self): return 'This is Class Foo' def __repr__(self): return 'This is Class Foo repr' f = Foo() print('%r !!!!! %s'%(f,f)) #This is Class Foo repr !!!!! This is Class Foo
在使用格式化输出的时候使用%r的时候也是默认调用了__repr__方法,而使用%s的时候则是调用__str__方法。
__repr__备胎方法
准确的说__repr__是__str__的备胎方法,当需要调用__str__的时候,会在类中寻找__str__,如果没有寻找到__str__方法,则会调用__repr__方法。
class Foo(): def __repr__(self): return 'This is Class Foo repr' f = Foo() print(str(f)) #This is Class Foo repr print('%s'%f) #This is Class Foo repr
上面的例子就是__repr__成为了__str__的备胎。真是没有想到在编程世界中还有备胎的存在。
0x01 __len__、__del__和__call__
在对一个对象进行长度计算的时候通常都是调用了其的__len__方法,因而实现了__len__方法才可以使用len()函数。
class Classes(): def __init__(self,student): self.student = student def __len__(self): return len(self.student) stuList = ['xzy','moe','xzymoe'] c = Classes(stuList) print(len(c)) #3
上面__len__的作用就是统计Classes对象里的学生的人数,因而实现__len__方法即可。
class Classes(): def __init__(self,student): self.student = student def __len__(self): return len(self.student) def __del__(self): print('lalalala') stuList = ['xzy','moe','xzymoe'] c = Classes(stuList) print(len(c)) del c #lalalala print(c) #NameError: name 'c' is not defined
从输出结果上看,在使用del的时候就先调用了__del__之后就自动删除了对象,不用在__del__中写删除的动作,与__delitem__不同,__delitem__方法需要在方法内部写删除动作。
__call__方法可以让实例能够像函数一样被调用,同时不影响实例本身的生命周期,不影响一个实例的构造和解析,不过可以用来改变其内部函数。
class Foo(): def __call__(self, *args, **kwargs): print('This is call func!') Foo()() #This is call func!
这样生成的对象就可以直接加上括号就使用了!!当然__call__函数也可以在函数内部对属性进行修改操作。
0x02 item系列的函数
item系列的函数主要有三个:__getitem__、__setitem__和__delitem__,其主要作用就是可以让操作对象像操作字典一样的方便。
class Person(): def __init__(self,name,age): self.name = name self.age = age def __getitem__(self, item): if hasattr(self,item): return self.__dict__[item] def __setitem__(self, key, value): self.__dict__[key] = value def __delitem__(self, key): del self.__dict__[key] p = Person('xzymoe',20) print(p['name']) #xzymoe p['country'] = 'China' print(p.__dict__) #{'country': 'China', 'age': 20, 'name': 'xzymoe'} del p['age'] print(p.__dict__) #{'country': 'China', 'name': 'xzymoe'}
这样就可以通过字典的操作方式来完成对对象属性的操作了。
0x03 __eq__和__hash__方法
在两个对象进行比较时候调用的就是__eq__方法,比如两个对象我们打算当其的name属性相同就判断为两个对象相等,那么可以通过重新__eq__方法。
内置函数中有一个hash()方法,默认情况下是对对象的内存地址进行hash的,而我们可以重写__hash__来修改hash的方法。
class Person(): def __init__(self,name,age): self.name = name self.age = age def __eq__(self, other): if self.name == other.name: return True return False def __hash__(self): return hash(self.name + str(self.age)) p = Person('xzymoe',20) q = Person('xzymoe',18) print(p == q) #True print(hash(p)) #-619876054969235157 print(hash(q)) #3494228422336944401 q.age = 20 print(hash(q)) #-619876054969235157
0x04 __new__方法
当创建一个对象的时候,是调用__init__方法,而在__init__方法之前,默认调用__new__方法,只是一般情况下都很不用__new__所以经常被忽略。
不过有一种设计模式——单例模式,这个就需要对__new__进行重写了。单例模式为,第一次实例化这个类时,就创建一个实例化的对象,当之后再来实例化的时候,就用第一次实例化的那个对象。
class Person(): __instance = False def __init__(self,name,age): self.name = name self.age = age def __new__(cls, *args, **kwargs): if cls.__instance: return cls.__instance else: cls.__instance = object.__new__(cls) return cls.__instance p = Person('xzymoe',20) print(id(p)) #2348200246240 d = Person('moe',18) print(id(d)) #2348200246240