反射
反射是指程序可以访问、检测和修改它自身状态和行为的一种能力(自省)
python面向对象中的反射:通过字符串的形式操作对象相关的属性,为什么是字符串呢?因为很多时候接收到的数据是字符串,如果没有反射,我们很难通过该数据来获取对象的属性或者方法。
python中的反射是通过四个内置函数来实现的,只要能通过对象名. 的形式访问到的属性都可以通过反射来访问到。可以应用反射的对象有实例对象、类、其他模块和本模块
1 class Person(object): 2 country = "China" 3 4 def __init__(self, name, age): 5 self.name = name 6 self.age = age 7 8 def say_hi(self): 9 print("hi, %s" % self.name)10 11 12 p1 = Person("小鱼", 21)13 14 # 检测是否含有某个属性15 print(hasattr(p1, "name")) # True16 print(hasattr(p1, "say_hi")) # True17 18 # 获取属性19 print(getattr(p1, "age")) # 2120 func = getattr(p1, "say_hi")21 func() # hi, 小鱼22 23 # 获取不到时可以设置返回值24 g = getattr(p1, "gender") # 报错25 g = getattr(p1, "gender", "不存在...")26 print(g) # 不存在...27 28 # 设置属性29 setattr(p1, "gender", "女")30 setattr(p1, "show_name", lambda self: self.name+"姑娘") # 设置方法31 print(p1.__dict__) # {'name': '小鱼', 'age': 21, 'gender': '女', 'show_name':at 0x0000016E4F271E18>}32 print(p1.show_name(p1)) # 小鱼姑娘33 34 35 # # 删除属性36 delattr(p1, "age")37 delattr(p1, "show_name")38 delattr(p1, "show_name111") # AttributeError: show_name11139 40 print(p1.__dict__) # {'name': '小鱼'}
1 class Company(object): 2 start_time = "2017-05-12" 3 4 def __init__(self): 5 self.name = "ABS" 6 7 def func(self): 8 return "func" 9 10 @staticmethod11 def bar():12 return "bar"13 14 15 print(getattr(Company, "start_time")) # 2017-05-1216 print(getattr(Company, "func")) #17 print(getattr(Company, "bar"))
# attr模块内容flag = Truedef func(a): print(a) return a+3class B: name_list = ["Tom", "Robin", "Julia"] def __init__(self, name, gender): self.name = name self.gender = gender def func(self): print(self)import attr# 1.找到func并执行# 2.调用B类并调用name_list func函数# 3.找到B类实例化对象再找name, name_list func# 1.找到func并执行getattr(attr, "func")(7) # 7# 2.调用B类并调用name_list func函数ret = getattr(attr, "B")print(getattr(ret, "name_list")) # ['Tom', 'Robin', 'Julia']getattr(ret, "func")(111) # 111# 3.找到B类实例化对象再找name, name_list funcb1 = ret("小春", "男")print(getattr(b1, "name")) # 小春print(getattr(b1, "name_list")) # ['Tom', 'Robin', 'Julia']getattr(b1, "func")() #
1 import sys 2 3 4 def func1(): 5 print(666) 6 7 8 def func2(): 9 print(888)10 11 12 obj = sys.modules[__name__]13 ret = input(">>>")14 getattr(obj, ret)()
练习
1 import sys 2 obj = sys.modules[__name__] 3 4 5 def func1(): 6 print("in func1") 7 8 9 def func2():10 print("in func1")11 12 13 def func3():14 print("in func1")15 16 17 func_list = ["func%s" % i for i in range(1, 4)]18 # print(func_list)19 20 for func in func_list:21 getattr(obj, func)()
小结
1. 反射就是通过字符串来操作(访问,设置,删除)对象属性
2. 反射有四种方法:hasattr, getattr, setattr, delattr
3. 反射用法为:getattr(obj, "属性名"), obj为对象名,包括类,属性名包括静态属性名和方法名
类的特殊成员
__len__
我们知道str, list, dic等数据类型可以使用len方法,这是因为这几个类里有__len__方法,当我们使用len()时,会自动触发类里面的__len__方法,从而返回对象的长度。但是现在有一个问题,如果我们自己定义了一个类,我们想要计算实例对象的长度,该怎么做呢?我们可以自己定义一个__len__方法,然后我们调用len()时就可以自动触发__len__方法
1 class A: 2 3 def __init__(self, name, age, gender): 4 self.name = name 5 self.age = age 6 self.gender = gender 7 8 def func1(self): 9 pass10 11 def func2(self):12 pass13 14 def __len__(self):15 count = 016 for i in self.__dict__:17 count += 118 return count # return len(self.__dict__)19 20 21 a = A("julia", 19, "女")22 # print(a.__dict__)23 print(len(a)) # 3
__hash__
1 class Computer: 2 date_of_manufacture = "2017-09-23" 3 4 def __init__(self, brand, type_name): 5 self.brand = brand 6 self.type_name = type_name 7 8 9 c1 = Computer("xiaomi", "pro")10 print(hash(c1)) # -9223371910015235040
我们定义的类里并没有__hash__方法,但是为什么也可以hash呢?这是因为Computer继承的object类里面有__hash__方法,我们可以通过查看源码验证这一点
1 class Computer: 2 date_of_manufacture = "2017-09-23" 3 4 def __init__(self, brand, type_name): 5 self.brand = brand 6 self.type_name = type_name 7 8 def __hash__(self): 9 return 66610 11 12 c1 = Computer("xiaomi", "pro")13 print(hash(c1)) # 666
__str__
如果一个类中定义了__str__方法,那么打印在打印对象时,默认输出该方法的返回值
class School: def __init__(self, name, location): self.name = name self.location = location def __str__(self): return "华南施工大学"scut = School("华南理工大学", "五山")print(scut) # 华南施工大学
__repr__
如果一个类中定义了__repr__方法,那么在repr(对象) 时,默认输出该方法的返回值。
1 class Language: 2 def __init__(self): 3 pass 4 5 def __repr__(self): 6 return "{1: 'a', 2: 'b'}" 7 8 9 l1 = Language()10 print(l1) # {1: 'a', 2: 'b'}11 print(repr(l1)) # {1: 'a', 2: 'b'}
__call__
对象后面加括号,触发执行__call__,通俗点说,__call__就是把一个python对象变成可调用的
class Person: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def __call__(self): print("My name is %s" % self.name) print("I'm %s years old" % self.age)obj = Person("julia", 9, "女")obj() # My name is julia I'm 9 years old
__eq__
1 class A: 2 def __init__(self): 3 self.a = 1 4 self.b = 2 5 6 def __eq__(self, other): 7 if self.a == other.a and self.b == other.b: 8 return True 9 10 11 obj1 = A()12 obj2 = A()13 # obj1 == obj2会自动触发__eq__方法,将==右边的对象传进去14 print(obj1 == obj2) # True
__del__
析构方法,当对象在内存中释放时,自动触发执行
此方法无需定义,它的调用是由解释器在进行垃圾回收时自动触发执行的
__new__
实例化对象的三个步骤
# 1. 在内存中开辟一个对象空间(__new__开辟的—)# 2. 自动执行__init__方法,将空间传给self# 3. 在__init__中给对象封装属性
我们可以通过代码来验证此执行顺序 ,我们自己定义一个__new__方法,看看结果怎么样
1 class A: 2 def __init__(self, name): 3 self.name = name 4 print("in A __init__") 5 6 # 自己定义一个__new__方法 7 def __new__(cls, *args, **kwargs): 8 print(111) 9 10 11 a = A("xiaochun")12 print(a.name) # AttributeError: 'NoneType' object has no attribute 'name'
为什么会报错呢,因为遇到A()时,解释器会去A的类里面找__new__方法,找到了就执行这个方法来开辟内存空间,但这个__new__方法是个假的方法,实际上开辟不了内存空间,因此最后返回给a的是一个None,因而也就会报错了
print(a) # None
那么有没有办法自己模拟一个办法,有的
1 class A: 2 def __init__(self, name): 3 self.name = name 4 print("in A __init__") 5 6 def __new__(cls, *args, **kwargs): 7 print("in A __new__") 8 return object.__new__(cls) # 注意这里一定要传入一个类名cls,表明这个实例是由哪个类创建的 9 10 11 a = A("xioachun")12 print(a.name)
执行结果
in A __new__in A __init__xioachun
in A __new__在in A __init__前面,可以说明__new__是在__init__前面执行的。
利用__new__可以是实现单例模式
单例模式
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。【采用单例模式动机、原因】对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。【单例模式优缺点】【优点】一、实例控制单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。二、灵活性因为类控制了实例化过程,所以类可以灵活更改实例化过程。【缺点】一、开销虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。二、可能的开发混淆使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。三、对象生存期不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用单例模式具体分析
1 class ThreebodyRole: 2 __instance = None 3 4 def __init__(self, name, gender, age): 5 self.name = name 6 self.gender = gender 7 self.age = age 8 9 def __new__(cls, *args, **kwargs):10 if cls.__instance is None:11 obj = object.__new__(cls)12 cls.__instance = obj13 return cls.__instance # 不为空,就返回这个对象14 15 16 ret1 = ThreebodyRole("云天明", "男", 25)17 print(ret1.name) # 云天明18 print(ret1) # <__main__.A object at 0x0000023DA925C9B0>19 ret2 = ThreebodyRole("章北海", "男", 26)20 print(ret2.name) # 章北海21 print(ret2) # <__main__.A object at 0x0000023DA925C9B0>22 ret3 = ThreebodyRole("艾AA", "女", 22)23 print(ret3.name) # 艾AA24 print(ret3) # <__main__.A object at 0x0000023DA925C9B0>25 print(ret3.name) # 艾AA 把之前对象的覆盖都给覆盖掉了
__item__
1 class Foo: 2 def __init__(self, name): 3 self.name = name 4 5 def __getitem__(self, item): 6 print(self.__dict__[item]) 7 8 def __setitem__(self, key, value): 9 self.__dict__[key] = value10 11 def __delitem__(self, key):12 print("del obj[key]时,我执行")13 self.__dict__.pop(key)14 15 def __delattr__(self, item):16 print("del obj.key时,我执行")17 self.__dict__.pop(item)18 19 20 f1 = Foo("程心")21 f1["age"] = 1822 print(f1.__dict__) # {'name': '程心', 'age': 18}23 f1["age1"] = 1924 print(f1.__dict__) # {'name': '程心', 'age': 18, 'age1': 19}25 26 del f1.age1 # del obj.key时,我执行27 # del f1["age"]28 print(f1.__dict__) # {'name': '程心', 'age': 18}29 30 del f1["age"] # del obj[key]时,我执行31 print(f1.__dict__) # {'name': '程心'}32 33 f1["name"] = "大史"34 print(f1.__dict__) # {'name': '大史'}