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, in 
    t.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
'''