python第二十八天,(元类,异常处理,)

元类的介绍:

1.什么时元类?

  在python中,一切皆对象,类也是对象,可以把一个类当成普通对象来使用,比如存储到列表中,或者作为参数传给函数等等。

对象时如何产生的?

  通过类实例化产生的

  类对象是由type实例化产生的

一个类由三个部分组成:

  1.类名,2.类的基类(object) 3. 类的名称空间

而使用type(类名,继承的父类,名称空间字典) 可以获得一个新的类。

所以,总结出来,当定义一个class时,解释器会自动调用type来完成类的实例化

 案例:

# 模拟解释器创建类的过程
obj = type('TeatClass', (object,), {})
print(obj)  # <class '__main__.TeatClass'>  得到一个类


# 名称空间也可以放实际的函数
def test1(a):
    print(a)


def test2(self, b):
    print(self, b)


class_name = 'C'
bases = (object,)
name_dict = {'name': 'jack', 'test1': test1, 'test2': test2} # 可以把函数作为名称空间字典

C = type(class_name, bases, name_dict)  # 创建一个新的类,
c1 = C()
c1.test2(100)  # 可以通过点语法来调用定义的方法。

总结:元类:用于产生类的类,称之为元类

元类翻译为:metaclass , 所以一般在定义元类时,尽量在类名后面添加MetaClass,方便阅读

元类的使用场景:

  当我们需要高度定制类时,比如限制类名,或着属性的名字等等。就需要使用元类,不过因为原类type中的代码无法被修改,所以我们一般创建新的元类方法是使用继承自type的子类,通过覆盖__init__来完成对类的限制

自定义元类的语法:

# 默认创建类时,是找的type来实例化的
class MyMetaClass(type):
    pass


# 使用metaclass关键字来定义原类的继承
class Person(metaclass=MyMetaClass):
    pass

class Student:
    def __init__(self):
        pass

print(type(Person))     # <class '__main__.MyMetaClass'>
print(type(Student))    # <class 'type'>
# 创建对象也是一样,会先创建空的类对象,再调用__init__方法
# Person = MyMetaClass()

 

exec与eval

exec:执行储存在字符串或文件中的 Python 语句,相比于 eval,exec可以执行更复杂的 Python 代码。

语法:exec(object, globals, locals )

  object : 必选参数,表示需要被指定的Python代码。它必须是字符串或code对象。如果object是一个字符串,该字符串会先被解析为一组Python语句,然后在执行(除非发生语法错误)。如果object是一个code对象,那么它只是被简单的执行。

  globals:可选参数,表示全局命名空间(存放全局变量),如果被提供,则必须是一个字典对象。

  locals:可选参数,表示当前局部命名空间(存放局部变量),如果被提供,可以是任何映射对象。如果该参数被忽略,那么它将会取与globals相同的值。

返回值

exec 返回值永远时None

 语法:

glob = {}
locl = {}

code = '''
def test(a):
    print(a)
'''
print(exec(code,glob,locl))

eval: 用来执行一个字符串表达式,并返回表达式的值,但是不能由任何特殊的语法

1.__init__方法

  实例化对象时会自动执行类中的__init__方法,类也是对象,在实例化类对象时会自动执行元类中的__init__方法

  并且传入类的三个必要参数,类的名字,继承的父类们,名称空间

  会自动传入类对象本身作为第一个参数(self)

自定义原类案例:判断类名首字母是否为大写,判断类中的方法名称是否都为小写

class MyMetaClass(type):
    def __init__(self, class_name, bases, name_dict):
        # 元类中的self表示的都是类对象
        # 不要忘记调用父类的初始化
        super().__init__(class_name, bases, name_dict)
        print(name_dict)
        # 判断类名,必须首字母大写,否则直接抛出异常
        if not class_name.istitle():
            print('类名首字母必须大写')
            raise Exception

        # 控制类中方法名必须全部小写
        # 遍历类中的名称空间,如果发现方法有大写,则报错
        for k in name_dict:
            if str(type(name_dict[k])) == "<class 'function'>":
                if not k.islower():
                    print('方法名称必须全小写')
                    raise Exception


# 会自动调用其元类中的__init__方法传入 类对象本身 类名称 父类们 名称空间
class Student(object, metaclass=MyMetaClass):  # # MyMetaClass("Student",(object,),{})
    NAME = 10

    def say(self):
        print('SAY')

    pass

2.__new__方法

元类中的new方法会创建类对象时执行,并且先于init方法,它的作用是创建一个类对象

执行步骤:1.执行__new__方法,拿到一个类对象, 2. 执行__init__方法,传入类对象以及其他属性,进行初始化

需要注意的是:定义类的时候,本身有一个默认的__new__,如果覆盖了__new__一定也要调用type中的__new__并返回执行结果,不然无法定义类

使用new方法也可以完成定制类的工作,和init有什么区别?

  在调用__init__方法前,类对象已经创建完成了,所以如果对性能要求高的话,可以选择在__new__中完成定制,如果发现有问题,就不用创建类对象了

语法:

class MyMetaClass(type):
    # 传入类的三大组成部分
    def __init__(self, class_name, bases, name_dict):
        super().__init__(class_name, bases, name_dict)
        print('init')

    # 该方法会在实例化类对象时自动调用并且优先在__init__之前调用
    # 其作用是用于创建新的类对象的
    # 注意这里必须调用type类中的__new__否则将无法产生类对象 并且返回其结果
    def __new__(cls, *args, **kwargs):
        # cls 表示元类自己,即MyMetaClass
        return type.__new__(cls, *args, **kwargs)  # 类本身有一个__new__方法,如果覆盖__new__一定要将__new__返回


class Person(metaclass=MyMetaClass):
    pass

print(Person)
# 就算__init__中什么都不写 这个类对象其实已经创建完成了 该有的属性都有了
# 这是与普通类不同之处

案例:需求:要求每个类必须包含__doc__属性,__doc__用于访问一个对象的注释信息

# 如果你要控制类的创建,那就自定义元类 覆盖__init__

class DocMeatClass(type):
    def __init__(self,class_name,bases,name_dict):
        super().__init__(class_name,bases,name_dict)
        if not self.__doc__:
            raise Exception

class Person(metaclass=DocMeatClass):
    pass

3.__call__方法

元类中的__call__方法会在调用类时执行,可以用于控制对象的创建过程

案例:

总结:当你要定制类时,就自定义元类并覆盖__init__方法

 案例:使传入的字符串全部改为大写

class MyMeta(type):

    # 获得某个类的实例
    def __call__(self, *args, **kwargs):
        new_args = []
        for i in args:
            if isinstance(i, str):
                new_args.append(i.upper())
            else:
                new_args.append(i)
        return super().__call__(*new_args, **kwargs)


# 注意注意注意!!!__new__  __init__是创建类对象时还会执行
# __call__类对象要产生实例时执行
class Student(metaclass=MyMeta):
    def __init__(self, name, gender, age):
        self.name = name
        self.gender = gender
        self.age = age

s= Student('jack','man',18)
print(s.age)


class Person(metaclass=MyMeta):
    def __init__(self,name,gender):
        self.name = name
        self.gender = gender

p = Person('rose','woman')
print(p.gender)

元类实现单例模式

什么是单例:

  某个类如果只有一个实例对象,那么该类成为单例类

单例的好处:

  当某个类的所有对象特征和行为完全一样时,避免重复创建对象,浪费资源,如果是避免重复创建对象,那么单列需要与__call__一起使用,在__call__内部加判断

案例:

class SingletonMetaClass(type):
    # 创建类时会执行init 在这为每个类设置一个obj属性,默认为None
    def __init__(self, a, b, c):
        super().__init__(a, b, c)
        self.obj = None

    # d当类要创建对象时会执行,该方法
    def __call__(self, *args, **kwargs):
        # 判断这个类,如果已经有实例了就 直接返回,从而实现单例
        if self.obj:
            return self.obj

        # 没有则创建新的实例并保存到类中
        obj = type.__call__(self, *args, **kwargs)
        self.obj = obj
        return obj


class Person(metaclass=SingletonMetaClass):
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def say(self):
        print('my name is %s my 姑姑 is 龙妈' % self.name)


class Student(metaclass=SingletonMetaClass):
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def say(self):
        print('my name is %s my 姑姑 is 龙妈' % self.name)


p1 = Person('jon snow', 18, 'man')
p2 = Person('jon snow', 18, 'man')
print(p1.say())



stu1 = Student("布兰", 16, "man")
stu2 = Student("布兰", 16, "man")
stu3 = Student("布兰", 16, "man")
print(stu1, stu2, stu3) # 地址完全一样

 案例二:生成单例播放器

class Single(type):
    def __init__(self, a, b, c):
        super().__init__(a, b, c)
        self.obj = None

    def __call__(self, *args, **kwargs):
        if self.obj:
            return self.obj

        obj = type.__call__(self, *args, **kwargs)
        self.obj = obj
        return obj


class QQPlayer(metaclass=Single):
    def __init__(self, voice_value, repeat=False):
        self.voice_value = voice_value
        self.repeat = repeat

    def play(self, file_path):
        if hasattr(self, 'file_path'):
            self.stop()

        print('正在播放%s' % file_path)
        self.file_path = file_path

    def stop(self):
        print('%s停止播放' % self.file_path)


playerl = QQPlayer(100,True)
playerl.play('如果我是DJ.mp3')

player2 = QQPlayer(100,True)
player2.play('你会爱我吗.mp3')

player3 = QQPlayer(100,True)
player3.play('我的滑板鞋.mp3')

   异常

1.什么是异常

  异常是程序运行过程中发生的非正常情况,是一个错误发生时的信号,异常如果没有被正常处理的话,将导致程序被终止,这对于用户体验是非常差的,可能导致严重的后果。处理异常的目的就是提高程序的健壮性

2.异常的分类

  

TypeError: 'int' object is not subscriptable     对象不能被切片  
TypeError: 'list' object is not callable        对象不能被调用
IndexError: list index out of range                索引超出范围
TypeError: 'builtin_function_or_method' object is not iterable     对象不能被迭代
KeyError: 'xxx'      不存在这个key
FileNotFoundError: [Errno 2] No such file or directory: 'xxxxx'  文件找不到

3.异常的组成

Traceback (most recent call last):
  File "F:/python8期/课堂内容/day29/11.常见异常.py", line 22, in <module>
    with open("xxxxx") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'xxxxx'

Traceback : 是异常追踪信息,用于展示错误发生的具体位置,以及调用的过程,其中包括了错误发生的  模块  ,  文件路径  ,行号  ,函数名称  ,具体的代码  

最后一行: 前面是错误的类型,后面是错误的详细信息,在查找错误时,主要参考的就是详细信息

4.异常的处理

  异常发生后,如果不正确处理将会导致程序终止,所以我们应该尽量的避免这种情况发生

必须掌握的语法:

try:

  可能会出现异常的代码,放到try里面

except 具体的类型 as e:

  如果真的发生异常就会执行except

5.如何正确处理异常

  1.当发生异常时,不是立马加try, 要先找出错误原因并解决它

  2.try 仅在 即使你知道为什么发生错误,但是你却无法避免

    例如,你明确告诉用户需要一真确的文件路径,然而用户依然传入了错误的路径

    如 socket 双方都要使用管道,但是如果以访由于某些原因强行关闭了,即使你只要原因也无法避免出错,那就只能try 保证程序正常结束

    能不加try 就不加 try

6.自定义异常类

  当系统提供异常类不能准确描叙错误原因时,就可以自定义异常类,继承自Exception即可

案例:

7.主动抛出异常

什么时候需要主动抛出异常?

  当我们做功能的提供者,给外界提供一个功能接口

  但是使用者不按照相应的方式来使用,或者参数类型不正确等原因,导致功能无法正常执行时,就应该主动抛出异常

主动抛出异常使用 raise 关键字

后面可以跟任何Exception的 子类 或是 对象。

案例:

8.断言assert

断言,其实可以理解为断定的意思

即非常肯定某个条件是成立的

条件是否成立其实可以使用 if 来判断

其存在的目的就是为了简化 if 判断而生的

猜你喜欢

转载自www.cnblogs.com/liguodeboke/p/10920571.html