• python之元类、双下方法( 双下方法也叫魔术方法、 内置方法)


    元类

    现在我们都应该知道一个对象的类型(type)就是创建这个对象的类,
    而实际上类也是被创建出来的,那么类的类型(type)也是创建这个类的类型,
    默认的话,创建类的是type元类,
    也就是说如果没有指定mateclass的类,那么这个类的类型就是type元类,
    而指定了mateclass的类的type就是指定的mateclass的值。

    例如:

    from abc import ABCMeta
    
    
    class A:
        pass
    
    
    class B:
        pass
    
    
    class C(metaclass=ABCMeta):
        pass
    
    
    print(type(A))  # <class 'type'>
    print(type(B))  # <class 'type'>
    print(type(C))  # <class 'abc.ABCMeta'>

    刚才也说了,实际上类也是被创造出来的,那么我们来看下

    """
    使用type创建类:
    以下两种写法都可以:
        type('Class',(object,),dict(key=value, funcName=function))
        type('Class',(object,),{"key": value, "funcName":function})
        参数:
            参数1:类名
            参数2:继承
            参数3:成员(包括属性和方法)
            
    假如我们要创建这样的类,使用type可以这么创建
    class Foo(object):
        country = '中国'
    
        def func(self):
            return 123
    """
    #           类名      继承         属性       值     属性    函数
    Foo = type('Foo', (object,), {'country': '中国', 'func': lambda self: 123})
    
    obj = Foo()
    ret = obj.func()
    print(obj.country)  # 中国
    print(ret)  # 123

    现在可以看出对象是由类创建,而类是由type创建
    那么metaclass是做什么的呢?
    实际上metaclass可以指定类由具体哪一个type创建。
    也就是说如果创建类的时候,有设置__metaclass__属性,那么就会通过__metaclass__创建这个类对象(是类对象)。
    如果没有设置__metaclass__属性,它会继续在父类中寻找__metaclass__属性,
    如果在任何父类中都找不到__metaclass__,就会用内置的type来创建这个类对象。

    # 统计一个类中值是 "zzz" 的所有属性和以"z"开头的属性和方法方法
    # 继承type,重写一个元类
    class MyType(type):
        def __new__(cls, name, bases, attrs):
            # name为实体类名,bases为实体类继承的类,attrs为实体类中的属性和方法
    
            # 定义一个my_z的属性存放
            attrs["my_z"] = {}
            for key, val in attrs.items():
                if val == "zzz":  # 值是 "zzz"
                    attrs["my_z"][key] = val
                if key.startswith("z"):  # 以"z"开头的属性和方法方法
                    if not attrs["my_z"].get(key, None):
                        attrs["my_z"][key] = val
            return type.__new__(cls, name, bases, attrs)  # 必须返回一个类
    
    
    class Foo(object, metaclass=MyType):
        zzz_name = "zzz"
        age = 18
    
        def zzz_talk(self):
            return "xx"
    
        def say(self):
            return "hello"
    
    
    obj = Foo()
    
    print(obj.my_z)  # {'zzz_name': 'zzz', 'zzz_talk': <function Foo.zzz_talk at 0x000001EFE22AC0D0>}

    类完整的创建流程

    class MyType(type):
        def __init__(self, name, bases, dic):
            print('2 type.init,在创建Foo类执行进行类的初始化')
            super().__init__(name, bases, dic)
    
        def __new__(cls, name, bases, dic):
            print('1 type.new,创建Foo类 ')
            foo_class = super().__new__(cls, name, bases, dic)
            return foo_class
    
        def __call__(self, *args, **kwargs):
            print('3. type.call')
            object = self.__new__(self, *args, **kwargs)
            object.__init__(*args, **kwargs)
    
    
    class Foo(object, metaclass=MyType):
        def __init__(self):
            print('3.2 foo.init')
    
        def __new__(cls, *args, **kwargs):
            print('3.1 foo.new')
            return super().__new__(cls)
    
    
    # Foo是一个类,Foo是MyType类创建的对象。所以 Foo(), MyType类创建的对象 -> MyType.call
    obj = Foo()

     元类的实际应用场景

    # wtforms组件中使用
    from wtforms import Form
    from wtforms.fields import simple
    
    
    class LoginForm(Form):
        """
        LoginForm -> Form ->NewBase -> 由FormMeta创建 -> BaseForm -> object -> type
        """
        name = simple.StringField()
        pwd = simple.StringField()
    
    
    
    # django form组件中使用
    from django import forms
    
    
    class LoginForm(forms.Form):
        """
        BaseForm
        Form -> DeclarativeFieldsMetaclass >  MediaDefiningClass > type 创建的。
        """
        name = forms.CharField()

    1、__new__ 构造方法

    __new__:在__init__执行之前,实例化对象的第一步是__new__创建一个对象空间。

    继承自object的新式类才有__new__

    __new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供,

    __new__必须要有返回值,返回的是实例化出来的实例,

    可以return父类__new__出来的实例,或者直接是object的__new__出来的实例,

    但是如果__new__没有正确返回当前类cls的实例,那__init__是不会被调用的,即使是父类的实例也不行。

    return的类是谁,就创建谁的对象,但是在本类中如果return的不是本类的对象,那么不会执行__init__。

    # 正常的写法:
    class Father:
        def __init__(self):
            print('执行了init')
    
        def __new__(cls, *args, **kwargs):
            print('执行了new')
            return object.__new__(cls)
            # return super().__new__(cls)
    
    
    f = Father()  # 执行了new 执行了init
    
    
    # return本类的对象
    class A:
        name = 'a'
    
        def __init__(self):
            print("A--->init")
    
    
    class B:
        name = 'b'
    
        def __init__(self):
            print("B--->init")  # 第二步:B--->init
    
        def __new__(cls):
            print("new %s" % cls)  # 第一步:new <class '__main__.B'>
            return object.__new__(cls)  # 正常返回当前类的实例,会执行当前类的init
            # return super().__new__(cls)
    
    
    b = B()
    print(type(b))  # 第三步:<class '__main__.B'>
    print(b.name)  # 第四步:b
    
    
    # 不return本类的对象
    class A:
        name = 'a'
    
        def __init__(self):
            print("A--->init")
    
    
    class B:
        name = 'b'
    
        def __init__(self):
            print("B--->init")  # 返回的是A的实例,不执行
    
        def __new__(cls):
            print("new %s" % cls)  # 第一步:new <class '__main__.B'>
            return object.__new__(A)  # 返回A的实例,当前类和A的init都不会执行
    
    
    b = B()
    print(type(b))  # <class '__main__.A'>:A的实例
    print(b.name)  # a

    我们常用__new__实现单例模式:

    https://www.cnblogs.com/Zzbj/p/10437873.html

    2、__del__ 析构方法

    __new__:构造方法
    __init__:构造函数
    __del__:析构方法
    析构方法 : 在删除这个类创建的对象的时候会先触发这个方法,再删除对象,
    方法内应该做一些清理工作,比如说关闭文件,关闭网络的链接,数据库的链接

    class Father:
        def __init__(self):
            self.file = open('file', encoding='utf-8', mode='w')
    
        def My_write(self):
            self.file.write('哈哈哈')
    
        def __del__(self):
            self.file.close()
            print('执行了del')
    
    
    f = Father()
    del f  # 执行了del
    # 注意:重写__del__后,即使不写 del f  在函数执行完毕后,也会自动执行__del__方法

    3、__dict__ 方法

    1. 类的__dict__会把类的所有属性对应的值和方法的地址以字典的形式打印出来
    2. 对象的__dict__只把对象的属性以字典的形式打印出来,即只打印实例化对象时候__init__里面初始化的值
    class Foo:
        f1 = "类属性"
    
        def __init__(self):
            self.obj = "对象属性"
    
    
    # 查看类的__dict__和对象的__dict__
    obj = Foo()
    print(Foo.__dict__)  # {'__model__': '__main__', 'f1': '类属性', '__init__': <function Foo.__init__ at 0x00000018C6D3D59D8>  ....}
    print(obj.__dict__)  # {'obj': '对象属性'}
    
    
    # 利用__dict__构造接口返回实例
    class BaseResponse(object):
    
        def __init__(self):
            self.code = None
            self.data = None
            self.error = None
    
        @property
        def dict(self):
            return self.__dict__
    
    
    # 这样构建的类,在写接口的时候就不需要频繁构建字典了
    # 例如:返回错误的信息
    res = BaseResponse()
    res.code = 500  # 错误状态码
    res.error = {"errorMessage": "错误的信息"}
    print(res.dict)  # 在接口中实际上是 return Response(res.dict)
    
    # 返回成功处理的数据
    res = BaseResponse()
    res.code = 200
    res.data = {"success": "成功返回的数据"}
    print(res.dict)  # 在接口中实际上是 return Response(res.dict)

    4、__call__ 

    __call__()的作用是使实例(对象)能够像函数一样被调用,
    同时不影响实例本身的生命周期[__call__()不影响一个实例的构造和析构]。
    但是__call__()可以用来改变实例的内部成员的值。

    class A:
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def __call__(self, x2, y2):
            self.x = x2
            self.y = y2
    
    
    a = A(1, 2)
    print(a.x, a.y)  # 1 2
    
    a(3, 4)
    print(a.x, a.y)  # 3 4

    5、__len__

    len("xx")  # 内置函数len可以接收的数据类型:list dict set str tuple
    print('__len__' in dir(list))  # True
    print('__len__' in dir(dict))  # True
    print('__len__' in dir(set))  # True
    print('__len__' in dir(tuple))  # True
    print('__len__' in dir(str))  # True
    
    print('__len__' in dir(int))  # False
    print('__len__' in dir(float))  # False
    
    
    # 从上面可以看出,能使用内置函数len的数据类型,内部都有__len__方法
    # 之前说过我们常用的数据类型,其实也是对象,那么可以得出结论:
    # 只要类中有__len__方法,那么这个类的对象就可以使用内置函数len
    
    
    class A:
        def __len__(self):
            return 1
    
    
    a = A()
    print(len(a))  # 1  值是__len__的返回值
    
    
    class Father:
        def __init__(self, s):
            self.s = s
    
        def __len__(self):
            return len(self.s)
    
    
    f = Father('你好啊')
    print(len(f))  # 3

    6、__eq__

    重写了类的__eq__ 方法后,当这个类的对象遇到 == 运算符则自动触发

    # 默认的__eq__不重写的话,比较的是两个对象的内存地址是否相同
    class Staff:
        def __init__(self, name, sex):
            self.name = name
            self.sex = sex
    
    
    s1 = Staff('小明', 18)
    s2 = Staff('小明', 18)
    s3 = Staff('小东', 18)
    
    print(s1 == s2)  # False
    print(s1 == s3)  # False
    
    
    # 可以自己重写,让其比较值是否相等
    class Staff:
        def __init__(self, name, sex):
            self.name = name
            self.sex = sex
    
        def __eq__(self, other):
            return self.__dict__ == other.__dict__  # 对象的所有内容相同,则认为值相同
    
    
    s1 = Staff('小明', 18)
    s2 = Staff('小明', 18)
    s3 = Staff('小东', 18)
    
    print(s1 == s2)  # True
    print(s1 == s3)  # False

    7、__add__

    重写了类的__add__ 方法后,当这个类的对象遇到 + 运算符则自动触发

    class Foo(object):
        def __init__(self, num):
            self.num = num
    
        def __add__(self, other):
            # other: 加号后面的值,也可以是对象
            return self.num + other.number
    
    
    class Bar(object):
        def __init__(self, number):
            self.number = number
    
    
    obj1 = Foo(9)
    obj2 = Bar(11)
    
    result = obj1 + obj2
    print(result)  # 20
    
    
    class Foo2(object):
        def __init__(self, num):
            self.num = num
    
        def __add__(self, other):
            # other: 加号后面的值,也可以是对象
            return self.num + other
    
    
    obj = Foo2(13)
    result = obj + 2
    print(result)  # 15

    8、__setitem__、__getitem__、__delitem__

    1. __setitem__:每当属性使用 ["xx"] 赋值的时候都会调用该方法,因此不能再该方法内再次赋值,比如 self[key] = value 会死循环
    2. __getitem__:当使用 X.["xx"] 访问属性时会调用该方法
    3. __delitem__:当删除属性时调用该方法
    class Session(object):
        def __init__(self):
            self["name"] = "zzz"
            self.age = 18  # 这种方式赋值不会触发__setitem__
    
        def __setitem__(self, key, value):
            # 每当属性被赋值的时候都会调用该方法
            print("__setitem__: Set: %s, Value: %s " % (key, value))
            # self[key] = value  # 如果这么写就会陷入死循环
            self.__dict__[key] = value
    
        def __getitem__(self, key):
            # 每当访问属性的时候都会调用该方法
            print("__getitem__: Get: %s" % key)
            return self.__dict__[key]
    
        def __delitem__(self, key):
            # 当删除属性时调用该方法
            print("__delitem__: Delete: %s" % key)
            del self.__dict__[key]
    
    
    obj = Session()
    obj['x1'] = 123
    obj.x2 = 456  # 这种方式赋值不会触发__setitem__
    x1 = obj['x1']
    print(x1)
    age = obj.age  # 这种方式获取值不会触发__getitem__
    del obj['x1']
    """ 结果
    __setitem__: Set: name, Value: zzz 
    __setitem__: Set: x1, Value: 123 
    __getitem__: Get: x1
    123
    __delitem__: Delete: x1
    """

    9、__entry__、__exit__

    面向对象上下文管理。

    在创建类的时候,在内部实现__enter__方法,with语句一开始就会执行这个方法, 再实现__exit__方法,退出with代码块的时候会自动执行这个方法。

    class A:
        def __enter__(self):
            print('with语句开始')
            return self  # 返回self就是把这个对象赋值给as后面的变量
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('with语句结束')
    
    
    with A() as f:
        print('IG牛批')
        print(f)
    print('IG真的牛批')
    
    """
    结果:
    with语句开始
    IG牛批
    <__main__.A object at 0x0000027B4D1596D8>
    with语句结束
    IG真的牛批
    """

    10、__hash__

    类的对象可以哈希

    class Foo():
        pass
    
    
    obj1 = Foo()
    obj2 = Foo()
    print(hash(obj1))  # 内存地址 83707816652
    print(hash(obj2))  # 内存地址 97649779271

    哈希算法:

    1. 每次执行hash值都会变化(因为默认是根据内存地址进行哈希)
    2. 但是在一次执行的过程中对同一个值的hash结果总是不变的
    3. 对于相同的值在一次程序的运行中是不会变化的
    4. 对于不同的值在一次程序的运行中总是不同的


    字典为什么寻址快
    因为:
      字典的键是不可变的数据类型,所以是可哈希的,
      字典在存入内存的时候会将你的所有的key先进行哈希,再将哈希值存入内存中,
      这样在查询的时候可以根据哈希值直接就可以找到,所以查询速度很快!


    set的去重机制

    1. 对每一个元素进行hash计算出一个哈希值
    2. 将这个哈希值存到对应的内存上
      如果这块内存中没有值
        将这个元素存到对应的内存地址上
      如果这块内存中已经有值
        判断这两个值是否相等
            如果相等,就舍弃后面的值
                 如果不相等,就二次寻址再找一个新的空间来存储这个值

    例如:100个对象,如果水果名相同的就只留一个

    class Fruit:
        def __init__(self, name, price):
            self.name = name
            self.price = price
    
        def __hash__(self):
            return hash(self.name)
    
        def __eq__(self, other):
            return self.name == other.name
    
    
    obj_lst = []
    name_lst = ['苹果', '香蕉', '葡萄', '榴莲']
    for i in range(100):
        name = name_lst[i % 4]
        obj = Fruit(name, i)
        obj_lst.append(obj)
    
    # 利用set的去重机制
    ret = set(obj_lst)
    # 1、set对每一个元素进行hash计算出一个地址(此时触发__hash__)
    # 2、查看每个地址的值是否相等(此时触发__eq__),相等的,set内部会自动去重。
    
    for i in ret:
        print(i.name, i.price)
    """结果
    苹果 0
    葡萄 2
    香蕉 1
    榴莲 3
    """

    11、_str__和__repr__

    __str__
    当你打印一个对象的时候print(obj) 触发__str__
    当你使用%s格式化的输出对象时候print('%s' %obj) 触发__str__
    str强转数据类型的时候str(obj) 触发__str__

    __repr__
    repr是str的备胎
    直接打印对象,有__str__的时候执行__str__,没有__str__的时候,执行__repr__
    当你使用%r输出对象时候print('%r' %obj) 触发__repr__
    repr强转数据类型的时候repr(obj) 触发__repr__

    注意:__str__ 和__perp__都必须要用return,而且返回值必须是字符串

    # 例子
    class Fruit:
        def __init__(self, name, price):
            self.name = name
            self.price = price
    
        def __str__(self):
            return 'in str:%s的价格是:%s' % (self.name, self.price)
    
        def __repr__(self):
            return 'in repr:%s的价格是:%s' % (self.name, self.price)
    
    
    apple = Fruit('苹果', 5)
    
    # 直接打印对象,有__str__的时候执行__str__,没有__str__的时候,执行__repr__
    print(apple)  # in str:苹果的价格是:5
    
    # 当你使用%s格式化的输出对象时候print('%s' %obj) 触发__str__
    print('%s' % apple)  # in str:苹果的价格是:5
    
    # 当你使用%r输出对象时候print('%r' %obj) 触发__repr__
    print('%r' % apple)  # in repr:苹果的价格是:5
    
    # str强转数据类型的时候str(obj) 触发__str__
    print(str(apple))  # in str:苹果的价格是:5
    
    # repr强转数据类型的时候repr(obj) 触发__repr__
    print(repr(apple))  # in repr:苹果的价格是:5
    
    
    # 升级
    class Fruit:
        def __str__(self):
            return 'Fruit_str'
    
        def __repr__(self):
            return 'Fruit_repr'
    
    
    class Apple(Fruit):
        def __str__(self):
            return 'Apple_str'
    
        def __repr__(self):
            return 'Apple_repr'
    
    
    apple = Apple()
    print(apple)
    # apple是Apple类对象,直接打印,先从Apple类找,有__str__的时候执行Apple的__str__,没有__str__的时候,
    # 从父类去找__str__,父类有就执行,如果父类没有,就找子类的__repr__,有就执行子类的__repr__,没有就去父类找,
    # 父类有就执行,没有就打印对象空间地址
  • 相关阅读:
    统计学——平均数
    JSON与JAVA的数据转换
    Linux使用笔记
    vim代码折叠命令
    让ubuntu的ssh保持长时间连接
    Ubuntu通过xinput禁用及启用联想笔记本的触摸板
    linux下Oracle 相关命令
    linux机械磁盘服务器分区方案
    centos 6.5搭建LNMP环境
    centos6.5下搭建oracle 11g
  • 原文地址:https://www.cnblogs.com/Zzbj/p/15195411.html
Copyright © 2020-2023  润新知