1 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
2 python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
四个可以实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
class Teacher: dic = {'查看老师信息':'show_teacher','查看学生信息':'show_studend'} def show_teacher(self): print('show teacher') def show_studend(self): print('show_studend') alex = Teacher() for k in Teacher.dic: print(k) key = input('you want: ') if hasattr(alex,Teacher.dic[key]): ret = getattr(alex,Teacher.dic[key]) #获取到内存地址 ret() #alex.show_studend()
#最重要的两个 hasattr getattr # class B:pass # class A(B):pass # a = A() # print(isinstance(a,A)) # print(isinstance('name',str)) # print(issubclass(A,B)) # # getattr() class A: price = 20 def fuc(self): print('in func') @classmethod def func2(cls): print('method of class') a = A() a.name = 'gkx' # 变量名 = input('>>> ') # print(getattr(a,变量名)) #print(a.__dict__[变量名]) #以下用 dict投机倒把而已 #A.__dict__[变量名](a) # print(getattr(A,'price')) # if hasattr(A,'func2'): # getattr(A,'func2')() #只要是 xx.yy 带点的,都可以反射 year = 2018 def wahaha(): print('wahahahaha') import sys print(__name__) #当__name__ 在本py运行的时候等于__main__ ,当被导入使用的时候,__name__等于 py文件名 print(sys.modules) print(sys.modules[__name__]) #获取本py文件的模块地址,然后就可以反射了, getattr(sys.modules[__name__],'wahaha')() #要用__name__ 此处不能用'__main__'这样这段代码就写死了,其他地方如果导入这个py,用不了这段代码 #只要是 xx.yy 带点的,都可以反射 #可以反射模块 import time # inp = input('>>> ') #输入time # print(getattr(time,inp)()) # inp2 = input('>>> ') #输入localtime # ret = getattr(time,inp2)() # print(ret) # inp3 = input('>>> ') #输入asctime # print(getattr(time,inp3)(ret)) #模块中的类也可以被我们拿到 import for_import print(getattr(for_import,'C').__dict__) c = getattr(for_import,'C')() c.name = 'gkx' print(c.__dict__) #不重要的 setattr 和 delattr # setattr 设置,修改属性 class A:pass a = A() setattr(A,'name','alex') setattr(a,'name','gkx') print(A.name) print(a.name) #对象里有name,我就先找 name,不找类属性 #delattr delattr(a,'name') #对象里的name被删除了,就去找类里的,输出为 alex print(a.name)
先了解这种编程思维,以后有机会可以来细究,双下方法用得好,有时候可以大大简化代码,及调高编程效率
__str__ __repr__ __del__ __call__
class B:pass class A(B): def __init__(self,name): self.name = name # def __str__(self): #这个方法一定要return一个字符串 # return "A 里面的 双下str" def __repr__(self): #这个方法一定要return一个字符串 return str(self.__dict__) a = A('gkx') print(a) #没当你打印对象的时候,就是调用这个对象的双下str方法(若无,就找父类object) a.__str__ print(str(a)) #此时调用的是 类A 父类 object里面的 __str__,它返回的是一个内存地址。 #如何证明呢? #此时如果 类A里有个双下方法__str__,它就会优先调用 类A里的 print('%s----%s'%('A',a)) # %s print(a) str(a) 都是在调用 a.__str__ #注意,此处针对的都是 class A。 print(str(a)) #调用的都是 双下repr,如果类中没有repr,就找父类要,父类没有继续找祖父类 object要 print('%r'%a) #!!!!!!特大八卦 # repr是str的备胎,当类A未定义双下str,但是类A中定义了 双下repr,此时调用str(a)就会调用类A中的双下 repr #但是反过来,如果类A中只有str,没有repr,则repr还是只会去找父类要双下repr #也就是说,str会去找类中的repr,repr只能自己找object #没str会找repr,没repr,只能回找boject #老师总结 # print(obj)/'%s'%obj/str(obj)的时候,实际上是内部调用了obj.__str__方法,如果str方法有,那么他返回的必定是一个字符串 # 如果没有__str__方法,会先找本类中的__repr__方法,再没有再找父类中的__str__。 # repr(),只会找__repr__,如果没有找父类的 #并不是所有内置方法都在object中,比如: #__len__就没有 class Classes: def __init__(self,name): self.name = name self.student = [] def __len__(self): #当去掉这个方法,下面再调用 len 会报错! return len(self.student) python_s9 = Classes('py_s9') python_s9.student.append('gkx') print(len(python_s9)) #__del__ # class A: # def __del__(self): # 析构函数: 在删除一个对象之前进行一些收尾工作 # self.f.close() # a = A() # a.f = open() # 打开文件 第一 在操作系统中打开了一个文件 拿到了文件操作符存在了内存中 # del a # a.f 拿到了文件操作符消失在了内存中 # del a # del 既执行了这个方法,又删除了变量 # 引用计数 # __call__ class A: def __init__(self,name): self.name = name def __call__(self): ''' 打印这个对象中的所有属性 :return: ''' for k in self.__dict__: print(k,self.__dict__[k]) a = A('alex')() #一个对象加上括号,就是调用了 双下call方法,如果类中没双下call,就会报错
python2中是调用 __unicode__
__hash__ __eq__
# __eq__方法 class A: def __init__(self,name): self.name = name def __eq__(self, other): if self.__dict__ == other.__dict__: return True else: return False a = A('gkx') a2 = A('gkx') print(a == a2) ##如果没有eq方法,是比较内存地址,会返回False # __hash__ class B: def __init__(self,name,age): self.name = name self.age = age def __hash__(self): return hash(self.name+self.age) #通过双下哈希,控制当属性一样哈希值是一样的 b = B('gkx','11') b1 = B('gkx','11') print(hash(b)) #在一个程序执行过程中,某个变量的哈希值一直不会改变 print(hash(b)) print(hash(b1))
__new__
# __new__ 构造方法,创建一个对象。在实例化的时候,就调用了object里的__new__创建了self # __init__ 初始化方法 class A: def __init__(self): self.x = 1 print('in the __init__ ') def __new__(cls, *args, **kwargs): print('in the __new__ ') return object.__new__(A,*args, **kwargs) a = A() #先打印 in the new 再打印 in the init
#23中设计模式 #单例模式 # 一个类 始终 只有 一个 实例 # 当你第一次实例化这个类的时候 就创建一个实例化的对象 # 当你之后再来实例化的时候 就用之前创建的对象 class B: __isinstance = False def __init__(self,name,age): self.name = name self.age = age def __new__(cls, *args, **kwargs): if cls.__isinstance: return cls.__isinstance cls.__isinstance = object.__new__(cls) return cls.__isinstance aa = B('gkx',11) aa.cloth = '1111111' bb = B('ww',22) print(aa,bb) #内存空间是一模一样的 print(aa.name) print(bb.name) #所以name的值根据后面一个实例化而得 print(bb.cloth)
__getitem__ __setitem__ __delitem__
class Foo: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def __getitem__(self, item): if hasattr(self,item): #判断self,这个对象里是否有 item这个属性 print(self.__dict__[item]) def __setitem__(self, key, value):#类似字典的新增 self.__dict__[key] = value #print(key,value) def __delitem__(self, key): del self.__dict__[key] f = Foo('gkx',12,'male') f['name'] #对于 双下getitem,当 obj[item]这种格式的时候,中括号里的值,自动传入方法内的参数 item,语法就是这么规定的 f['hobby'] = 'ww' #类似字典的新增,语法规定,hobby传给key ,ww传给value print(f.__dict__) # del f.hobby #原生删除方法 del f['hobby'] print(f.__dict__)
我们在使用内置函数的时候,很多时候都是调用内置双下方法
内置函数 内置模块 内置数据类型 其实和双下方法有着千丝万缕的关系:但是只能在以后学习工作中慢慢积累,
我们来看看下面这两个练习:
练习一:
在这个纸牌游戏中,利用双下方法,就定义了纸牌游戏,不用自己写方法
同时了解到使用 random模块的时候,choice需要用到__len__获取长度, shuffle需要用到 __setitem__ 乱序的时候需要获取到 index
import json from collections import namedtuple Card = namedtuple('card',['rank','suit']) ranks = [str(n) for n in range(2,11)] + list('JQKA') suits = ['红心','黑桃','梅花','方块'] class Franchdeck: def __init__(self): self._card = [Card(rank,suit) for rank in ranks # for suit in suits: for suit in suits] #for rank in ranks: #在推导式中写后面的放前面 def __getitem__(self, item): # item就是你在类外想调用的index,在字典中就是key return self._card[item] def __len__(self): #在导入random模块的时候,使用choice和shuffle需要用到双下len return len(self._card) def __setitem__(self, key, value): #乱序的时候需要setitem,是因为乱序需要用到索引,把索引打乱,但是对应的值不能错 self._card[key] = value def __str__(self): return json.dumps(self._card,ensure_ascii=False) # ensure_ascii=False 取消中文转义 #return str(self._card) # 这样直接打印 print(deck)就不是内存地址了 str(deck) %s deck = Franchdeck() print(deck[0]) import random print(random.choice(deck)) random.shuffle(deck) print(deck[:5]) print(deck) #可以获取整个列表的值,上面的切片就是从这里切的 #以下没用,自己瞎写的 # c1 = Card(2,'红心') # print(c1) # print(c1.rank) # print(getattr(c1,'rank')) # print(c1[1]) # # lst = [] # dic1 = {} # ranks = [str(n) for n in range(2,11)] + list('JQKA') # for suit in ['红心','黑桃','方块','梅花']: # for rank in ranks: # lst.append(Card(rank,suit)) # print(lst) # print(dic1)
练习二:
set集合去重,依赖__eq__ 以及 __hash__
class A: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def __eq__(self, other): if self.name == other.name and self.sex == other.sex: return True return False def __hash__(self): return hash(self.name+self.sex) # def __str__(self): # return str(self.__dict__) a = A('egg',11,'男') a2 = A('egg',12,'男') print(set([a,a2])) p_lst = [] for i in range(84): p_lst.append(A('egg',i,'男')) print(p_lst) print(set(p_lst)) # for i in p_lst: #关于读取内存地址内容,如果是函数就直接加括号执行,把内存地址等价于变量名即可 # print(i.name)