面向对象 | 双下方法
Posted summer-skr--blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象 | 双下方法相关的知识,希望对你有一定的参考价值。
定义:双下方法是特殊方法,他是解释器提供的。由双下划线+方法名+双下划线 。它具有特殊意义的方法,双下方法主要是python源码程序员使用的,我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。
调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:__init__
一、 __len__
class A: def __init__(self): self.a = 1 self.b = 2 def __len__(self): return len(self.__dict__) a = A() print(len(a)) # len 一个对象就会触发 __len__方法。
二、__hash__
class A: def __init__(self): self.a = 1 self.b = 2 def __hash__(self): return hash(str(self.a)+str(self.b)) a = A() print(hash(a)) # -5694928641182144238
三、__str__ 和 __repr__
如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值。
class A: def __init__(self): pass def __str__(self): return ‘太白‘ a = A() print(a) # 太白 print(‘%s‘ % a) # 太白 print( ‘姓名:%s‘ % a) # 姓名:太白
class A: def __init__(self,*args): self.args = list(args) def __str__(self): return ‘[%s]‘ % (‘,‘.join([str(i) for i in self.args])) li = A(1,2,3,4,5) print(li) # 输出的结果是obj.__str__()的结果 print(str(li)) # 结果同上 print(‘%s‘%li) # 结果同上 执行输出: [1,2,3,4,5] [1,2,3,4,5]
如果一个类中定义了__repr__方法,那么在repr(对象) 时,默认输出该方法的返回值
class A: def __init__(self): pass def __repr__(self): return ‘太白‘ a = A() print(repr(a)) # 太白 print(‘%r‘%a) # 太白
每一个对象,都有__str__方法,print对象时,实际是调用了__str__方法;repr执行时,调用__repr__方法
class Teacher: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "Teacher‘s object %s" % self.name a = Teacher(‘alex‘, 80) b = Teacher(‘egon‘, 80) print(a) # 实际调用了__str__ print(b) print(repr(a)) # 打印对象的内存地址 ‘‘‘ 输出结果: Teacher‘s object alex Teacher‘s object egon <__main__.Teacher object at 0x00000254D84945C8> ‘‘‘
所有的输出,本质就是向文件中写。print执行时,是去内部中寻找__str__方法,所以print没有输出不了的数据, 因为每一个对象都有__str__方法,print一个对象时,打印的是内存地址
class Teacher: def __init__(self, name, age): self.name = name self.age = age def __str__(self): # 重新定义内置方法 return "Teacher‘s object %s" % self.name def __repr__(self): # 重新定义内置方法,为了做个性化输出 return ‘repr function %s‘ % self.name a = Teacher(‘Tony‘, 80) print(repr(a)) # 打印repr函数的返回值 print(a.__repr__()) # 调用__repr__方法 print(‘%r‘ % a) print(str(a)) # 打印str函数的返回值 ‘‘‘ 执行输出: repr function Tony repr function Tony repr function Tony Teacher‘s object Tony ‘‘‘
repr(obj) 、obj__repr()__ 和 ‘%r‘%(obj) 的结果是一样的
obj 、obj__str()__ 和 ‘%s‘%(obj) 的结果是一样的
双下repr是双下str的备胎:
- 当需要使用__str__的场景时找不到 __str__就找__repr__ ;
- 当需要使用__repr__的场景时找不到__repr__的时候就找父类的repr
- 如果__str__和__repr__同时存在, 一定是选择repr
总结:
- len() obj.__len__() 返回值是一致的, len() 的结果是依赖 obj.__len__();
- repr() 的结果是依赖 obj.__repr__(); %r 的结果是依赖 obj.__repr__()
- str() 的结果是依赖 obj.__str__(); print(obj) 的结果是依赖 obj.__str__();%s 的结果是依赖 obj.__str__() 语法糖
- hash() 的结果是依赖 obj.__hash__()
为什么int类型,可以使用%s,是因为int有___repr__方法
object.__repr__() ‘%r‘ # __repr__() int.__repr__() str.__repr__()
%r 和 %s也是语法糖,不是只有@, 凡是不是调用函数,而是使用一些符号之类的,都是语法糖
== 也是语法糖,它在内部执行时,是调用了内置方法,python解释器帮我们做了。
表面能使用的符号,内部其实是调用了方法,执行了计算过程。
四、__format__
format执行,就是调用了__format__方法。对象之所以能用format,是因为object有这个__format__方法
class A: def __init__(self, name, school, addr): self.name = name self.school = school self.addr = addr a = A(‘Tony‘, ‘斯坦福‘, ‘美国‘) print(format(a)) ‘‘‘ 执行输出: <__main__.A object at 0x0000021AA5E50988> ‘‘‘
format执行时,必须要有参数format_spec才行
自定义一个__format__方法
class A: def __init__(self, name, school, addr): self.name = name self.school = school self.addr = addr def __format__(self, format_spec): #format_spec = ‘{obj.name}-{obj.addr}-{obj.school}‘ return format_spec.format(obj=self) #此行的format_spec等同于上面一行 a = A(‘Tony‘, ‘斯坦福‘, ‘美国‘) format_spec = ‘{obj.name}-{obj.addr}-{obj.school}‘ print(format(a,format_spec)) ‘‘‘ 执行输出: Tony-美国-斯坦福 ‘‘‘
正常情况下,用{}表示一个占位符,它是不能变边的,如果输出的字符串,需要加中括号[ ],可以这么写
class A: def __init__(self, name, school, addr): self.name = name self.school = school self.addr = addr def __format__(self, format_spec): #format_spec = ‘{obj.name}-{obj.addr}-{obj.school}‘ return format_spec.format(obj=self) #此行的format_spec等同于上面一行 a = A(‘Tony‘, ‘斯坦福‘, ‘美国‘) format_spec = ‘[{obj.name}]-[{obj.addr}]-[{obj.school}]‘ # 每个 { } 外加 [ ] print(format(a,format_spec)) ‘‘‘ 执行输出: [Tony]-[美国]-[斯坦福] ‘‘‘
下面一个列子
format_dict = { ‘nat‘: ‘{obj.name}-{obj.addr}-{obj.type}‘, # 学校名-学校地址-学校类型 ‘tna‘: ‘{obj.type}:{obj.name}:{obj.addr}‘, # 学校类型:学校名:学校地址 ‘tan‘: ‘{obj.type}/{obj.addr}/{obj.name}‘, # 学校类型/学校地址/学校名 } class School: def __init__(self, name, addr, type): self.name = name self.addr = addr self.type = type def __format__(self, format_spec): if not format_spec or format_spec not in format_dict: # 判断参数是否为空或者是否在format_dict字典里 format_spec = ‘nat‘ # 默认值为nat fmt = format_dict[format_spec] # ‘{obj.name}-{obj.addr}-{obj.type}‘ return fmt.format(obj=self) # ‘{obj.name}-{obj.addr}-{obj.type}‘.format(obj=self) s1 = School(‘清华‘, ‘北京‘, ‘国办‘) print(format(s1, ‘nat‘)) # s1.__format__(‘nat‘) print(format(s1, ‘tna‘)) print(format(s1, ‘tan‘)) print(format(s1, ‘aaa‘)) # 字典不存在,走默认值。s1.__format__(‘nat‘) ‘‘‘ 执行输出: 清华-北京-国办 国办:清华:北京 国办/北京/清华 清华-北京-国办 ‘‘‘
五、__call__
对象后面加括号,触发执行。注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print(‘__call__‘) obj = Foo() # 执行 __init__ obj() # 执行 __call__ Foo()() # 效果同上
有些源码会出现类名()()这种的,它其实是调用了__call__方法。所以一定要注意!!!
object默认没有__call__方法,使用callable方法,判断对象是否可调用
class Teacher(): def __call__(self): print(123) t = Teacher() print(callable(Teacher)) print(callable(t)) ‘‘‘ 执行输出: True True ‘‘‘ # 一个对象是否可调用,完全取决于这个对象对应的类是否实现了__call__
看Teacher类是否有__call__方法
print(‘__call__‘ in Teacher.__dir__(Teacher)) #True print(callable(Teacher)) # True
查看callabble方法的解释,有一句话:Note that classes are callable (callinga class returns a new instance); instances are callable if their class has a __call__() method.
__call __()方法是针对对象的,而不是类。python一切皆对象
class Teacher(object): def __call__(self): print(123) def _call(self): print(456) t = Teacher() t.call() # 手动执行call方法 ‘‘‘ 执行输出: 456 ‘‘‘
六、__eq__
__eq__ 定义了类的等号(==)行为
class A:pass a = A() b = A() print(a) print(b) ‘‘‘ <__main__.A object at 0x000001F4429C5488> <__main__.A object at 0x000001F4429E9108> ‘‘‘
判断是否相等
class A:pass a = A() b = A() print(a == b) # False
==实际是调用了 __eq__方法,它是判断内存地址,是否一致
自定义__eq__方法
class A: def __eq__(self, other): return True a = A() b = A() a.name = ‘aaa‘ b.name = ‘bbb‘ print(a == b) # True
== 是由__eq__的返回值来决定的
为了让__eq__方法更有意义,再改动一下。
class A: def __eq__(self, other): if self.__dict__ == other.__dict__: return True else: return False a = A() b = A() a.name = ‘aaa‘ b.name = ‘bbb‘ print(a == b) # False
class A: def __init__(self): self.a = 1 self.b = 2 def __eq__(self, obj): if self.a == obj.a and self.b == obj.b: return True a = A() b = A() print(a == b) # True
七、__del__
析构方法,当对象在内存中被释放时,自动触发执行。只要执行了析构函数,说明对象要删除了。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class A: def __init__(self): pass def __del__(self): print(‘执行删除了‘) a = A() print(‘aaa‘) ‘‘‘ 输出结果: aaa 执行删除了 ‘‘‘
当类执行完毕时,自动执行析构方法
删除一个对象
class A: def __init__(self): pass def __del__(self): print(‘执行删除了‘) a = A() del a # 主动删除对象 print(‘aaa‘) ‘‘‘ 输出结果: 执行删除了 aaa ‘‘‘
看下面的例子
class A: def __init__(self): self.f = open(‘文件‘, ‘w‘) # 打开文件句柄 # def __del__(self): # print(‘执行我啦‘) a = A() del a print(‘aaa‘) ‘‘‘ 输出结果: aaa ‘‘‘
这段代码,有一个问题,文件打开了,但是文件句柄没有释放。那么就浪费内存了。这个时候,需要在析构方法中,关闭文件句柄
class A: def __init__(self): self.f = open(‘文件‘,‘w‘) def __del__(self): self.f.close() # 关闭文件句柄 print(‘文件关闭了‘) a = A() del a
八、__new__ *****
__new__方法是创建类实例的方法,它的调用是发生在__init__之前的
class A: def __init__(self): self.name = "哈哈" print(‘in init function‘) def __new__(cls, *args, **kwargs): print(‘in new function‘) return object.__new__(A, *args, **kwargs) a = A() print(a.name) ‘‘‘ 输出结果: in new function in init function 哈哈 ‘‘‘
object默认就有__new__。它是一个静态方法,没有self,因为此刻还没有self,__new__方法不需要写,object自带
@staticmethod # known case of __new__ def __new__(cls, *more): # known special case of object.__new__ """ Create and return a new object. See help(type) for accurate signature. """ pass
先有对象,才能初始化
__new__()面试必考*****
先来讲一个设计模式-->单例模式。如果面试中,只要是问单例模式,就是问__new__。
单例模式就让一个类的实例化对象有且只有一个
应用场景
- 当一个类,多次实例化时,每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次
- 当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题
看下面的代码:
class A:pass a = A() b = A() print(a) print(b) 执行输出: <__main__.A object at 0x00000299A8D9BF28> <__main__.A object at 0x00000299A8DA1320>
这不是单例模式,因为内存地址不一样,不是__init__的锅,是__new__的锅。__new__每次实例化,会创建一个新的内存地址
下面看一个真正的单例模式,使用__new__方法
class B: __instance = None def __new__(cls, *args, **kwargs): # cls表示类 if cls.__instance is None: # 判断类变量__instance是否为None obj = object.__new__(cls) # 创建一个实例对象 cls.__instance = obj # 赋值 return cls.__instance # 返回私有静态属性 a = B() b = B() print(a) print(b) ‘‘‘ 输出结果: <__main__.B object at 0x0000029B8A914908> <__main__.B object at 0x0000029B8A914908> ‘‘‘
第一次执行时,cls.__instance 是None,创建一个对象
第二次执行时,cls.__instance 不是None,返回私有静态属性
注意:__new__每次实例化,都会执行!!!实例化时,先执行__new__,再执行__init__
再添加几个属性
class B: __instance = None def __new__(cls, *args, **kwargs): if cls.__instance is None: obj = object.__new__(cls) cls.__instance = obj return cls.__instance def __init__(self, name, age): self.name = name self.age = age def func(self): print(self.name) a = B(‘Tony‘, 80) # 实例化,传值 b = B(‘John‘, 20) # 实例化,覆盖值 print(a) print(b) print(a.name) print(b.name) ‘‘‘ 输出结果: <__main__.B object at 0x000002466FC20DC8> <__main__.B object at 0x000002466FC20DC8> John John ‘‘‘
b实例化时,a对象的值指向就中断了。由于a和b共用一个内存空间,所以最终结果为John
单例模式具体分析
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
采用单例模式动机、原因
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
单例模式优缺点
【优点】
- 实例控制。单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
- 灵活性。因为类控制了实例化过程,所以类可以灵活更改实例化过程。
【缺点】
1)开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
2)可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
3)对象生存期
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用
九、item系列
对象使用中括号的形式去操作
__getitem__ 当访问不存在的属性时会调用该方法
__setitem__属性被赋值的时候都会调用该方法
__delitem__删除属性时调用该方法
想要用对象名.[名字],必须实现__getitem__方法
class Foo: def __init__(self, name): self.name = name def __getitem__(self, item): return (self.__dict__[item]) def __setitem__(self, key, value): self.__dict__[key] = value def __delitem__(self, key): print(‘del obj[key]时,我执行‘) self.__dict__.pop(key) f = Foo(‘Tony‘) print(f[‘name‘]) # f.__getitem__(‘name‘) f[‘age‘] = 18 # 赋值 print(f.age) # 自带的语法 print(f[‘age‘]) # 通过实现__getitem__得到的 del f[‘age‘] # 删除 # print(f.age) # 报错 ‘Foo‘ object has no attribute ‘age‘ ‘‘‘ 输出结果: Tony 18 18 del obj[key]时,我执行 ‘‘‘
_getitem __只能有一个参数
__setitem__能接收2个参数,一个是等号左边,一个是等号右边的
__delitem__很少用
__delattr不用实现,因为object自带就有。(delattr不需要重写,python自带)
class Foo: def __init__(self,name): self.name=name def __delattr__(self, item): print(‘del obj.key时,我执行‘) self.__dict__.pop(item) f = Foo(‘Tony‘) # del f.name #相当于执行了__delattr__ delattr(f,‘name‘) ‘‘‘ del obj.key时,我执行 ‘‘‘
面试题:有一个类Person,它有3个属性,分别是name,sex,age。实例化100次,每个对象的内存地址是不一样的。其中有2个对象,name和sex是一样的,age不同。那么如何,去掉这2个重复的对象?
class Person: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def __hash__(self): # 实例化时,执行此方法 return hash(self.name + self.sex) # 对name和sex做hash,因为有2个对象name和sex一样,age不同 def __eq__(self, other): # 实例化时,执行此方法 if self.name == other.name and self.sex == other.sex: # 判断每一个对象的name和sex是否相同 return True p_lst1 = [] #定义一个列表 #生成98个实例对象 for i in range(98): p_lst1.append(Person(‘egon‘ + str(i),i,‘male‘)) #手动增加2个重复的,name和sex值是一样的,age不同 p_lst1.append(Person(‘egon50‘,200,‘male‘)) p_lst1.append(Person(‘egon50‘,300,‘male‘)) #查看p_lst1的长度 print(len(p_lst1)) #100 #使用集合去重,查看p_lst1的长度 print(len(set(p_lst1))) # 98 注意:__hash__和__eq__方法,必须自己定义,否则无法去重
十、 上下文管理器相关
__enter__
__exit__
如果想要对一个类的对象进行with as 的操作 不行。
class A: def __init__(self, text): self.text = text with A(‘test.txt‘) as f1: print(f1.text) ‘‘‘ 执行报错: with A(‘test.txt‘) as f1: AttributeError: __enter__ ‘‘‘
有他们可以这样操作
class A: def __init__(self, text): self.text = text def __enter__(self): # 开启上下文管理器对象时触发此方法 self.text = self.text + ‘enter出现了‘ return self # 将实例化的对象返回f1 def __exit__(self, exc_type, exc_val, exc_tb): # 执行完上下文管理器对象f1时触发此方法 self.text = self.text + ‘exit出现了‘ with A(‘test.txt‘) as f1: print(f1.text) print(f1.text) ‘‘‘ 执行输出: test.txtenter出现了 test.txtenter出现了exit出现了 ‘‘‘
自定义文件管理器
class Diycontextor: def __init__(self, name, mode): self.name = name self.mode = mode def __enter__(self): print("Hi enter here!!") self.filehander = open(self.name, self.mode) return self.filehander def __exit__(self, *para): print("Hi exit here") self.filehander.close() with Diycontextor(‘test2.py‘, ‘r‘) as f: for i in f: print(i)
内置方法 必须能看懂 能用尽量用
-
__len__ len(obj)的结果依赖于obj.__len__()的结果,计算对象的长度
-
__hash__ hash(obj)的结果依赖于obj.__hash__()的结果,计算对象的hash值
-
__eq__ obj1 == obj2 的结果依赖于obj.__eq__()的结果,用来判断值相等
-
__str__ str(obj) print(obj) ‘%s‘%obj 的结果依赖于__str__,用来做输出、显示
-
__repr__ repr(obj) ‘%r‘%obj的结果依赖于__repr__,还可以做str的备胎
-
__format__ format() 的结果依赖于__format__的结果,是对象格式化的
-
__call__ obj()相当于调用__call__,实现了__call__的对象是callable的
-
__new__ 构造方法,在执行__init__之前执行,负责创建一个对象,在单例模式中有具体的应用
-
__del__ 析构方法,在对象删除的时候,删除这个对象之前执行,主要用来关闭在对象中打开的系统的资源
单例模式
只有一个对象 只开了一个内存空间
创建一个类 单例模式中的对象属性编程类中的静态属性,所有的方法变成类方法
python中的单例模式 是使用__new__
默写单例模式:
class B: __instance = None def __new__(cls, *args, **kwargs): if cls.__instance is None: obj = object.__new__(cls) cls.__instance = obj return cls.__instance def __init__(self,name,age): self.name = name self.age = age def func(self): print(self.name) a = B(‘Tony‘,80) b = B(‘John‘,20) print(a) print(b) print(a.name) print(b.name)
相关面试题:
class StarkConfig: def __init__(self, num): self.num = num def run(self): self() def __call__(self, *args, **kwargs): print(self.num) class RoleConfig(StarkConfig): def __call__(self, *args, **kwargs): print(345) def __getitem__(self, item): return self.num[item] v1 = RoleConfig(‘Tony‘) v2 = StarkConfig(‘John‘) print(v1[1]) # o # print(v2[2]) # TypeError: ‘StarkConfig‘ object does not support indexing v1.run() # 345
class UserInfo: pass class StarkConfig: def __init__(self, num): self.num = num def changelist(self, request): print(self.num, request) def run(self): self.changelist(999) class AdminSite: def __init__(self): self._registry = {} def register(self, k, v): self._registry[k] = v site = AdminSite() site.register(UserInfo, StarkConfig) # 1 # obj = site._registry[UserInfo]() # TypeError: __init__() missing 1 required positional argument: ‘num‘ # 2 obj = site._registry[UserInfo](100) obj.run()
class UserInfo: pass class Department: pass class StarkConfig: def __init__(self, num): self.num = num def changelist(self, request): print(self.num, request) def run(self): self.changelist(999) class RoleConfig(StarkConfig): def changelist(self, request): print(666, self.num) class AdminSite: def __init__(self): self._registry = {} def register(self, k, v): self._registry[k] = v(k) site = AdminSite() site.register(UserInfo, StarkConfig) site.register(Department, RoleConfig) for k, row in site._registry.items(): row.run() ‘‘‘ 执行输出: <class ‘__main__.UserInfo‘> 999 666 <class ‘__main__.Department‘> ‘‘‘
class A: list_display = [] def get_list(self): self.list_display.insert(0,33) return self.list_display s1 = A() print(s1.get_list()) # [33]
class A: list_display = [1, 2, 3] def __init__(self): self.list_display = [] def get_list(self): self.list_display.insert(0, 33) return self.list_display s1 = A() print(s1.get_list()) # [33]
class A: list_display = [] def get_list(self): self.list_display.insert(0,33) return self.list_display class B(A): list_display = [11,22] s1 = A() s2 = B() print(s1.get_list()) # [33] print(s2.get_list()) # [33, 11, 22]
面试题:写一个类 定义100个对象,拥有三个属性 name age sex。如果两个对象的name 和 sex完全相同就认为这是一个对象忽略age属性,做这100个对象的去重工作
hash算法:一个值进行一系列的计算得出一个数字在一次程序执行中总是不变来让每一个不同的值计算出,的数字都不相等
创建一个对象
class Person: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def __hash__(self): # hash算法本身就存在了 且直接在python中就能调用 # 姓名相同 性别相同的对象的hash值应该相等才行 # 姓名性别都是字符串 return hash(self.name + self.sex) def __eq__(self, other): if self.name == other.name and self.sex == other.sex: return True obj_lst = [] # 手动创建8个对象,将对象写入列表 obj_lst.append(Person(‘Selina‘, 80, ‘male‘)) obj_lst.append(Person(‘Selina‘, 70, ‘male‘)) obj_lst.append(Person(‘Selina‘, 60, ‘male‘)) obj_lst.append(Person(‘Hebe‘, 50, ‘male‘)) obj_lst.append(Person(‘Hebe‘, 40, ‘male‘)) obj_lst.append(Person(‘Hebe‘, 30, ‘male‘)) obj_lst.append(Person(‘Ella‘, 20, ‘male‘)) obj_lst.append(Person(‘Ella‘, 10, ‘male‘)) obj_lst = set(obj_lst) # 列表去重 for obj in obj_lst: print(obj.name) # 打印列表 ‘‘‘ 执行输出: Hebe Selina Ella
set对一个序列对象(有索引的对象)去重,依赖于这个对象的两个方法 hash和eq
key hash 数字 --》 内存地址 --》 value
set hash 数字 --》 内存地址 --》 set中的元素
‘aaa‘ hash A
如果值存在,则覆盖
那么问题来了:判断第100个元素时,需要做100次__eq__,set 对一个对象序列去重,如何判断这2个值是否相等
值a进行hash --> 存值
值b进行hash --> 判断值是否相等 -相等-> 说明是一样的
不相等-> 在开辟一个空间 来存放b
因为set依赖__hash__和__eq__所以在类里面,重新定义这2个方法,可以做个性化需求,最终得到我们想要的结果。
以上是关于面向对象 | 双下方法的主要内容,如果未能解决你的问题,请参考以下文章