自学内容网 自学内容网

python全栈学习记录(二十四)元类、异常处理

元类、异常处理

一、元类

1.元类控制类的实例化

类的__call__方法会在产生的对象被调用时自动触发,args和kwargs就是调用实例时传入的参数,返回值是调用实例以后的结果。

class A():
    def __call__(self, *args, **kwargs):
        print("from __call__")
        print(args,kwargs)
        return 333


a=A()
print(a(111,k=222))

<<<from __call__
<<<(111,) {'k': 222}
<<<333

一般情况下类的__call__函数会完成以下的几件事情:创建一个新对象、根据传入的值完成新对象的初始化、返回新建的对象。换句话说__new__和__init__两个方法是在__call__方法里面完成的。
基于此我们就可以通过复写元类的__call__方法完成对类实例化的控制效果。

class Mymeta(type):

    def __call__(self, *args, **kwargs):
        #因为是类调用的时候触发了__call__方法,所以此处的self是People
        # 1、先造出一个People的空对象
        #obj=object.__new__(self)
        obj=self.__new__(self)
        # 2、用People的初始化方法为新建的People对象完成初始化
        self.__init__(obj,*args,**kwargs)
        # 3、返回初始好的对象
        return obj


class People(object,metaclass=Mymeta):
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age


print(People('111',222))

<<< <__main__.People object at 0x0000022AFFB9AE80>

上述代码中当调用People(‘111’,222)时,触发元类的__call__方法,首先会调用People的__new__方法产生一个People类的新对象,由于People没有__new__方法,根据继承的机制解释器会去父类object中找,新对象产生后就需要根据People类的初始化方法完成对象的初始化,所以我们调用People的初始化方法并将新产生的obj对象传入完成初始化操作,最后返回这个obj对象也就完成了实例化的操作了。我们完全可以在__call__方法的内部添加一些功能已完成实例的定制,例如下面要讲到的单例。

2.属性/方法的查找顺序

python类的属性/方法的查找顺序为:

  • 先在对象层查找,当前对象找不到就去找父类(mro表)
  • 如果父类也找不到再去对象上一级查找(注意实例和类、类和元类都是跨一级的关系)
  • 但是属性/方法的查找最多只能跨一级(例如实例中调用属性,在对应类及其父类中找不到就结束了,因为实例到元类跨了两个级别,实例中调的属性无法去元类中查找)

根据上面的规则简单来讲就是:

  • 在实例中查找:实例——对应的类——对应类的父类
  • 在类中查找:类——父类——元类——元类的父类

python中继承和实例的关系如下:

  • type是所有类的元类、也是自己的元类
  • object是所有类的父类
  • int、str、list等是object的子类,是type的实例
  • 1、‘1’、[1]等是int、str、list的实例
  • class创建的类是type的实例,是object的子类

3.单例

单例模式:基于某种方法实例化多次得到实例是同一个。
当实例化多次得到的对象中存放的属性都一样的情况,应该将多个对象指向同一个内存地址以减少占用的空间。
例如现在我需要传入一个ip地址,我们自动最常用的ip是127.0.0.1,如果需要传入的是127.0.0.1那么就直接调用单例,如果是其他ip就创建新实例。这个需求可以有三种方法实现:

方式一:内置属性法

class Single:
    __instacne=None
    #默认的ip值
    __ip="127.0.0.1"

    def __init__(self,ip):
        self.ip=ip

#使用默认的ip单例
    @classmethod
    def from_conf(cls):
        if cls.__instacne is None:
            cls.__instacne=cls(cls.__ip)
        return cls.__instacne

obj1=Single('127.10.0.10')
obj2=Single.from_conf()
obj3=Single.from_conf()
print(obj1,obj2,obj3)
<<< <__main__.Single object at 0x0000021A1F24AEB0> <__main__.Single object at 0x0000021A1F24ADF0> <__main__.Single object at 0x0000021A1F24ADF0>

方法二:装饰器

def singleton(cls):
    #闭包函数内存一个传默认值的实例
    cls.__instance=cls('127.0.0.1')
    def wrapper(*args,**kwargs):
        #不传值则返回存的单例
        if len(args) == 0 and len(kwargs) == 0:
            return cls.__instance
        return cls(*args,**kwargs)
    return wrapper

@singleton
class Single:
    def __init__(self,ip):
        self.ip=ip


obj1=Single('127.10.0.10')
obj2=Single()
obj3=Single()
print(obj1,obj2,obj3)
<<< <__main__.Single object at 0x000001F5E256AD60> <__main__.Single object at 0x000001F5E256AE80> <__main__.Single object at 0x000001F5E256AE80>

方法三:元类控制单例的产生

class Mymeta(type):
    #初始化类的过程需要传入class_name,class_bases,class_dic
    def __init__(self,class_name,class_bases,class_dic):
        #继承type的__init__方法初始化Single
        super(Mymeta,self).__init__(class_name,class_bases,class_dic )
        #由于Single需要一个内置属性存放单例,所以在初始化类的过程中完成单例的创建
        self.__instance = self.__new__(self)  # 造出一个Mysql的对象
        self.__init__(self.__instance, '127.0.0.1')

    def __call__(self, *args, **kwargs):
        #当实例化不传参时,返回单例,传参则正常完成实例化
        if len(args) == 0 and len(kwargs) == 0:
            return self.__instance
        obj=self.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj

class Single(metaclass=Mymeta):
    def __init__(self,ip):
        self.ip=ip

obj1=Single('127.10.0.10')
obj2=Single()
obj3=Single()
print(obj1,obj2,obj3)
<<< <__main__.Single object at 0x000001CA60BFCAF0> <__main__.Single object at 0x000001CA60BFCC40> <__main__.Single object at 0x000001CA60BFCC40>

二、异常处理

当程序运行过程中出现错误时,就会报错,这个就是异常。如果我们希望代码在可能发生错误的情况下依然能正常运行,就需要用到异常处理。但是需要注意异常处理并不是越多越好,如果程序中的错误是可以预知的,应该尽可能不使用异常处理,只有当程序中的错误是不可预知的,才需要使用到异常处理。

异常处理的常用格式:

#当try中的程序运行出现错误时会跳转到except处,根据except后的信息处理错误
#except ValueError表示出现ValueError错误时出现跳转到except处运行下面的代码,如果出现其他错误则直接报错
try:
代码块
except ValueError:
代码块
#同时接受多个错误
except (IOError,NameError):
代码块
#接受错误并打印错误信息
except ImportError as e:
#e为错误信息
print(e)
#except Exception表示接受所有的错误,Exception可以不写
except Exception:
代码块

#else表示try中的代码没有异常时运行,finally表示try except代码运行完以后运行,通常用来回收资源
try:
代码块
except:
代码块
else:
代码块
finally:
代码块

常见的错误形式:
在这里插入图片描述

断言:

#assert会在不满足后面的条件时报错,一般用于测试阶段
l=[1,2]
assert len(l)==3
<<<AssertionError

raise:

#程序运行至raise处时会抛出后面的错误
raise ValueError('找不到值')
<<<ValueError: 找不到值

自定义异常:

#自定义的错误类必须继承BaseException类
class NagaException(BaseException):
    def __init__(self,data):
        self.data=data

    def __str__(self):
        return self.data

try:
    raise NagaException('自定义错误')
except NagaException as e:
    print(e)
<<<自定义错误

原文地址:https://blog.csdn.net/m0_45491627/article/details/142768646

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!