面向对象进阶

一、isinstance(obj,cls)和issubclass(sub,super)

  isinstance(obj,cls)检查obj是否是类cls的对象

  issubclass(sub,super)检查sub类是否是super类的派生类

class Bar:
    pass
class Foo(Bar):
    pass
f=Foo()
print(Foo.__bases__)
print(isinstance(f,Foo))
print(issubclass(Foo,Bar))  

二、反射

1、什么是反射

  反射就是自省程序把本来没有意义的字符串反射成与之对应的属性先看看自己有没有,有的话再实现要求。

  反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改他本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩

2、Python面向对象中的反射:通过字符串的形式操作对象相关的属性。Python中的一切皆对象(都可以使用反射)

  多了一种属性引用方式,之前是通过“.”现在是通过字符串

  反射是指就是把字符串当做属性,就是通过四个attr把对属性的操作变成对字符串的操作

  四个attr过来过去就是操作的__dict__里面的key_str

# class People:
#     country="China"
#     def __init__(self,name):
#         self.name=name
#     def walk(self):
#         print("%s is walking" %self.name)
# p=People("egon")
#检测是否含有某属性
# print(hasattr(p,"name"))#判断对象p有没有这个属性
#相当于if "name" in p.__dict__

#获取属性,通过__dict__的key值获取value
# x=getattr(p,"name")#由下面的打印结果看,她就相当于p.name
# print(x)#egon,相当于print(p.name)
# y=getattr(p,"xxx","找不到")
# print(y)#找不到,getattr可以自定义返回值

#设置属性,__dict__的增,改
# setattr(p,"name","alex")
# setattr(p,"sb",True)
# setattr(p,"bs",lambda self:self.name+"sb")#可以增加函数属性,是函数属性,而不是绑定方法
# s=getattr(p,"bs")#调用增加的函数属性
# print(s(p))
# print(p.__dict__)#就是加到这个字典里面了

#删除属性
# delattr(p,"bs")#删除函数属性就是删除函数名
# delattr(p,"sb")#删除数据属性
# print(p.__dict__)

# 总结-------------------------------------------------------------
#hasattr(object,"name") #查看,object可以是对象,类,模块,name是属性名
#相当于判断能不能找的到"name" 这个名字,如果可以找到print返回True

# getattr(object,"name",default=None) #获取,object可以是对象,类,模块
# p.name

# setattr(x,"y","v") #修改,x可以是对象,类,模块,y是属性名,v是新值
# p.name="alex"

# delattr(x,"y")#删除,x可以对象,类,模块,y是属性名
# del p.name
# -----------------------------------------------------------------
#反射当前模块的属性
#这个文件为test
#模块就是文件,定义文件也是定义一堆名字,一切皆对象,只要是对象就能.出来名字
#名字就对应属性的概念
import sys
#导入一个模块名
x=1111
class Foo:  #定义一个类名
    pass

def s1():  #定义一个函数名
    print("s1")

def s2():  #定义一个函数名
    print("s2")

# print(__name__)#__main__
# __name__能够用来区分文件(模块)是直接运行(作为脚本)文件还是作为模块被导入
#如果是脚本的话,打印__main__,如果作为模块被导入时,打印模块名
#脚本:把一个文件当成一个独立的程序去运行
#如何在自己模块获取自己模块(要结合上面的__name__知识点去理解)
    #不能再自己模块中导入自己模块
    #下面就是正确用法,需要借助sys模块
this_module=sys.modules[__name__] #这就相当于拿到当前的模块test作为对象
print(this_module)#<module '__main__' from 'D:/py_fullstack_s4/day31/test.py'>
print(hasattr(this_module,"s1"))
print(getattr(this_module,"s2"))

#如何在当前模块调用其他模块(比较简单)
    #比如我现在有两个文件,在同一级目录,test和test1我在test1中调用test会出现下面的情况
#这个文件事test1
#如何在当前模块调用其他模块(比较简单)
    #比如我现在有两个文件,在同一级目录,test和test1我在test1中调用test会出现下面的情况
import test
print(test)#<module 'test' from 'D:\\py_fullstack_s4\\day31\\test.py'>
#会把test模块的完整路径打印出来
#现在我们把test模块作为一个对象
print(test.x)
print(test.s1)
test.s1()
#上边都是作为理解的,都没有说到反射,下面正式加入反射
print(hasattr(test,x))#判断test模块中有没有x属性
print(getattr(test,s1))#获取test中的s1属性

  

3、为什么用反射和反射的好处

  好处一:可插拔机制

  有两个程序员lili和egon,lili在写程序的时候需要egon的类,但是egon没有完成这个类,lili想到了反射,使用反射机制后可以继续完成自己的代码,egon不会影响lili的工作

  总之反射的好处就是,可以事先定义好接口,接口只有再被完成后才会真正的执行,这实现了即插即用,这其实是一种“后期绑定”,什么意思?即你可以实先把主要的逻辑写好(只定义接口),然后后期再去实现接口功能

#egon还没有完成全部功能
#class FtpClient:
#    'ftp客户端,但是还没有实现具体功能'
#    def __init__(self,addr):
#        print('正在连接服务器[%s]' %addr)
#        self.addr=addr
#不影响lili代码的编写
# import ftpclient
# f1 = ftpclient.FtpClient("192.168.1.1")
# if hasattr(f1,"get"): #get属性就是egon还没有完成的功能
#     func_get=getattr(f1,"get")
#     func_get()
# print("不存在此方法")
# print("处理其他逻辑")

  好处二:动态导入模块(基于当前模块)

#通过字符串导入模块
#把字符串变为模块名也算映射
#之前学的
# m=input("请输入您要导入的模块:")#输入time
# m1=__import__(m)
# print(m1)#<module 'time' (built-in)>
# print(m1.time())

#官方推荐的字符串作为模块的导入方法
# import importlib
# m=input("请输入您要导入的模块:")#输入time
# t=importlib.import_module(m) #这里可以用到反射
# print(t)#<module 'time' (built-in)>
# print(t.time())
#------------------------------------------------------------------------------------
# __import__("import_lib.metaclass") #这是解释器自己内部用的
# importlib.import_module("import_lib.metaclass")#与上面这句效果一样,官方建议用这个

  还有个用途

#之前我们学的是判断字典里有没有key,有的话字典["key"]去运行
# def add():
#     print("add")
# def change():
#     print("change")
# def search():
#     print("search")
# def delete():
#     print("delete")
# func_dic={
#     "add":add,
#     "change":change,
#     "search":search,
#     "delete":delete
# }
# while True:
#     cmd=input(">>: ").strip()
#     if not cmd:continue
#     if cmd in func_dic: #hasattr()
#         func=func_dic.get(cmd)  #func=getattr()
#         func()

#######################################################################################################################

#学完面向对象后,可以通过反射搞
#思想是:借用sys,导入当前模块判断输入的字符串在不在模块中(hasattr),在的话执行(getattr获取后运行)
# import sys
# def add():
#     print("add")
# def change():
#     print("change")
# def search():
#     print("search")
# def delete():
#     print("delete")
# this_moudle=sys.modules[__name__]#导入当前模块
# while True:
#     cmd=input(">>: ").strip()
#     if not cmd:continue
#     if hasattr(this_moudle,cmd): #判断有没有cmd
#         func=getattr(this_moudle,cmd) #获取属性
#         func() #运行属性

  

三、内置函数__attr__系列

#这里和之前的那四个东西就不是一回事,前面的是自省针对程序本身的,这个主要用于绑定方法
#三个attr本质上都是对__dict__去操作
#一碰到给对象设置(增,改)属性就会触发__setattr__(self, key, value),加上类型限制,弥补Python没有类型限制的不足
#一碰到给对象删除属性就会触发__delattr__(self, item),加上限制,满足什么条件再删
#属性不存在的情况下才会触发 __getattr__(self, item)
#内置函数都是操作的内置的东西,__attr__操作__dict__

# class Foo:
#     x=1
#     def __init__(self,y):
#         self.y=y
# 
#     def __getattr__(self, item):
#         print('----> from getattr:你找的属性不存在')
# 
# 
#     def __setattr__(self, key, value):
#         print('----> from setattr')
#         # self.key=value #这就无限递归了,你好好想想
#         # self.__dict__[key]=value #应该使用它
# 
#     def __delattr__(self, item):
#         print('----> from delattr')
#         # del self.item #无限递归了
#         self.__dict__.pop(item)
# 
# #__setattr__添加/修改属性会触发它的执行
# f1=Foo(10)
# print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
# f1.z=3
# print(f1.__dict__)
# 
# #__delattr__删除属性的时候会触发
# f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
# del f1.a
# print(f1.__dict__)
# 
# #__getattr__只有在使用点调用属性且属性不存在的时候才会触发f1.xxxxxx

  

四、加工定制自己的数据类型(包装)

  property是用在封装,是往对象中封装属性(数据属性,函数属性),不操作字符串,同时只对一种属性操作,通过对外提供的接口进行实现增改删,针对外部,实质是对隐藏的__dict__进行操作

  __attr__系列是是内置函数,同时操作所有属性,专门操作字符串,程序可以访问、检测和修改他本身状态或行为,针对内部,实质是对__dict__进行操作

  包装:Python为大家提供了标准的数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

 

#基于继承的原理来定制自己的数据类型
#比如我要新建一个数据类型他的append方法只能增加int数据
# class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid
#     def append(self, p_object):
#         ' 派生自己的append:加上类型检查'
#         if not isinstance(p_object,int):
#             raise TypeError('must be int')
#         super().append(p_object)
#     def insert(self, index, p_object):#需要改的自己写,默认的不用管,继承就搞定了
#         ' 派生自己的insert:加上类型检查'
#         if not isinstance(p_object,int):
#             raise TypeError("must be int")
#         super().insert(index,p_object)
#     @property
#     def mid(self):
#         '新增自己的属性'
#         index=len(self)//2
#         return self[index] #查看列表中间的值
#
# l=List([1,2,3,4])
# print(l)
# l.append(5)
# print(l)
# # l.append('1111111') #报错,必须为int类型
# print(l.mid)
# #其余的方法都继承list的
# l.clear()
# print(l)

  授权:授权是包装的一个特性,包装一个类型通常是对已经存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其他的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性

#授权实现定制自己的数据类型
#碰见像文件句柄这种不能用继承的,需要改写的自定制,需要使用默认的用getattr
# import time
# class Open:
#     def __init__(self,filepath,mode="r",encoding="utf8"):
#         self.filepath=filepath
#         self.mode=mode
#         self.encoding=encoding
#         self.x=open(filepath,mode=mode,encoding=encoding)
#     def write(self,line): #需要改写的自定制
#         t=time.strftime("%Y-%m-%d %X")
#         self.x.write("%s %s"%(t,line))
#     def __getattr__(self, item): #需要使用默认的用getattr
#         if hasattr(self.x,item):
#             return getattr(self.x,item)
#
# f=Open("b.txt","w+") #现在的f只是self
# print(f)
# f.write("11111111\n")
# f.write("11111111\n")
# f.write("11111111\n")
# f.write("11111111\n")
# f.write("11111111\n")
# f.seek(0)
# res = f.read()
# print(res)
# f.seek(0)
# print(f.read())
# f.flush()
# f.close()

  

五、__getattribute__

#__getattr__和__getitem__都是找不到才会触发,而这主是找到找不到都会触发
class Foo:
    def __init__(self,x):
        self.x=x

    def __getattribute__(self, item):
        print('不管是否存在,我都会执行')

f1=Foo(10)
f1.x
f1.xxxxxx

  

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

class Foo:
    def __init__(self,x):
        self.x=x

    def __getattr__(self, item):
        print('执行的是我')
        # return self.__dict__[item]
    def __getattribute__(self, item):
        print('不管是否存在,我都会执行')
        raise AttributeError('哈哈')

f1=Foo(10)
f1.x
f1.xxxxxx

#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError

  

六、__setitem__,__getitem__,__delitem__

# 把对象操作属性模拟成字典的格式
# 之前是用.属性名的形式触发__attr__系列,现在是用[key]去触发__item__系列
class Foo:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)

f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)

七、__str__,__repr__,__format__

  改变对象的字符显示__str__,__repr__

  自定制格式化字符串__format__

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
format_dict={
    'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型
    'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址
    'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名
}
class School:
    def __init__(self,name,addr,type):
        self.name=name
        self.addr=addr
        self.type=type

    def __repr__(self):
        return 'School(%s,%s)' %(self.name,self.addr)
    def __str__(self):
        return '(%s,%s)' %(self.name,self.addr)

    def __format__(self, format_spec):
        # if format_spec
        if not format_spec or format_spec not in format_dict:
            format_spec='nat'
        fmt=format_dict[format_spec]
        return fmt.format(obj=self)

s1=School('oldboy1','北京','私立')
print('from repr: ',repr(s1))
print('from str: ',str(s1))
print(s1)

'''
str函数或者print函数--->obj.__str__()
repr或者交互式解释器--->obj.__repr__()
如果__str__没有被定义,那么就会使用__repr__来代替输出
注意:这俩方法的返回值必须是字符串,否则抛出异常
'''
print(format(s1,'nat'))
print(format(s1,'tna'))
print(format(s1,'tan'))
print(format(s1,'asfdasdffd'))

  

date_dic={
    'ymd':'{0.year}:{0.month}:{0.day}',
    'dmy':'{0.day}/{0.month}/{0.year}',
    'mdy':'{0.month}-{0.day}-{0.year}',
}
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

    def __format__(self, format_spec):
        if not format_spec or format_spec not in date_dic:
            format_spec='ymd'
        fmt=date_dic[format_spec]
        return fmt.format(self)

d1=Date(2016,12,29)
print(format(d1))
print('{:mdy}'.format(d1))

  

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

class A:
    pass

class B(A):
    pass

print(issubclass(B,A)) #B是A的子类,返回True

a1=A()
print(isinstance(a1,A)) #a1是A的实例

八、__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#报错

  下面这个例子更详细

class Foo:
    __slots__=['name','age']

f1=Foo()
f1.name='alex'
f1.age=18
print(f1.__slots__)

f2=Foo()
f2.name='egon'
f2.age=19
print(f2.__slots__)

print(Foo.__dict__)
#f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存

九、__next__和__iter__实现迭代器协议

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
class Foo:
    def __init__(self,x):
        self.x=x

    def __iter__(self):
        return self

    def __next__(self):
        n=self.x
        self.x+=1
        return n

f=Foo(3)
for i in f:
    print(i)

  

class Foo:
    def __init__(self,start,stop):
        self.num=start
        self.stop=stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.num >= self.stop:
            raise StopIteration
        n=self.num
        self.num+=1
        return n

f=Foo(1,5)
from collections import Iterable,Iterator
print(isinstance(f,Iterator))

for i in Foo(1,5):
    print(i)

  

#斐波那契数列
class Fib:
    def __init__(self):
        self._a=0
        self._b=1

    def __iter__(self):
        return self

    def __next__(self):
        self._a,self._b=self._b,self._a + self._b
        return self._a

f1=Fib()

print(f1.__next__())
print(next(f1))
print(next(f1))

for i in f1:
    if i > 100:
        break
    print('%s ' %i,end='')

  

#模拟range
class Range:
    def __init__(self,stop,start=0):
        self.start=start
        self.stop=stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.start > self.stop-1:
            raise StopIteration
        n=self.start
        self.start+=1
        return n
r=Range(10)
for i in r:
    print(i)

  

十、__doc__

class Foo:
    '我是描述信息'
    pass

print(Foo.__doc__)

  

class Foo:
    '我是描述信息'
    pass

class Bar(Foo):
    pass
print(Bar.__doc__) #该属性无法继承给子类

  

十一、__module__和__class__

  __module__ 表示当前操作的对象在那个模块

  __class__     表示当前操作的对象的类是什么

class C:

    def __init__(self):
        self.name = ‘SB'

  

from lib.aa import C

obj = C()
print obj.__module__  # 输出 lib.aa,即:输出模块
print obj.__class__      # 输出 lib.aa.C,即:输出类

  

十二、__del__

  析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

class Foo:

    def __del__(self):
        print('执行我啦')

f1=Foo()
del f1
print('------->')

#输出结果
执行我啦
------->
class Foo:

    def __del__(self):
        print('执行我啦')

f1=Foo()
# del f1
print('------->')

#输出结果
------->
执行我啦

'''
总结:
在不考虑Python自动回收的情况下,当对象的引用计数为0时,会先把模块当中的代码运行完然后再
触发__del__,如果我们直接del对象后会直接触发__del__函数,会马上把对象占用的空间回收掉
__del__的函数体一般都是关闭文件或数据库,比如下面的例子
一般用在数据库中,和文件句柄中
'''
import time
class Open:
    def __init__(self,filepath,mode="r",encode="utf8"):
        self.f=open(filepath,mode=mode,encoding=encode)
    def write(self):
        print("sdfsfsdf")
    def __del__(self):
        print("---->del")
        self.f.close()
f=Open("a.txt","w")
f.write()
time.sleep(10)
del f

  

十三、__enter__和__exit__

  我们知道在操作文件对象的时候可以这么写

1 with open('a.txt') as f:
2   '代码块'

  上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

class Open:
    def __init__(self,name):
        self.name=name

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
        # return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')


with Open('a.txt') as f:
    print('=====>执行代码块')
    # print(f,f.name)

  __exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

class Open:
    def __init__(self,name):
        self.name=name

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')
        print(exc_type)
        print(exc_val)
        print(exc_tb)



with Open('a.txt') as f:
    print('=====>执行代码块')
    raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->不会执行

  如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

class Open:
    def __init__(self,name):
        self.name=name

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True



with Open('a.txt') as f:
    print('=====>执行代码块')
    raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->会执行

  用途或者说好处:

  1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

  2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处

 

class Open:
    def __init__(self,filepath,mode="w+",encoding="utf-8"):
        self.filepath=filepath
        self.mode=mode
        self.encoding=encoding
        self.f=open(filepath,mode=mode,encoding=encoding)
    def write(self,line):
        if not isinstance(line,str):
            raise TypeError("%s must be str" %line)
        self.f.write(line)
    def __getattr__(self, item):
        return getattr(self.f,item)
    def __enter__(self):
        return self  #如果是self.f的话write和getattr就没有用了
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        print("exc_type",exc_type)
        print("exc_val",exc_val)
        print("exc_tb",exc_tb)
        return True  #这里如果没有return true的话脚本到这里就崩了,有了他就正常走了
with Open("a.txt") as f:
    f.write("hello")
    f.seek(0)
    print(f.read())
    raise TypeError("我下边的这个prient就不运行了")
    print("hello 看不到我了") #我上变有raise我就永远不会被执行
print("lalallaala")#如果没有return True 我就不会被执行

  

十四、__call__

  对象后面加括号,触发执行。

  注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:

    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 执行 __init__
obj()       # 执行 __call__ 
print(callable(obj))#True
print(callable(Foo))#True
print(callable(Foo()))#True

 

十五、metclass

 1、引子

class Foo:
    pass
print(isinstance(Foo,type))#True
f1=Foo() #f1是通过Foo类实例化的对象

  Python中一切皆对象,类本身也是一个对象,当使用关键字class的时候,Python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非实例的实例)

  上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

# type函数可以查看类型,也可以用来查看对象的类,二者是一样的
print(type(f1))  # 输出:<class '__main__.Foo'>     表示,obj 对象由Foo类创建
print(type(Foo))  # 输出:<type 'type'>  
print(isinstance(Foo,type))  #True,说明类Foo就是type
print(Foo.__class__)  #<class 'type'> 还有什么可说的

 2、什么是元类?

  元类可以用来定制自己的类

  元类是类的类,是类的模板

  元类是用来控制如何创建类的,正如类是创建对象的模板一样

  元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是type类的一个实例)

  type是Python的一个內建元类,用来直接控制生成类,Python中任何class定义的类其实都是type类实例化的对象

 3、创建类的两种方式

 方式一:通过class机制

class Foo:
    def func(self):
        print("from func")

 方式二:通过元类type“实例化”产生类

def func(self):
    print("from func")
x=1
Foo=type('Foo',(objext,),{'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): #这是让实例化出来的类能够callable,是能实例化的基础 print('Mytype call---->',self,args,kwargs) obj=self.__new__(self) self.__init__(obj,*args,**kwargs) return obj #返回的obj就是由type产生的类 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,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(what,bases,dict)

    def __call__(self, *args, **kwargs):
        print('--->')
        obj=object.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj
class Room(metaclass=Mytype):
    def __init__(self,name):
        self.name=name

r1=Room('alex')
print(r1.__dict__)

  

# 元类总结
class Mymeta(type):
    def __init__(self, name, bases, dic):
        print('===>Mymeta.__init__')

    def __new__(cls, *args, **kwargs):
        print('===>Mymeta.__new__')
        return type.__new__(cls, *args, **kwargs)

    def __call__(self, *args, **kwargs):
        print('aaa')
        obj = self.__new__(self)
        self.__init__(self, *args, **kwargs)
        return obj


class Foo(object, metaclass=Mymeta):
    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)


'''
需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__

而爹.__call__一般做两件事:
    1.调用name.__new__方法并返回一个对象
    2.进而调用name.__init__方法对儿子name进行初始化
'''

'''
class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
Foo=Mymeta('foo',(...),{...})
因此我们可以看到,只定义class就会有如下执行效果
===>Mymeta.__new__
===>Mymeta.__init__
实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作,
遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
'''

'''
obj=Foo('egon')
的原理同上
'''

'''
总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
1.谁后面跟括号,就从谁的爹中找__call__方法执行:
    type->Mymeta->Foo->obj
    Mymeta()触发type.__call__
    Foo()触发Mymeta.__call__
    obj()触发Foo.__call__
2.__call__内按先后顺序依次调用儿子的__new__和__init__方法
'''

  

 补充:一道经典的面试题

 

 

posted @ 2017-04-24 20:38  黄土地上的黑石头  阅读(257)  评论(0编辑  收藏  举报