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] 的方式去赋值给对象。
  2. __setitem__:每当属性使用 ["xx"] 赋值的时候都会调用该方法,因此不能再该方法内再次赋值,比如 self[key] = value 会死循环
  3. __getitem__:当使用 X.["xx"] 访问属性时会调用该方法
  4. __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  # 如果这里不设置值,那么这个对象[xx]就不会设置生效

    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__ 和__repr__都必须要用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__,没有就去父类找,
# 父类有就执行,没有就打印对象空间地址

 

posted @ 2021-08-27 22:12  我用python写Bug  阅读(260)  评论(0编辑  收藏  举报