面向对象
一 三个item方法
#item 对象加中括号属性的时候才会调用到这几个内置函数,实至都是在对象或者类中对名称空间进行操作 #__setitem__ 赋予新值的时候会触发这个函数 #__delitem__ 删除的时候会触发这个函数 #__getitem__ 查询的时候会触发这个函数 class People: def __init__(self,name): self.name=name def __getitem__(self, item): print("get") return self.__dict__[item] def __setitem__(self, key, value): print("set") self.__dict__[key]=value def __delitem__(self, key): self.__dict__.pop(key) p=People("egon") p["name"] #触发__getitem__ p["age"]=123 #触发__setitem__ print(p["age"])
二 __next__和__iter__方法实现迭代器协议
#__next__和__iter__方法实现迭代器协议 #示例一个类似range的函数 class Range: def __init__(self,start,end,num): self.start=start self.end=end self.num=num def __iter__(self): return self #迭代器返回的是其本身 def __next__(self): if self.start>=self.end: raise StopIteration n=self.start #设置n是为了初始化self。start的值可以等于0 self.start+=self.num return n r=Range(0,10,3) for i in r: #for可自动帮我捕捉到异常,使得程序不报错 print(i)
三 __doc__
__doc__的属性子类是无法从父类中继承来的
四 __module__,__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
五 __del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。在程序正常走完后,都会自动触发这个__del__函数
class Foo: def __del__(self): print('执行我啦') f1=Foo() # del f1 print('------->') #输出结果 -------> 执行我啦 #为何啊???
六 __enter__,__exit__
with 对象 as f:
with 对象的时候第一时间触发__enter__,并且把__enter__方法的返回值赋给f:
当with里面的所有代码运行完的时候,系统会自动执行__exit__方法,(遇到异常的时候也会触发__exit__方法,如果__exit__返回一个True则继续执行异常后面的代码,反之不执行)
class Foo: def __enter__(self): print("执行enter") def __exit__(self, exc_type, exc_val, exc_tb): return None with Foo() as f: print("=======>") raise TypeError("cuowu") #定义一个异常,用来触发__exit__ print("=======>22222") #__exit__返回一个True的时候执行,不然就不执行
七 __call__
对象后面加括号,触发执行。 注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()() class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 执行 __init__ obj() # 执行 __call__
八 元类
元类必须定义的三个变量,类名,继承的object,名称空间
使用这三个变量轻松定制一个类
def run(): print("runing") class_name="People" bases=(object,) class_dict={ "x":1, #定义一个数据类型 "run":run, #定义一个函数类型 } People=type(class_name,bases,class_dict) print(People)
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?
class Foo: pass f1=Foo() #f1是通过Foo类实例化的对象 #type函数可以查看类型,也可以用来查看对象的类,二者是一样的 print(type(f1)) # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建 print(type(Foo)) # 输出:<type 'type'>
2 什么是元类?
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
3 创建类的两种方式
方式一:
1 class Foo: 2 def func(self): 3 print('from func')
方式二:
1 def func(self): 2 print('from func') 3 x=1 4 Foo=type('Foo',(object,),{'func':func,'x':1})
4 一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)
class Mytype(type): def __init__(self,class_name,bases=None,dict=None): print("Mytype init--->") print(class_name,type(class_name)) print(bases) print(dict) def __call__(self, *args, **kwargs): print('Mytype call---->',self,args,kwargs) obj=self.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Foo(object,metaclass=Mytype):#in python3 #__metaclass__ = MyType #in python2 x=1111111111 def __init__(self,name): self.name=name def __new__(cls, *args, **kwargs): return super().__new__(cls) # return object.__new__(cls) #同上 f1=Foo('name') print(f1.__dict__)
class Mytype(type): def __init__(self,what,bases=None,dict=None): print('mytype init') def __call__(self, *args, **kwargs): obj=self.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Foo(object,metaclass=Mytype): x=1111111111 def __init__(self,name): self.name=name def __new__(cls, *args, **kwargs): return super().__new__(cls) f1=Foo('egon') print(f1.__dict__)
1 #元类总结 2 class Mymeta(type): 3 def __init__(self,name,bases,dic): 4 print('===>Mymeta.__init__') 5 6 7 def __new__(cls, *args, **kwargs): 8 print('===>Mymeta.__new__') 9 return type.__new__(cls,*args,**kwargs) 10 11 def __call__(self, *args, **kwargs): 12 print('aaa') 13 obj=self.__new__(self) 14 self.__init__(self,*args,**kwargs) 15 return obj 16 17 class Foo(object,metaclass=Mymeta): 18 def __init__(self,name): 19 self.name=name 20 def __new__(cls, *args, **kwargs): 21 return object.__new__(cls) 22 23 ''' 24 需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__ 25 26 而爹.__call__一般做两件事: 27 1.调用name.__new__方法并返回一个对象 28 2.进而调用name.__init__方法对儿子name进行初始化 29 ''' 30 31 ''' 32 class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行 33 Foo=Mymeta('foo',(...),{...}) 34 因此我们可以看到,只定义class就会有如下执行效果 35 ===>Mymeta.__new__ 36 ===>Mymeta.__init__ 37 实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作, 38 遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法 39 于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化 40 ''' 41 42 ''' 43 obj=Foo('egon') 44 的原理同上 45 ''' 46 47 ''' 48 总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了 49 1.谁后面跟括号,就从谁的爹中找__call__方法执行 50 type->Mymeta->Foo->obj 51 Mymeta()触发type.__call__ 52 Foo()触发Mymeta.__call__ 53 obj()触发Foo.__call__ 54 2.__call__内按先后顺序依次调用儿子的__new__和__init__方法 55 '''
九 __slots
''' 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 ''' class Foo: __slots__='x' f1=Foo() f1.x=1 f1.y=2#报错 print(f1.__slots__) #f1不再有__dict__ class Bar: __slots__=['x','y'] n=Bar() n.x,n.y=1,2 n.z=3#报错