元类
现在我们都应该知道一个对象的类型(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__ 方法
- 类的__dict__会把类的所有属性对应的值和方法的地址以字典的形式打印出来
- 对象的__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__
- __setitem__:每当属性使用 ["xx"] 赋值的时候都会调用该方法,因此不能再该方法内再次赋值,比如 self[key] = value 会死循环
- __getitem__:当使用 X.["xx"] 访问属性时会调用该方法
- __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
哈希算法:
- 每次执行hash值都会变化(因为默认是根据内存地址进行哈希)
- 但是在一次执行的过程中对同一个值的hash结果总是不变的
- 对于相同的值在一次程序的运行中是不会变化的
- 对于不同的值在一次程序的运行中总是不同的
字典为什么寻址快
因为:
字典的键是不可变的数据类型,所以是可哈希的,
字典在存入内存的时候会将你的所有的key先进行哈希,再将哈希值存入内存中,
这样在查询的时候可以根据哈希值直接就可以找到,所以查询速度很快!
set的去重机制
- 对每一个元素进行hash计算出一个哈希值
- 将这个哈希值存到对应的内存上
如果这块内存中没有值
将这个元素存到对应的内存地址上
如果这块内存中已经有值
判断这两个值是否相等
如果相等,就舍弃后面的值
如果不相等,就二次寻址再找一个新的空间来存储这个值
例如: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__,没有就去父类找, # 父类有就执行,没有就打印对象空间地址