Python面向对象之反射、元类、异常处理
一、反射
反射指的是一个对象应该具备,可以增、删、改、查属性的能力,通过字符串来操作属性。涉及四个函数,这四个函数就是普通的内置函数,没有下划线,但实现的功能和原理基本一致
hasattr(object,name) # 判断对象是否实现某个属性,返回值是bool类型 setattr(object,name,value) # 为对象增加新的属性 getattr(object,name,default) # 从对象中获取某个属性,返回值是str类型 delattr(object,) # 从对象中删除某个属性,无返回值
class Person: def __init__(self,name,age,gender): self.name=name self.age=age self.gender=gender p=Person("aaa",20,"woman") # print(p.name) print(hasattr(p,"name")) # 判断是否是类的属性 True if hasattr(p,"name"): print("我有name") print(getattr(p,"name",None)) #从对象中取出属性,第三个值位默认值 aaa # 当属性不存在是返回默认值 # 为对象添加新的属性 a = setattr(p,"id","1234") print(a) # 从对象中删除对象 c = delattr(p,"id") print(c)
反射使用:
1、一个类在定义的时候,可能一些属性的设计并不是很完美,而后期需要作出修改过或删除操作属性时,使用反射可以不需要修改源代码。反射其实就是对属性的增删改查,但如果直接使用内置的__dict__来操作,语法繁琐不便操作。
2、另一个就是调用另一方提供的对象时,必须先判断这个对象是否满足需求,也就是判断是否是我们需要的属性和方法。动态添加模块功能。框架就与代码实现了彻底的耦合
二、元类 metaclass
元类是创建类的类 所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化)。元类即 用于产生类的类。默认情况下所有类的元类都是type
1、编写元类:
只需要声明一个继承自type的类(使用class关键字)
类只是对象,元类也只是类。元类的行为继承自type;因此任何type的子类都可以作为元类
实例:例如控制类的名字必须以大驼峰体的方式来书写。用初始化的方法 我们只要找到类对象的类(元类),覆盖其中__ init__方法就能实现需求。不能修改源代码,所以应该继承type来编写自己的元类,同时覆盖__init__来完成需求。
# 定义一个元类 class MyType(type): def __init__(self,clss_name,bases,dict): #继承后用super调用 super().__init__(clss_name,bases,dict) print(clss_name,bases,dict) if not clss_name.istitle(): raise Exception("类名写错了~") class pig (metaclass=MyType): print("绑定了元类~") class Duck(metaclass=MyType): print("规定的协议要遵守~") MyType("pig",(),{})
2、元类中的__call__方法
当调用类对象时会自动执行元类中的__call__方法,并将这个类本身作为第一个参数传入,以及后面的数,覆盖元类中的__call__之后,这个类无法产生对象,必须调用super().__call__来完成对象的创建,并返回其返回值。
#实现将对象的所有属性名称转化为大写 class MyType(type): def __call__(self, *args, **kwargs): new_arg = [] # 要求的书写规范 for a in kwargs: new_arg.append(a.upper()) # 转换为大写 print(new_arg) #print(kwargs) return super().__call__(*new_arg) class Person(metaclass=MyType): def __init__(self,name,gender): self.name=name self.gender=gender p=Person(name="aaa",gender="man") print(p.gender) print(p.name) ''' ['NAME', 'GENDER'] GENDER NAME '''
3、__new__方法
创建类的对象时,会先执行元类中的__new__ 方法。执行了__new__
函数,就不会再执行__init__
,因为__new__
函数是真正用于创建类的方法,只有创建类成功了才会执行__init__函数,__new__必须要有返回值且返回值类型为__type__
时才会执行__init__
函数,
class Meta(type): def __new__(cls, *args, **kwargs): print(cls) # 元类自己 print(args) # 创建类需要的几个参数 类名,基类,名称空间 print(kwargs) #空的 print("new run") # return super().__new__(cls,*args,**kwargs) obj = type.__new__(cls,*args,**kwargs) print(obj) #return obj #无返回值,不执行init方法 def __init__(self,a,b,c): super().__init__(a,b,c) print("init run") #有返回值就执行init run class A(metaclass=Meta): pass print(A) #此时有返回值执行init方法,就执行这个 ''' ('A', (), {'__module__': '__main__', '__qualname__': 'A'}) {} new run init run '''
4、单例
单例是指的是单个实例,指一个类只能有一个实例对象。为了节省资源,当两个对象的数据完全相同时 则没有必要占用两份资源。
# 单例n元类 class Single(type): def __call__(self, *args, **kwargs): if hasattr(self,"obj"): #判断是否存在已经有的对象 return getattr(self,"obj") # 有就返回 obj = super().__call__(*args,**kwargs) # 没有则创建 print("这下有了") self.obj = obj # 并存入类中 return obj class Student(metaclass=Single): def __init__(self,name): self.name = name class Person(metaclass=Single): pass # 只会创建一个对象 Person() Person()
四、冒泡算法:冒泡排序
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
def bubbleSort(arr): n = len(arr) # 遍历所有数组元素 for i in range(n): print(i) for j in range(0, n - i - 1): if arr[j] > arr[j + 1]: arr[j], arr[j + 1] = arr[j + 1], arr[j] arr = [64, 34, 25, 12, 22, 11, 90] #即用64与34比较,交换二者位置,再与25比较又交换位置,以此类推 bubbleSort(arr) print("排序后的数组:") for i in range(len(arr)): print("%d" % arr[i])
五、异常处理
1、异常是程序发生错误的信号,程序一旦出错就会抛出异常,程序的运行随即终止。为了增强程序的健壮性,即便是程序运行过程中出错了,也不要终止程序。而是捕捉异常并处理:将出错信息记录到日志内
2、异常的处理
2.1语法上的错误SyntaxError,
if 1>3 print("run...")
2.2逻辑上的错误
2.2.1错误发生的条件是可以预知的,使用if判断来解决
age=input('>>: ').strip() # 输入的只要不是数字就会出错 # if age.isdigit(): # age=int(age) # if age > 18: # print('猜大了') # elif age < 18: # print('猜大了') # else: # print('猜对了') # else: # print('必须输入数字')
2.2.2错误发生的条件是无法预知的
语法:
# print('start...') # try: # # 有可能会抛出异常的代码 # 子代码1 # 子代码2 # 子代码3 # except 异常类型1 as e: # pass # except 异常类型2 as e: # pass # ... # else: # 如果被检测的子代码块没有异常发生,则会执行else的子代码 # finally: # 无论被检测的子代码块有无异常发生,都会执行finally的子代码 # # print('end...')
用法(五种):
# # 用法一: # print('start...') # # try: # print('1111111111') # l=['aaa','bbbb'] # l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行 # print('2222222222') # xxx # print('33333333') # dic={'a':1} # dic['a'] # except IndexError as e: # print('异常的信息: ',e) # # print('end....') # # 用法二: # print('start...') # # try: # print('1111111111') # l=['aaa','bbbb'] # # l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行 # print('2222222222') # xxx # print('33333333') # dic={'a':1} # dic['a'] # except IndexError as e: # print('异常的信息: ',e) # except NameError as e: # print('异常的信息: ',e) # # print('end....') # # 用法三: # print('start...') # # try: # print('1111111111') # l = ['aaa', 'bbbb'] # l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行 # print('2222222222') # xxx # print('33333333') # dic = {'a': 1} # dic['aaa'] # # except (IndexError, NameError) as e: # # print('异常的信息: ', e) # # except KeyError as e: # # print('字典的key不存在: ', e) # except Exception as e: # 万能异常 # print('所有异常都可以匹配的到') # print('end....') # # 用法四:else不能单独与try配合使用,必须要搭配except # print('start...') # # try: # print('1111111111') # print('2222222222') # print('33333333') # except Exception as e: # 万能异常 # print('所有异常都可以匹配的到') # else: # print('====>') # # print('end....') # 用法五:finally可以单独与try配合使用 print('start...') try: print('1111111111') l = ['aaa', 'bbbb'] l[3] # 抛出异常IndexError,该行代码同级别的后续代码不会运行 print('2222222222') xxx print('33333333') dic = {'a': 1} dic['aaa'] finally: # 不处理异常,无论是否发生异常都会执行finally的子代码 print('把被检测代码中回收系统资源的代码放到这里') print('end....')
3、异常处理之断言assert
Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。断言可以在条件不满足程序运行的情况下直接返回错误,而不必等待程序运行后出现崩溃的情况
#语法格式如下: assert expression 等价于: if not expression: raise AssertionError #assert 后面也可以紧跟参数: assert expression [, arguments] 等价于: if not expression: raise AssertionError(arguments) #assert 使用实例: assert 1==2, '1 不等于 2' Traceback (most recent call last): File "", line 1, in AssertionError: 1 不等于 2
使用:
mathmark = int(input()) #断言mathmark是否位于正常范围内 assert 0 <= mathmark <= 100 #只有当 mathmark 位于 [0,100]范围内,程序才会继续执行 print("分数为:",mathmark)
4、异常处理之Traceback
4.1sys.exc_info和traceback object
Python程序的traceback信息均来源于一个叫做traceback object的对象,而这个traceback object通常是通过函数sys.exc_info()来获取的
4.2traceback object里面可以通过traceback module来打印和格式化traceback的相关信息,traceback module的相关函数。
4.2.1 print_tb
print_tb(tb, limit=None, file=None):
tb: 这个就是traceback object, 是我们通过sys.exc_info获取到的
limit: 这个是限制stack trace层级的,如果不设或者为None,就会打印所有层级的stack trace
file: 这个是设置打印的输出流的,可以为文件,也可以是stdout之类的file-like object。如果不设或为None,则输出到sys.stderr。
import sys import traceback def func1(): raise NameError("func1 exception") def main(): try: func1() except Exception as e: exc_type, exc_value, exc_traceback_obj = sys.exc_info() traceback.print_tb(exc_traceback_obj) if __name__ == '__main__': main() ''' 结果为 File "", line 11, in main func1() File " '''", line 6, in func1 raise NameError("--func1 exception--")
4.4.2print_exception
def print_exception(etype, value, tb, limit=None, file=None, chain=True):
跟print_tb相比多了两个参数etype和value,分别是exception type和exception value,加上tb(traceback object),正好是sys.exc_info()返回的三个值
另外,与print_tb相比,打印信息多了开头的"Traceback (most...)"信息以及最后一行的异常类型和value信息
还有一个不同是当异常为SyntaxError时,会有"^"来指示语法错误的位置
import sys import traceback def func1(): raise NameError("func1 exception") def func2(): func1() def main(): try: func2() except Exception as e: exc_type, exc_value, exc_traceback_obj = sys.exc_info() traceback.print_exception(exc_type, exc_value, exc_traceback_obj, limit=2, file=sys.stdout) if __name__ == '__main__': main() #输出: ''' Traceback (most recent call last): File "", line 13, in main func2() File " '''", line 9, in func2 func1() NameError: --func1 exception--
4.4.3 print_exc
def print_exc(limit=None, file=None, chain=True):
import sys import traceback def func1(): raise NameError("--func1 exception--") def func2(): func1() def main(): try: func2() except Exception as e: traceback.print_exc(limit=1, file=sys.stdout) if __name__ == '__main__': main() #输出(由于limit=1,因此只有一个层级被打印出来): ''' Traceback (most recent call last): File "", line 13, in main func2() NameError: --func1 exception-- '''
4.4.4 format_exc
def format_exc(limit=None, chain=True):
import logging import sys import traceback logger = logging.getLogger("traceback_test") def func1(): raise NameError("--func1 exception--") def func2(): func1() def main(): try: func2() except Exception as e: logger.error(traceback.format_exc(limit=1, file=sys.stdout)) if __name__ == '__main__': main()
以上实例:获取一个线程的异常信息
import threading import traceback def my_func(): raise BaseException("thread exception") class ExceptionThread(threading.Thread): def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, verbose=None): """ Redirect exceptions of thread to an exception handler. """ threading.Thread.__init__(self, group, target, name, args, kwargs, verbose) if kwargs is None: kwargs = {} self._target = target self._args = args self._kwargs = kwargs self._exc = None def run(self): try: if self._target: self._target() except BaseException as e: import sys self._exc = sys.exc_info() finally: #Avoid a refcycle if the thread is running a function with #an argument that has a member that points to the thread. del self._target, self._args, self._kwargs def join(self): threading.Thread.join(self) if self._exc: msg = "Thread '%s' threw an exception: %s" % (self.getName(), self._exc[1]) new_exc = Exception(msg) raise new_exc.__class__, new_exc, self._exc[2] t = ExceptionThread(target=my_func, name='my_thread') t.start() try: t.join() except: traceback.print_exc() ''' Traceback (most recent call last): File "/data/code/testcode/thread_exc.py", line 43, int.join() File "/data/code/testcode/thread_exc.py", line 23, in run self._target() File "/data/code/testcode/thread_exc.py", line 5, in my_func raise BaseException("thread exception") Exception: Thread 'my_thread' threw an exception: thread exception '''