一 面向对象基础
在 Python 中,面向对象编程主要有两个主题,就是类和实例。
类大致由类变量,实例变量,类方法三部分组成
class Human: # class是关键字,Human是类名,类名使用驼峰(CamelCase)命名风格,首字母大写,私有类可用一个下划线开头。 """ 此类主要是构建人类 #解释文档 """
mind = '有思想' # 类变量 faith = '佛系'
def __init__(self, name, age): # 实例变量 self.name = name self.age = age
def work(self): # 方法 、 动态属性 #类方法 print('%s的工作' % self.name)
p1 = Human('matt',20) #调用
类的特点:
- 多态:对不同的类对象使用相同的操作方法。
- 封装:封装之后,可以直接调用类的对象,来操作内部的一些类方法,不需要让使用者看到代码工作的细节。
- 继承:类可以从其它类或者元类中继承它们的方法,直接使用。
实例化时共发生了三件事:
- 在内存中开辟了一个空间; ===》__new__方法,构造函数
- 将空间传给__init__(self)中的self,并执行类中的__init__方法;===》初始化函数
- 将空间返回给调用的实例。
封装的定义:
- 广义:把变量和方法封装在一个类里,定义一个规范来描述一类事物
- 狭义:私有化,只能在类的内部访问
1. 类变量操作
1.1 __dict__ 查看所有类变量和实例变量
class Human: faith = '佛系' food = 'meat' hair = 'black' def __init__(self, name,age,hobby): #构造函数 self.name = name self.age = age self.hobby = hobby def work(self): print('%s的工作'%self.name) print(Human.__dict__ ) p = Human('matt',20, 'dog') print(p.__dict__) {'__module__': '__main__', 'faith': '佛系', 'food': 'meat', 'hair': 'black',
'__init__':__init__ at 0x00000000025CA9D8>, 'work': ,
'__dict__':'__dict__' of 'Human' objects>,
'__weakref__':'__weakref__' of 'Human' objects>, '__doc__': None} #所有的类变量
{'name': 'matt', 'age': 20, 'hobby': 'dog'} #所有的实例变量
注意:实例空间仅仅存放实例变量,不会存放类变量和类方法;通过实例访问类变量和类方法时,实例会根据构造函数的声明位置向上查找。
1.2 外部对类变量的增删改查
class Human: faith = '佛系' food = 'meat' hair = 'black' def __init__(self, name,age,hobby): self.name = name self.age = age self.hobby = hobby def work(self): print('%s的工作'%self.name) Human.eye = 'blue' #增 del Human.hair #删 Human.food = 'rice' #改 print(Human.faith) #查,查找单个变量 print(Human.__dict__) #查看所有变量 佛系 {'__module__': '__main__', 'faith': '佛系', 'food': 'rice', '__init__':__init__ at 0x00000000027AA9D8>,
'work':, '__dict__': '__dict__' of 'Human' objects>,
'__weakref__':'__weakref__' of 'Human' objects>, '__doc__': None, 'eye': 'blue'}
1.3 内部函数对类变量的增删改查
#自身内部函数对类变量的修改
class Human: faith = '佛系' food = 'meat' hair = 'black' def __init__(self, name): self.name = name def work(self): Human.eye = 'blue' del Human.hair Human.faith = 'God' print(Human.food) #访问类变量只能通过定向查找(Human.food)的方式,即使在类内部的函数也不能用food,可以认为,只有定向查找才开启向上查找,否则仅仅本空间查找。 对比2.2 Human.work('matt') print(Human.__dict__)
# 2号类中的函数对1号类中类变量的修改 class Human2: def work(self): Human.faith = 'God2' Human2.work('matt') #类名 调用 类方法 print(Human.faith)
meat
{'faith': 'God', 'food': 'meat', '__init__':
God2
总结:只要有类名这一指针,就能完成对类内部变量的操作
2. 实例变量操作
2.1 外部对实例变量的增删改查
class Human: faith = '佛系' food = 'meat' hair = 'black' def __init__(self, name,age,hobby): self.name = name self.age = age self.hobby = hobby def work(self): print('%s的工作'%self.name) p = Human('matt',20,'dog') p.eye = 'blue' #增 del p.hobby #删 p.age = 30 #改 print(p.name) #查 print(p.__dict__) matt {'name': 'matt', 'age': 30, 'eye': 'blue'}
2.2 内部函数对实例变量的增删改查
#自身内部函数对实例变量的操作
class Human: def __init__(self, name,age,hobby): self.name = name self.age = age self.hobby = hobby def work(self): self.eye = 'blue' del self.hobby self.age = 30 print(self.name) # 对比 1.3 p = Human('matt',20,'dog') p.work() print(p.__dict__)
#2号类中函数对1号类实例变量的操作 class Human2: def work(self): p.name = 'logan' p2 = Human2() p2.work() print(p.__dict__)
matt
{'name': 'matt', 'age': 30, 'eye': 'blue'}
{'name': 'logan', 'age': 30, 'eye': 'blue'}
只要有实例名称,就能修改实例变量,同类变量一致
3. 变量的命名空间解析
Human是类名也是命名空间名称,P是实例名称也是命名空间名称;形如Human. faith 和 p. name,可完成定向操作。
类变量和实例变量的本质都是字典,字典在参数传递中属于可变数据类型。
class Human: faith = '佛系' print(faith) #id = 1 def __init__(self, name): self.name_obj = name self.eye = 'blue' age = 20 print(locals()) def work(self): print(self.name_obj) print(locals()) # id = 2 p = Human('matt') # id = 3 print(p.__dict__) # id = 4 p.work() # id = 5 print(globals()) # id = 6 佛系 #id = 1 {'__qualname__': 'Human', 'faith': '佛系', '__init__':__init__ at 0x000000000248A9D8>, 'work': }
# id = 2 类Human中的变量 {'self': <__main__.Human object at 0x00000000020CC748>, 'name': 'matt', 'age': 20}
# id = 3 实例化时只会执行构造函数__init__,此变量空间里未生成name_obj 和 eye ,因为两者在定向操作的实例p空间下生成。 {'name_obj': 'matt', 'eye': 'blue'}
#id = 4 执行构造函数后,在实例p空间下生成的结果,只会生成带self的变量。 matt #id = 5 通过实例名调用类函数,在实例空间找不到work变量后,会根据构造函数的声明位置进行向上查询,直到找到或报错。但仅限于查询,不能修改,类似于嵌套函数中,内层对外层只能访问,不能修改。 {'__name__': '__main__', '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000001FB1CF8>,
'__file__': 'E:/2019allstark/practice1/1/001.py', 'Human': <class '__main__.Human'>, 'p': <__main__.Human object at 0x00000000020CC748>}
# id = 6 全局变量, 虽然类Human和实例p的命名空间是同级的,但实例p可以通过构造函数的声明位置向上寻值,因此可以通过实例访问类变量和类函数。
3.1 属性查找的顺序
类向上查找的方式,此方式不同于嵌套函数的向上查找。
- 实例查找属性的顺序:先从对象空间找 ------> 类空间找 ------> 父类空间找 ------->..... #类空间找不到去父类里找,而不是到全局变量查找
- 类名查找属性的顺序:先从本类空间找 -------> 父类空间找--------> ........
上面的顺序都是单向不可逆,类名不可能找到对象的属性,并且只是查询,不能修改。类似于嵌套函数,内层函数只能对外层中的变量进行访问,不能修改,修改就是在内层创建新值。
def func(): i = 1 def func1(): # del i #取消注释会报错,不能修改外部命名空间的变量。 print(i) func1() func()
1
3.2 函数对全局变量(可变类型)的修改
类变量和实例变量的本质是字典,函数对全局变量(可变类型)的修改 与 类变量和实例变量的修改 一致,都属于定向操作。
dict1 = {'a': 1, 'b': 2} con1 = 3 def func(var): var = 4 #在函数内部生成一个新变量,不可变数据不会被修改,只会生成新值 def func1(var): dict1['a']=var #定向修改可变数据 func(10) func1(10) print(dict1) print(con1)
3.3 练习
1、计算一个类实例化的次数
class H: count = 0 def __init__(self): H.count +=1 #只有定向查找才能开启向上查找,并且不会查找全局变量 # H.count = self.count +1 #与上一句作用相同 print(H.count) h1 = H();h1 = H();h1 = H();h1 = H();h1 = H()
1;2;3;4;5
2、游戏练习
class GameRole: def __init__(self, name, hp): self.name = name self.hp = hp def attcak(self, p, w): p.hp -= w.ad print('%s 使用 %s 攻击 %s , 掉了%s血, 还剩%s' % (self.name, w.name, p.name, w.ad, p.hp)) class Weapon: def __init__(self, name, ad): self.name = name self.ad = ad p1 = GameRole('matt', 500) p2 = GameRole('logan', 300) w1 = Weapon('axe', 40) w2 = Weapon('sword', 60) p1.attcak(p2, w1)
matt 使用 axe 攻击 logan , 掉了40血, 还剩260
二 继承
类继承的本质是:为 实例变量 、类变量方法 或者 构造函数 提供向上查找的命名空间。类继承不会复制任何内容,仅仅拓宽了向上查找的命名空间。
类向上查找的方式:
- 实例查找属性的顺序:先从对象空间找 ------> 类空间找 ------> 父类空间找 ------->.....
- 类名查找属性的顺序:先从本类空间找 -------> 父类空间找--------> ........
1. 单继承
1.1 构造函数的继承
声明的类中不包含构造函数时会向上查找。
class Human: def __init__(self,name): self.name = name def func(self): print('1') class People(Human): #子类执行父类的构造函数,但寻值时还是从子类开始,区别于从构造函数声明位置开始 def func(self): print('2') p = People('matt') p.func()
2
1.2 同时执行本类和父类的构造函数
class Human: def __init__(self, name, age): self.name = name self.age = age def run(self): print('running...') class People(Human): def __init__(self, name, age, hobby): # Human.__init__(self, name, age) #一般不写这种,注意这是调用语句,不是声明语句 super().__init__(name, age) #推荐写法,super是严格按照继承顺序执行的 # super(People, self).__init__(name, age) #上式的简写 self.hobby = hobby def jump(self): print('jumping...') p = People('matt','20','dog')
1.3 同时执行父类和子类的同名方法
class Human: def run(self, var): print('1234%s'%var) class People(Human): def run(self,var): print('2234') super().run(var) #super隐藏self参数,super按照继承顺序执行 p = People() p.run('啊啊啊') 2234 1234啊啊啊
2. 多继承
新式类:继承object的类,广度优先,进入点有且仅有一个,且后来的进入
经典类:不继承object的类,深度优先,一条路走到黑
新式类查询继承顺序:__mro__, 算法:C3
class A: pass class B: pass class C(A, B): pass class D(A, B): pass class E(C, D): pass print(E.__mro__) # __mro__继承顺序 (<class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
参考:C3算法详细解析
https://www.cnblogs.com/jin-xin/articles/10274734.html
三 多态
1. 定义
一种类型的多种表现形态
举例:类B和类C都继承类A,B和C就是A的两种表现形态,python中一切皆对象,多有的对象都继承object,所以python中处处是多态。
2. 抽象类
抽象类中只能有抽象方法,子类继承抽象类时,不能通过实例化使用其抽象方法,必须实现该方法。一句话,让继承的子类中必须定义该方法。
抽象类(单继承)接口类(多继承,约束为pass)是在java中区分的,在python中不区分
from abc import ABCMeta,abstractmethod #abc模块就是关于抽象类模块 class Abs(metaclass=ABCMeta): #抽象类 @abstractmethod def foo(self): #定义抽象方法,无需实现功能 pass class A(Abs): #子类继承抽象类,必须定义抽象方法 def foo(self): print('aa') class B(Abs): #子类继承抽象类,不定义抽象方法,实例化时会报错 pass a = A();a.foo() # b = B() aa
参考:抽象了概念解析
http://www.imooc.com/article/74245
3. 鸭子类型
规范全凭自觉
class A: def f1(self): print('in A f1') def f2(self): print('in A f2') class B: def f1(self): print('in A f1') def f2(self): print('in A f2') a = A();b = B() # A 和 B两个类完全没有必然联系,但是在某种意义上他们却统一了一个标准。 # 对相同的功能设定了相同的名字,这样方便开发,这两个方法就可以互成为鸭子类型。 # 这样的例子比比皆是:str tuple list 都有 index方法,这就是统一了规范。 # str bytes 等等 这就是互称为鸭子类型。
四 类的其他成员
class A: class_name = 'class' # 类变量 __iphone = '1353333xxxx' # 私有类变量:双下划綫开头,只有本类内部能够访问,定向访问无效,一句话:只有本类函数能调用 def __init__(self,name,age): # 构造方法 self.name = name # 实例变量 self.__age = age # 私有实例变量:双下划綫开开头,只有本类函数能调用 def func1(self): # 方法、实例方法,self代表实例 pass def __func(self): # 私有方法:双下划綫开头, print(666) @classmethod # 类方法(classmethod),cls代表实例的父类 def class_func(cls): """ 定义类方法,至少有一个cls参数 """ print('类方法') @staticmethod # 静态方法 def static_func(): """ 定义静态方法 ,无默认参数""" print('静态方法') @property # 属性方法 def prop(self): pass
1. 私有变量
双下划线开头,在类内部的方法中使用时 self.__private_attrs或者self.__private_methods
私有变量的本质就是只留一个访问接口,留给本类的方法。
私有化:在类的内部访问,你就知道了在哪个类中。
1.1 私有类变量
class A: __NAME = "matt" #私有类变量一般都是全大写 def func(self): print(A.__name) class B(A): def show(self): print(A.__name) # A.__NAME #报错 obj = A() # obj.__NAME #报错 obj.func() obj_son = B() # obj_son.show() #报错
obj_son.func()
matt; matt
1.2 私有实例变量
class C: def __init__(self): self.__foo = "matt" def func(self): print(self.__foo) class D(C): def show(self): print(self.__foo) obj = C() # obj.__foo #报错 obj.func() obj_son = D() # obj_son.show() #报错;执行父类构造函数,但依旧会报错 obj_son.func() matt;matt
1.3 私有方法
class C: def __init__(self): pass def __add(self): print('C') class D(C): def __show(self): print('D') def func(self): self.__show() obj = D() # obj.__show() #报错 obj.func() # obj.__add() #报错
D
2. 静态方法
@staticmethod 静态方法是类中的独立函数,不需要实例,不需要传递self参数。简单说:静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
调用:实例名称+方法名称
class Human: def __init__(self,name): self.name = name @staticmethod def func(a, b ): return a+b p = Human('matt') print(p.func(1,2) ) #3
3. 类方法
普通的方法叫做实例方法,导入的self代表实例, 类方法(@classmethod)导入的第一个是类cls。
调用方法:只能通过类名+方法名称,这种调用方法很少见
class Dog(object): food = "bone" age = "1" def __init__(self, name): self.name = name @classmethod def eat(cls,age): #print(self.name) 会报错,这是因为类变量里没有name print(age) print(cls.food) d = Dog("金毛") d.eat(Dog.age) #1;bone
用途:一般是调用类方法去修改类变量
class ClassTest(object): __num = 0 @classmethod def addNum(cls): cls.__num += 1 @classmethod def getNum(cls): return cls.__num def __new__(self): ClassTest.addNum() return super(ClassTest, self).__new__(self) # return object.__new__(self)等效 class Student(ClassTest): def __init__(self): self.name = '' a = Student();b = Student();print(ClassTest.getNum()) #2
4. 属性方法
4.1 @ property
函数不用‘’()‘’直接调用,一般都有返回值,形式上类似于变量,一句话:伪装成属性,函数调用时不用()
用途:返回的是一个属性,这个属性一般会随着类或者实例的基础数据变化而变化,例如求圆的面积
class Flight: def __init__(self, name): self.flight_name = name def checking_status(self): print("checking flight %s status " % self.flight_name) return 1 @property def flight_status(self): status = self.checking_status() if status == 0: print("flight got canceled...") elif status == 1: print("flight is arrived...") else: print("cannot confirm the flight status...,please check later") f = Flight("CA980") f.flight_status #不用()就可以调用,形式类似于变量
checking flight CA980 status
flight is arrived...
4.2 @method_name.setter;@method_name.deleter
class A: def __init__(self,name,age): if type(age) ==int: self.name = name self.__age = age else: print('age输入数字') @property # 属性化方法 def age(self): return self.__age @age.setter # a.gae = '20' 语句调用,@name.setter执行完之后执行@property;
def age(self,var): # var 为赋值的数据;设置这个装饰器的原因在于a.age = ‘20’ ,age本质是个方法,若将其改为字符串等数据,会影响后期调用操作。 if type(var) == int: self.__age = var else: print('重新输入数字') @age.deleter # del a.age 语句调用,之后不会自动执行@property def age(self): del self.__age a = A('matt',23) a.age = '20' print(a.age) del a.age # print(a.age)
重新输入数字; 23
5. 双下方法
双下方法一般都放置在类中,其调用方法不尽相同(内置函数调用,特殊符号如:+调用);别名:特殊方法,魔术方法,内置方法。
双下方法(类的内置方法)一般与内置函数有联系:内置函数一般都是调用双下方法来实现功能的(间接调用),双下的返回值就是内置函数的返回值。
1、__doc__ 注释文档
2、__module__ 和 __class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
3、 __init__ 实例化方法
实例化时自动执行
4、__del__ 析构方法
调用:对象在内存中被释放时,自动触发执行,进行垃圾回收,分为两种执行情况:
- 主动这行del语句;
- 在程序最后执行,因为所有程序执行在全部执行完之后都要消除变量,释放内存
class A: def __init__(self,name,age): self.name =name self.age =age def __del__(self): print('asd') a = A('matt',20) b = A('loagn',30) del a.name #删除实例变量不会触发 print('-----') del a #触发:先执行print(‘asd’),再执行删除实例,因为__del__需要传入self的值。 print('-----') #所有语句执行完之后,消除所有实例、变量,触发
-----;asd;-----;asd
用途:主要用于在最后关闭打开的文件;
class File: def __init__(self, file_path): self.f = open(file_path) def read(self): self.f.read() def __del__(self): #保证最后一定能关闭文件 self.f.close() file = File('file_path') file.read()
5、 __call__
调用:实例名称+()
__inin__的调用方式为:类名称+();__call__的调用方式为:实例名称+()
class A: def __call__(self, *args, **kwargs): print('call') class B: def __init__(self,cls): self.cls =cls() self.cls() def func(self): pass
a = A(); a() #调用__call__的方式:实例名称+() b = B(A) #源代码中常用此方式
call;call
6、__dict__ 查看类或实例中的所有成员
不同于内置方法dict(),后者为用多种方法构造字典
class A: def __init__(self,name,age): self.name =name self.age = age def func(self): pass a =A('matt',20) print(A.__dict__);print(a.__dict__) {'__module__': '__main__', '__init__':__init__ at 0x00000000027AABF8>, 'func': ,
'__dict__':'__dict__' of 'A' objects>, '__weakref__': '__weakref__' of 'A' objects>, '__doc__': None} {'name': 'matt', 'age': 20}
7、__str__
调用: print(obj. __str__),打印时自动执行,并非str()调用;
返回值必须是字符串格式
li = [1,2];print(li) # print(li) 等效于print(list.__str__)
class A:pass a = A();print(a) # 等效于print(object.__str__),默认为内存空间
class B: def __init__(self,name ,age): self.name =name self.age =age def __str__(self): return '%s is %s'%(self.name,self.age) #返回值必须是字符串 b = B('matt',20) print(b) # 等效于print(b.__str__) [1, 2] <__main__.A object at 0x00000000020CC7B8> matt is 20
__str__并不是由str()函数调用的;str(obj)是内置类,其返回obj的字符串格式
li = [1,2,3] class A: def __str__(self): pass class B(A): def __init__(self, name): self.name = name def __str__(self): return str(self.name) #调用的是内置类 b =B(li) print(b);print(type(b))
[1,2,3] ;
8、repr
调用:repr(obj) ; 作为__str__的备胎,类中不存在__str__时调用;%r
li = 'asd';print(repr(li))
class A: def __repr__(self): return 'asd' #返回值必须是字符串 a =A();print(a)
print('我是%r'%'matt')
'asd';asd; 我是'matt' #repr() , %r 与__repr__略有不用
__str__查找顺序:先去父类,然后再__repr__
class A: def __str__(self): return 'A.__str__' class B(A): def __repr__(self): return 'B.__repr__' b =B();print(b) A.__str__
9、__getitem__、__setitem__、__delitem__
对象通过 [ ] 访问、设置、删除值有关
class A(object): def __getitem__(self, key): print('__getitem__', key) def __setitem__(self, key, value): #要求三个参数 print('__setitem__', key, value) def __delitem__(self, key): print('__delitem__', key) a = A() result = a['k1'] #查询,触发,三个方法都需要[ ]触发 a['k2'] = 'alex' #设置,触发 del a['k1'] #删除,触发
__getitem__ k1
__setitem__ k2 alex
__delitem__ k1
作用:在内置模块中,有一些特殊的方法,要求实例必须实现__getitem__/__setitem__ /__delitem__才能使用
10、__len__ 返回对象内的元素个数
调用:len(obj)
# 解析内置函数len()与双下方法的联系
class A: def __len__(self): return 100 a = A() print(len(a)) #内置函数都是调用双下方法来实现的,双下方法的返回值就是内置函数的返回值 print(a) #内置函数len(a),处理过程为首先找到实例a的类,然后执行类中的__len__()方法,若本类中没有,则去父类中查找 100 <__main__.A object at 0x00000000020477F0>
11、__new__
实例化时完成三件事:
- 开辟一块内存空间;
- 将空间传给__init__(self)中的self,并执行__init__方法;
- 将空间传给调用者
class A: def __new__(cls, *args, **kwargs): #传入的参数是类,因为此时实例还未生成,cls = A obj = object.__new__(cls) #调用原类object中的__new__方法,实质为开辟内存空间 print(obj) return obj #返回obj空间 def __init__(self): #接受__new__方法返回的空间,并赋值给self,之后执行__init__方法 print(self) a = A() #执行顺序为:A() >>> __new__ >>> __init__ >>> a = A() #self赋值给了a,而不是a赋值给了self <__main__.A object at 0x00000000023F82B0> <__main__.A object at 0x00000000023F82B0>
单例类:一个类始终只能只有一个实例
class Single: __SPACE = 0 def __new__(cls, *args, **kwargs): if cls.__SPACE == 0: cls.__SPACE = object.__new__(cls) return cls.__SPACE def __init__(self, name): self.name = name a = Single('matt') b = Single('logan') print(a.name);print(b.name)
logan;logan
12、__eq__ 与 == 一致
判断两个对象是否相等
def func(): a = 10246 return a def func1(): b = 10246 return b a = func();b = func1() print(id(a));print(id(b)) print(a == b);print(a is b);print(a.__eq__(b))
print(set((a,b)))
33937392;33936816;True;False;True; {10246} #验证与==一致
重构__eq__方法
class A: def __init__(self,name,age): self.name =name self.age = age def __eq__(self, other): if self.name == other.name and self.age == other.age: return True a = A('matt',20);b = A('matt',20) print(a == b) True
13、__hash__
这个hash与文件的hash(hashlib)不相同
__hash__和__eq__经常是一起出现的
#自己修改的类似于__eq__和__hash__的源码,仅供理解,源代码并非如此
def __hash__(self): return hash(id(self)) # hash的是实例的内存地址,能够解释同一数据在一次执行中hash值相同,在不同执行中不同
def __eq__(self, other):
if isinstance(other, self.__class__): #首先判读是否属于一类 return self.__dict__== other.__dict__ #判断只是否相等,此行与参考不一致 else: return False
set、frozenset和dict这些集合利用hash函数创建键,利用不可变对象的哈希值来高效查找集合中的对象。
须知:不同的内存地址可能hash值一样,;hash值不同,内存地址一定不同
set去重解析:
- set() 函数中会先调用对象的
__hash__()
方法,获取 hash 结果; - 如果 hash 结果相同(桶球理论,hash值相同会自动提示),用比较操作符
==
(也就是调用函数__eq__()
)判断二者的值是否相等; - 如果都相等,去重;否则,set() 认为二者不同,两个都保留到结果中。
通过重构__hash__和__eq__方法来自定义去重
class Student: def __init__(self,name,age,department): self.name =name self.age =age self.depatment =department def __hash__(self):
print('aaa') return hash(self.name+str(self.age)) # hash是调用的str类中的,hash之后就存储数据(桶球理论) def __eq__(self, other): # 要存入的地址中已有数据,则调用__eq__方法 print('bbb')
if self.name == other.name and self.age == other.age: return True # 返回True表示相等,不存入 def __str__(self): #无效 return self.name s1 = Student('matt',25,'01') s2 = Student('matt',25,'02') s3 = Student('logan',25,'02') print(set((s1,s2,s3)))
aaa;aaa;bbb;aaa {<__main__.Student object at 0x000000000244C7B8>, <__main__.Student object at 0x00000000024BA4A8>}
参考:
类似__eq__和__hash__原码解析
https://blog.csdn.net/anlian523/article/details/80910808
__eq__、__hash__与比较运算符解析
https://blog.csdn.net/q1403539144/article/details/94400722
set去重机理解析
https://blog.csdn.net/qq_41359051/article/details/91836100
五、反射
1. 反射基础
反射:通过字符串获取方法。
- hasattr(object, name) 判断对象object是否包含名为name的特性。
- getattr(object,name,default) 返回object的name属性值,若name不存在,则触发AttribetError异常或返回default值。
- setattr(object,name,method) setattr(x, 'y', v) 等效于 `` x.y = v '' (不常用)
- delattr(object,name) 删除name属性 (不常用)
object可以是实例、类、也可以是模块,其本质上就是一个命名空间,查找该命名空间下的name变量。
hasattr ()和 getattr() 都是一起用的
def bulk(self): print("%s is bulking。。。。。。"%self.name ) # bulk =12 class Dog(object): def __init__(self, name): self.name = name def eat(self, food): print("%s is eating......" % self.name, food) d = Dog("金毛") choice = input("请选择方法>>").strip() if hasattr(d, choice): # choice是字符串格式 func = getattr(d, choice) # 获取内存地址 func("狗粮") else: setattr(d, 'foo', bulk) # d.foo = bulk # print(d.__dict__) d.foo(d) #调用函数有些问题,在于self传值问题,此处添加的方法类似于静态函数staticmethod,不常用
请选择方法>>32
金毛 is bulking。。。。。。
另外:
class Foo:
def __init__(self):
self.name = 'wupeiqi'
def func(self):
return 'func'
@staticmethod
def bar():
return 'bar'
print(getattr(Foo, 'bar')) # delattr(Foo, 'func') # 注意字符串形式 del Foo.func # 与上式等效,注意是变量形式 print(Foo.__dict__) # 静态方法也能被查找 f = Foo() # delattr(Foo, 'func') ; def f.func # 此两种方法都会报错,因为f.__dict__没有func方法
{'__module__': '__main__', '__init__':
'bar':
2. 反射的应用
反射大致分为:对类反射,对实例反射,对本模块反射,对其他模块反射。
2.1 对本模块反射
import sys def func1(): print('aa') def func2(): print('bb') # file = sys.modules[__name__] # 与下式等效,注意__name__是变量,sys.moudles获取加载的所有模块 file = sys.modules['__main__'] # 获取本文件(模块)的名称(地址),类似os、sys等 print(file) getattr(file, 'func1')()
aa
六 函数和方法
1. 打印名称确认
def func(): pass class A: def func1(self): pass @staticmethod #实例的静态方法也是函数,不自动传递self def func2(self): pass a =A() print(func) print(A.func1) #一般不会采用 cls.function 的形式,因为这样不会自动传递self print(a.func1) #一般都是采用此种形式调用类中的方法,自动传递self print(a.func2) <function func at 0x0000000001FDC1E0> <function A.func1 at 0x000000000279AE18>method A.func1 of <__main__.A object at 0x000000000241C780>> <function A.func2 at 0x000000000279AD08>
2. types模块确认
from types import FunctionType from types import MethodType def func(): pass class A: def func1(self): pass a = A() print(isinstance(func,FunctionType)) print(isinstance(A.func1,FunctionType)) print(isinstance(a.func1,FunctionType)) print(isinstance(a.func1,MethodType)) True;True;False;True
3.两者区别
两者的区别在于是否主动传递self,function不会自动传递self,而method会自动传递