反射(reflection)
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
python中反射机制的实现很简单,提供了几个方法
'''
内置函数hasattr、getattr、setattr、delattr的使用
'''
class Teacher:
def __init__(self,name,age):
self.name =name
self.age =age
t=Teacher('Libai',12)
# hasattr(object,'name')
hasattr(t,'name') # 判断有无属性t.name,返回true和false
# getattr(object, 'name', default=None)
getattr(t,'name',None) # 存在属性name返回属性值,不存在则返回默认值None
# setattr(object, 'name', value)
setattr(t,'age',18) # 设置属性值t.age=18
# delattr(object, 'name')
delattr(t,'age') # 等同于del t.age,删除该属性,属性不存在时会报错
上面的属性实际上是字符串,这是有原因的,在进行交互时,计算机收到的数据一般是字符串形式,这样的设计有助于我们进行交互操作
除此之外,内置函数dir(object)能够获取类或者对象的属性列表,列表中全为字符串格式
有必要说一下学会了反射之后会有什么变化
# 学习反射之前我们如何处理判断
class User:
def login(self):
print('欢迎来到登录页面')
def register(self):
print('欢迎来到注册页面')
while 1:
choose = input('>>>').strip()
if choose == 'login':
obj = User()
obj.login()
elif choose == 'register':
obj = User()
obj.register()
# 学习反射之后:
class User:
def login(self):
print('欢迎来到登录页面')
def register(self):
print('欢迎来到注册页面')
user = User()
while 1:
choose = input('>>>').strip()
if hasattr(user,choose):
func = getattr(user,choose)
func()
else:
print('输入错误!!!')
类的内置方法
class People:
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return '<姓名:%s 年龄:%s>' %(self.name,self.age)
'''
需要注意的是,str方法返回值必须是字符串
双下划线方法能够在满足条件时自动执行,str方法能够在执行str函数或者print方法时打印返回值
p=People('lilei',18)
print(p) # 触发p.__str__(),将返回值打印出来
<Name:lili Age:18>
'''
# 同理del方法是在del 对象时自动触发
class People:
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return '<姓名:%s 年龄:%s>' %(self.name,self.age)
p=People('lilei',18)
del p # 触发p.__del__()
其他的方法介绍:
# __repr__方法:
1.在执行repr()方法或者是交互式解释器的时候会打印其返回值
2.如果str方法没有被定义,那么打印时也会打印repr的返回值
3.返回值也必须是字符串,否则会报错
def __repr__(self):
return '返回repr'
# __del__方法:一般用不上,因为python自带的垃圾回收机机制能有效地回收内存
1.执行del对象时触发内部代码
def __del__(self):
print('执行该部分的代码')
# __len__方法:
1.执行len(对象)时执行内部代码
class A:
def __init__(self):
self.a = 1
self.b = 2
def __len__(self):
return len(self.__dict__)
a = A()
print(len(a))
# __new__方法:
1.只有继承于object的新式类才能有__new__方法,__new__方法在创建类实例对象时由Python解释器自动调用,一般不用自己定义,Python默认调用该类的直接父类的__new__方法来构造该类的实例,如果该类的父类也没有重写__new__,那么将一直按此规矩追溯至object的__new__方法
2.__new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供;__new__必须要有返回值,返回实例化出来的实例,可以return父类new出来的实例,或直接是object的new出来的实例。
class Person(object):
def __init__(self):
print("__init__")
self.name="张三"
def __new__(cls):
print('__new__')
ob = object.__new__(cls)#ob为Person实例对象
print(ob)
return ob
p1=Person()
3.p1=Person()该语句主要做了以下工作:
首先调用Person的__new__方法,该方法通过object.new(cls)创建了Person实例对象,并返回。最后调用了该Person实例对象的__init__方法进行初始化
4.简单的可以理解为父类实例化时得到该类,然后该类进行init初始化,如果该类本身返回的并不是父类实例化的列本身,那么将不会进行实例化
class Dog(object):
def __init__(self):
self.name="旺财"
print("__init__")
class Person(object):
def __init__(self):
self.name="张三"
print("__init__")
def __new__(cls):
print('__new__')
ob = object.__new__(Dog) # 父类实例化的是Dog类,不会执行People类的init方法
ob.__init__()
return ob
# __call__方法:
1.在我们进行实例化时,通常是在类后面加上括号,然后直接在对象中进行实例化,实际上这与p1.__call__("李白")是一样的,也就是说,实例化时执行的顺序是new、call、init
class People:
# 定义__call__方法
def __call__(self,name):
print("调用__call__()方法",name)
p1 = People()
p1("李白")
p1.__call__("李白")
# call方法弥补hasattr的不足
hasattr() 函数能够查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法
要解决这个问题,我们可以借助可调用对象的概念,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是
class People:
def __init__ (self):
self.name = "李白"
def say(self):
print("你好")
p1 = People()
if hasattr(p1,"name"):
print(hasattr(p1.name,"__call__")) # false
if hasattr(p1,"say"):
print(hasattr(p1.say,"__call__")) # true
# __item__系列(了解一下就行)
class Fool:
def __init__(self,name):
self.name=name
def __getitem__(self, item):
print(self.__dict__[item])
def __setitem__(self, key, value):
self.__dict__[key]=value
def __delitem__(self, key):
print('del obj[key]时,我执行')
self.__dict__.pop(key)
def __delattr__(self, item):
print('del obj.key时,我执行')
self.__dict__.pop(item)
f1=Fool('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)
# 上下文管理器相关__enter__ __exit__
class A:
def __init__(self, text):
self.text = text
def __enter__(self): # 开启上下文管理器对象时触发此方法
self.text = self.text + '您来啦'
return self # 将实例化的对象返回f1
def __exit__(self, exc_type, exc_val, exc_tb): # 执行完上下文管理器对象f1时触发此方法
self.text = self.text + '拜拜'
with A('大爷') as f1:
print(f1.text)
print(f1.text)
自定义文件管理器
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('py_ana.py','r') as f:
for i in f:
print i
下面的内容就当拓展了:hash和eq方法
__eq__和__hash__方法:
判断(==)时实际上比较的是__eq__里面的内容
class Item:
def __init__(self, val):
self.val = val
def __eq__(self, other):
return self.val == other.val
first = Item('hello')
second = Item('hello)
print(first == second) # True
如果不实现__eq__方法,那么自定义类型会调用默认的__eq__方法, 通过默认方法进行比较的相等条件相当严格,只有自己和自己比才会返回True
class Item:
def __init__(self, val):
self.val = val
first = Item('hello')
second = Item('hello')
print(first == second) # False
因此,在需要进行自定义类型比较的时候,建议实现__eq__方法
谈及__eq__方法,就不得不说一下__hash__方法,也就是我们常说的哈希,什么是哈希呢,下面是官方文档的解释:
如果一个对象在其生命周期内有一个固定不变的哈希值 (这需要__hash__()方法) 且可以与其他对象进行比较操作 (这需要__eq__()方法) ,那么这个对象就是可哈希对象 (hashable) 。可哈希对象必须有相同的哈希值才算作相等。
由于字典 (dict) 的键 (key) 和集合 (set) 内部使用到了哈希值,所以只有可哈希 (hashable) 对象才能被用作字典的键和集合的元素。
所有python内置的不可变对象都是可哈希的,同时,可变容器 (比如:列表 (list) 或者字典 (dict) ) 都是不可哈希的。用户自定义的类的实例默认情况下都是可哈希的;它们跟其它对象都不相等 (除了它们自己) ,它们的哈希值来自id()方法。
Python中的对象分为可变和不可变对象,我们从另一个角度来看,可以分为不可哈希对象和可哈希对象。而不可哈希对象无法作为哈希集合的元素使用(这个hashable collections值得是set、frozenset和dict)。有时候,我们使用列表或者自定义对象作为字典的键,或者使用set进行元素去重的时候,会遇到unhashable type: xxx之类的问题,这类问题出现的原因就是字典的键或者集合中的元素类型为不可哈希类型。
下面讲述四种情况,深入理解一下两者的区别:
1. 只自定义__eq__
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
print(self.name, '使用了__eq__方法')
return self.__dict__ == other.__dict__
person1 = Person('A', 20)
person2 = Person('B', 20)
person3 = Person('C', 30)
person4 = Person('A', 20)
print(person1 == person4) # A使用了__eq__方法 true
print(person2 == person3) # B使用了__eq__方法false
上面的例子说明,对象在进行双等号比较时,会调用内部的__eq__方法进行比较
2. 重写__eq__()方法不重写__hash__()方法,那么这个类无法作为哈希集合的元素使用(这个hashable collections指的是set、frozenset和dict)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
print(self.name, '使用了__eq__方法')
return self.__dict__ == other.__dict__
person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
set1 = {person1, person2, person3, person4} # 内部元素要求是可哈希对象,简单的说就是__hash__方法有返回值
print(set1)
# 结果报错,因为重写__eq__()方法后会默认把__hash__赋为None(文档上面有说)
class A:
def __eq__(self, other):
pass
a = A()
print(a.__hash__) # None
hash(a)
# TypeError: unhashable type: 'A'
3. 如果自定义类重写了__hash__()方法没有重写__eq__()方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __hash__(self):
print(self.name, '使用了__hash__方法')
return hash(self.name)
person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
set1 = {person1, person2, person3, person4}
print(set1)
不报错,但不合理———person1与person4是同一个人,却都被添加到集合,违背了集合(元素不能有重复)的理念:
但是,为什么person4也会被添加进去呢,主要是因为当发现hash出的值相同时,就需要__eq__进行下一步判断。我们重写了__hash__却没重写__eq__,默认调用了object的__eq__,而默认的__eq__比较的是对象地址,person1与person4地址不同,所以将最后的person4加到了集合
4. hash与eq都重写
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __hash__(self):
print(self.name, '使用了__hash__方法')
return hash(self.name)
def __eq__(self, other):
print(self.name, '使用了__eq__方法')
return self.__dict__.get("name") ==
person1 = Person('zs', 20)
person2 = Person('ls', 20)
person3 = Person('ww', 30)
person4 = Person('zs', 20)
set1 = {person1, person2, person3, person4}
print(set1) # 报错,person1和person4是一样的不能被添加到集合中
原理:因为要加入集合,所以每个person都会调用__hash__,比较每个对象内容中的name属性。当hash(self.name)值不同时,name肯定不同,所以就不会再调用,而当hash(self.name)值相同时,我们不能盲目认为不能加入集合,因为不同的name可能hash出一样的值(算法内部可能会出现相同),这时候就会再调用我们重写的__eq__帮忙来进一步判断所有内容是否相同了
'''
总结:
eq方法是执行双等号判断时自定触发,比较里面的值,返回true或false
hash方法是判断是不是可哈希对象,能否作为集合或者字典的键
只定义eq方法时会自动将hash返回None(即变成不可哈希对象)
只定义hash时,虽然是可哈希对象,但是当当两个对象的哈希值一样时,会自动调用对象的eq方法进行进一步判断,eq结果不等时依旧会被判定为可哈希对象,作为集合或字典的键
'''