美文网首页
面向对象:元类,异常处理

面向对象:元类,异常处理

作者: 牛奶加醋 | 来源:发表于2018-07-31 00:02 被阅读1次

    元类

    一 知识储备

    exec:三个参数
    
    参数一:字符串形式的命令
    参数二:全局作用域(字典形式),如果不指定,默认为globals()
    参数三:局部作用域(字典形式),如果不指定,默认为locals()
    

    exec的使用

    #可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
    g={
    'x':1,
    'y':2
    }
    l={}
    
    exec('''
    global x,z
    x=100
    z=200
    
    m=300
    ''',g,l)
    
    print(g) #{'x': 100, 'y': 2,'z':200,......}
    print(l) #{'m': 300}
    

    一切皆对象,对象可以怎么用:

    1.都可以被引用(赋值给变量)

    2.当做函数的参数传入

    3.当做函数的返回值

    4.当做容器类的元素(列表,字典,元组,集合中的元素)

    类也是对象.....

    三 什么是元类?

    元类是类的类,是类的模板

    元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为

    元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)

    type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

    四 创建类的两种方式

    方式一:使用class关键字

    方式二:就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建

    #准备工作:
    
    #创建类主要分为三部分
    
      1 类名
      2 类的父类
      3 类体
    
    #类名
    class_name='Chinese'
    #类的父类
    class_bases=(object,)
    #类体
    class_body="""
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print('%s is talking' %self.name)
    """
    

    步骤一(先处理类体====>名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

    class_dic={}
    exec(class_body,globals(),class_dic)
    
    
    print(class_dic)
    #{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}
    

    步骤二:调用元类type(也可以自定义)来产生类Chinense

    Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo
    
    
    print(Foo)
    print(type(Foo))
    print(isinstance(Foo,type))
    '''
    <class '__main__.Chinese'>
    <class 'type'>
    True
    '''
    

    我们看到,type 接收三个参数:

    • 第 1 个参数是字符串 ‘Foo’,表示类名
    • 第 2 个参数是元组 (object, ),表示所有的父类
    • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

    补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type('Foo',(Bar,),{})

    五 自定义元类控制类的行为

    #一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的行为,工作流程是什么)
    

    egon5步带你学会元类

    #知识储备:
        #产生的新对象 = object.__new__(继承object类的子类)
    
    #步骤一:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建
    class Mymeta(type):  # 继承默认元类的一堆属性
        def __init__(self, class_name, class_bases, class_dic):
            if '__doc__' not in class_dic or not class_dic.get('__doc__').strip():
                raise TypeError('必须为类指定文档注释')
    
            if not class_name.istitle():
                raise TypeError('类名首字母必须大写')
    
            super(Mymeta, self).__init__(class_name, class_bases, class_dic)
    
    
    class People(object, metaclass=Mymeta):
        country = 'China'
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def talk(self):
            print('%s is talking' % self.name)
    
    #步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用
    class People(object,metaclass=type):
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def __call__(self, *args, **kwargs):
            print(self,args,kwargs)
    
    
    # 调用类People,并不会触发__call__
    obj=People('egon',18)
    
    # 调用对象obj(1,2,3,a=1,b=2,c=3),才会触发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3)
    obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}
    
    #总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj
    
    #步骤三:自定义元类,控制类的调用(即实例化)的过程
    class Mymeta(type): #继承默认元类的一堆属性
        def __init__(self,class_name,class_bases,class_dic):
            if not class_name.istitle():
                raise TypeError('类名首字母必须大写')
    
            super(Mymeta,self).__init__(class_name,class_bases,class_dic)
    
        def __call__(self, *args, **kwargs):
            #self=People
            print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}
    
            #1、实例化People,产生空对象obj
            obj=object.__new__(self)
    
    
            #2、调用People下的函数__init__,初始化obj
            self.__init__(obj,*args,**kwargs)
    
    
            #3、返回初始化好了的obj
            return obj
    
    class People(object,metaclass=Mymeta):
        country='China'
    
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def talk(self):
            print('%s is talking' %self.name)
    
    obj=People('egon',18)
    print(obj.__dict__) #{'name': 'egon', 'age': 18}
    
    #步骤四:
    class Mymeta(type): #继承默认元类的一堆属性
        def __init__(self,class_name,class_bases,class_dic):
            if not class_name.istitle():
                raise TypeError('类名首字母必须大写')
    
            super(Mymeta,self).__init__(class_name,class_bases,class_dic)
    
        def __call__(self, *args, **kwargs):
            #self=People
            print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}
    
            #1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj
            obj=self.__new__(self,*args,**kwargs)
    
            #2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值
            return obj
    
    class People(object,metaclass=Mymeta):
        country='China'
    
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def talk(self):
            print('%s is talking' %self.name)
    
        def __new__(cls, *args, **kwargs):
            obj=object.__new__(cls)
            cls.__init__(obj,*args,**kwargs)
            return obj
    
    obj=People('egon',18)
    print(obj.__dict__) #{'name': 'egon', 'age': 18}
    
    #步骤五:基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存
    class Mysql:
        __instance=None
        def __init__(self,host='127.0.0.1',port='3306'):
            self.host=host
            self.port=port
    
        @classmethod
        def singleton(cls,*args,**kwargs):
            if not cls.__instance:
                cls.__instance=cls(*args,**kwargs)
            return cls.__instance
    
    
    obj1=Mysql()
    obj2=Mysql()
    print(obj1 is obj2) #False
    
    obj3=Mysql.singleton()
    obj4=Mysql.singleton()
    print(obj3 is obj4) #True
    
    #应用:定制元类实现单例模式
    class Mymeta(type):
        def __init__(self,name,bases,dic): #定义类Mysql时就触发
            self.__instance=None
            super().__init__(name,bases,dic)
    
        def __call__(self, *args, **kwargs): #Mysql(...)时触发
    
            if not self.__instance:
                self.__instance=object.__new__(self) #产生对象
                self.__init__(self.__instance,*args,**kwargs) #初始化对象
                #上述两步可以合成下面一步
                # self.__instance=super().__call__(*args,**kwargs)
    
            return self.__instance
    class Mysql(metaclass=Mymeta):
        def __init__(self,host='127.0.0.1',port='3306'):
            self.host=host
            self.port=port
    
    
    obj1=Mysql()
    obj2=Mysql()
    
    print(obj1 is obj2)
    

    六 练习题

    练习一:在元类中控制把自定义类的数据属性都变成大写

    class Mymeta(type):
        def __new__(cls, cls_name, cls_bases, cls_dict):
            """
            将类中的属性都变成大写例如下面的country = 'chinese'
            """
            update_attrs = {}
            for k, v in cls_dict.items():
                if not callable(v) and not k.startswith('__') and isinstance(k, str):
                    update_attrs[k] = v.upper()
                else:
                    update_attrs[k] = v
            return type.__new__(cls, cls_name, cls_bases, update_attrs)
    
        def __call__(self, *args, **kwargs):
            """
            将类中初始化传进的参数都变成大写例如下面的name,sex
            """
            obj = object.__new__(self)
            args1 = list(args)
            for i, k in enumerate(args1):
                if isinstance(k, str):
                    args1[i] = k.upper()
            for i in kwargs:
                if isinstance(kwargs[i], str):
                    kwargs[i] = kwargs[i].upper()
            self.__init__(obj, *args1, **kwargs)
            return obj
    
    
    class Foo(metaclass=Mymeta):
        country = 'chinese'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
        def tell_info(self):
            info = """
            country:  %s
            name:     %s
            age:      %s
            sex:      %s
            """ % (self.country, self.name, self.age, self.sex)
            print(info)
    
    
    obj1 = Foo('dc', 18, 'male')
    obj2 = Foo('zj', 16, 'famale')
    obj1.tell_info()
    obj2.tell_info()
    

    练习二:在元类中控制自定义的类无需init方法

    1.元类帮其完成创建对象,以及初始化操作;
      2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
      3.key作为用户自定义类产生对象的属性,且所有属性变成大写

    class Mymeta(type):
        def __call__(self, *args, **kwargs):
            obj = object.__new__(self)
            if args:
                raise TypeError('must use keyword argument')
            for i in kwargs:
                obj.__dict__[i.upper()] = kwargs[i]
            return obj
    
    
    class Foo(metaclass=Mymeta):
        def tell_info(self):
            print(self.__dict__)
    
    
    obj1 = Foo(name='dc', age=18, sex='male')
    obj1.tell_info()
    

    面向对象的软件开发

    1.面向对象分析**(object oriented analysis ,OOA)

    2.面向对象设计(object oriented design,OOD)

    3.面向对象编程(object oriented programming,OOP)

    4. 面向对象测试(object oriented test,OOT)

    5.面向对象维护(object oriendted soft maintenance,OOSM)

    异常处理

    1. 异常: 异常是错误发生的信号,一旦程序出错,并且程序没有处理这个错误,那就会抛出异常,并且程序的运行随之终止.

    • 语法错误
      • 这种错误不能通过python解释器的语法检测,必须在程序执行前就改正
    • 逻辑错误
      • TypeErroe 类型错误
      • ValueError 变量值错误
      • IndexError 索引错误
      • KeyError 键值错误
      • AttributeError 属性错误
      • ZeroDivisionError 无法完成计算

    2. 异常的种类

    • 常见异常

      • AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
        IOError 输入/输出异常;基本上是无法打开文件
        ImportError 无法引入模块或包;基本上是路径问题或名称错误
        IndentationError 语法错误(的子类) ;代码没有正确对齐
        IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
        KeyError 试图访问字典里不存在的键
        KeyboardInterrupt Ctrl+C被按下
        NameError 使用一个还未被赋予对象的变量
        SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
        TypeError 传入对象类型与要求的不符合
        UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
        导致你以为正在访问它
        ValueError 传入一个调用者不期望的值,即使值的类型是正确的
        
    • 更多异常

      • ArithmeticError
        AssertionError
        AttributeError
        BaseException
        BufferError
        BytesWarning
        DeprecationWarning
        EnvironmentError
        EOFError
        Exception
        FloatingPointError
        FutureWarning
        GeneratorExit
        ImportError
        ImportWarning
        IndentationError
        IndexError
        IOError
        KeyboardInterrupt
        KeyError
        LookupError
        MemoryError
        NameError
        NotImplementedError
        OSError
        OverflowError
        PendingDeprecationWarning
        ReferenceError
        RuntimeError
        RuntimeWarning
        StandardError
        StopIteration
        SyntaxError
        SyntaxWarning
        SystemError
        SystemExit
        TabError
        TypeError
        UnboundLocalError
        UnicodeDecodeError
        UnicodeEncodeError
        UnicodeError
        UnicodeTranslateError
        UnicodeWarning
        UserWarning
        ValueError
        Warning
        ZeroDivisionError
        

    3.异常处理

    为了保证程序的健壮性与容错性,即在遇到错误时程序不会崩溃,我们需要对异常进行处理,

    • 如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防
    • 如果错误发生的条件是不可预知的,则需要用到try...except:在错误发生之后进行处理
    #基本语法为
    try:
        被检测的代码块
    except 异常类型:
        try中一旦检测到异常,就执行这个位置的逻辑
    #举例
    try:
        f=open('a.txt')
        g=(line.strip() for line in f)
        print(next(g))
        print(next(g))
        print(next(g))
        print(next(g))
        print(next(g))
    except StopIteration:
        f.close()
    

    4. try..except....详细用法

    1.异常类只能用来处理指定的异常情况,如果非指定异常则无法处理

    • 如果指定的异常与实际抛出的异常不符,则无法捕捉到改异常,程序也会报错终止

    2.多分支

    • 被监测的代码块抛出的异常有多重可能性,并且需要针对每种异常类型都定制专门的处理逻辑
    s1 = 'hello'
    try:
        int(s1)
    except IndexError as e:
        print(e)
    except KeyError as e:
        print(e)
    except ValueError as e:
        print(e)
    

    3.万能异常Exception

    s1 = 'hello'
    try:
        int(s1)
    except Exception as e:
        print(e)
    

    4.也可以在多分支后来一个Exception

    s1 = 'hello'
    try:
        int(s1)
    except IndexError as e:
        print(e)
    except KeyError as e:
        print(e)
    except ValueError as e:
        print(e)
    except Exception as e:
        print(e)
    

    5.异常的其他结构

    • else:在被监测的代码块没有发生异常时执行....(不常用)

    • finally: 无论异常与否,都会执行该语句后面的代码,通常是进行清理工作(常用)

    s1 = 'hello'
    try:
        int(s1)
    except IndexError as e:
        print(e)
    except KeyError as e:
        print(e)
    except ValueError as e:
        print(e)
    #except Exception as e:
    #    print(e)
    else:
        print('try内代码块没有异常则执行我')
    finally:
        print('无论异常与否,都会执行该模块,通常是进行清理工作')
    

    6.主动出发异常

    • raise TypeError('类型错误')
    try:
        raise TypeError('类型错误')
    except Exception as e:
        print(e)
    

    7.自定义异常

    class EgonException(BaseException):
        def __init__(self,msg):
            self.msg=msg
        def __str__(self):
            return self.msg
    
    try:
        raise EgonException('类型错误')
    except EgonException as e:
        print(e)
    

    8.断言,assert条件

    • assert 1 == 1
    • assert 1 == 2
    • assert 'name' in info and 'age' in info
      • 断定'name'和'age'在info里,如果不是就抛出一个AssertError的异常

    9.总结 try...except..

    1. 把错误处理和真正的工作分开来;
    2. 代码更易组织,更清晰,复杂的工作任务更容易实现;
    3. 毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
    • 明确的可以预知异常的发生,就应该用if...else...来避免异常
    • 错误一定会发生,无法预知错误发生的条件,这个时候去使用try...except....

    相关文章

      网友评论

          本文标题:面向对象:元类,异常处理

          本文链接:https://www.haomeiwen.com/subject/msaqvftx.html