面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用
类 是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中)
对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数
面向对象三大特性:封装、继承和多态
本篇将详细介绍Python 类的成员、成员修饰符、类的特殊成员。
类的成员
类的成员可以分为三大类:字段、方法和属性
注:
所有成员中,只有普通字段的内容保存在对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。
而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中值创建一份。
一、字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
普通字段属于对象
静态字段属于类
classProvince:#静态字段
#静态字段执行的时候,类还没有创建呢,所以不可能放在对象里面,所以保存在类中。
country = '中国'
def __init__(self, name):#普通字段
self.name =name#直接访问普通字段
obj = Province('河北省')printobj.name#直接访问静态字段
Province.country
字段的定义和使用
由上述代码可以看出【普通字段需要通过对象来访问】【静态字段通过类访问】,在使用上可以看出普通字段和静态字段的归属是不同的。其在内容的存储方式类似如下图:
由上图可知:
静态字段在内存中只保存一份
普通字段在每个对象中都要保存一份
静态字段的应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段
不使用静态字段的情况
class Provice:
# country = "中国"
def __init__(self, name, country):
self.name = name
self.country = "中国"
# 每一个都要写一遍 "中国" ,相同的东西存在了多份,没有必要
# 并且会在内存中保存多次,浪费
hebei = Provice("河北", "中国")
print(hebei.name , hebei.country)
henan = Provice("河南", "中国")
print(henan.name , henan.country)
输出结果:
河北 中国
河南 中国
使用静态字段的情况
class Provice:
#写一个静态字段,让对象自己去调用它,并且只保存了一份,节约空间
country = "中国"
def __init__(self, name):
self.name = name
hebei = Provice("河北")
henan = Provice("河南")
print(henan.name)
print(Provice.country) #调用静态字段
输出结果:
河南
中国
补充:访问成员的规范
自己去访问自己的成员(类Provice中定义的静态字段就由它自己去调用),除了类中的方法(类中的方法要先创建对象,再用对象去掉用)
# 在python中是可以“胡乱”调用的
class Provice:
country = "中国"
def __init__(self, name):
self.name = name
def show(self):
print('show')
obj = Provice('ciri')
r = obj.country
print(r)
输出结果:中国
# 可以看出,用对象也是可以调用类中的静态字段的
Provice.show(obj) #因为没有创建对象,不会自动传self参数,所以手动写上对象obj
输出结果:show
#可以看出同样,用类也可以调用类中的方法
在python中是支持这四种操作的(类调用方法、类调用字段、对象调用字段、对象调用方法),但是java和c#是严格按照规范来写的,所以为了让自己的代码更条理,还是按照规范来写
二、方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都属于类,区别在于调用方式不同
普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类赋值给cls;
静态方法:由类调用;无默认参数;
classFoo:def __init__(self, name):
self.name=namedeford_func(self):"""定义普通方法,至少有一个self参数"""
#print self.name
print '普通方法'@classmethoddefclass_func(cls):"""定义类方法,至少有一个cls参数"""
print '类方法'@staticmethoddefstatic_func():"""定义静态方法 ,无默认参数"""
print '静态方法'
#调用普通方法
f =Foo()
f.ord_func()#调用类方法
Foo.class_func()#调用静态方法
Foo.static_func()
方法的定义和使用
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点:方法调用者不同、调用方法时自动传入的参数不同。
静态方法的应用场景
class Provice:
def __init__(self,name):
self.name = name
def show(self):
print('show')
#静态方法
@staticmethod #python的内置函数,用来装饰类中的方法。只要一装饰,就变成了静态方法
def xo(): #没有self参数
print('xo')
# 执行静态方法,通过类调用
Provice.xo()
#通过对象也可以,但是要遵守规范,不这么写
obj = Provice('ciri') #加上括号,执行构造方法(init),所以要传个参数
obj.xo()
静态方法的应用:
普通方法想要被执行,要先创建一个对象,然后才能执行;但是静态方法不需要创建对象,直接用类执行,就相当于一个函数放到了类里面。在Java和c#里,可以把静态方法当函数使用,也算是有个函数式编程的办法了。
def denglu(name):
print(name + '登陆成功')
class web:
@staticmethod
def login(name):
print(name + "登陆成功")
#执行函数
denglu('ciri')
#执行静态方法
web.login('ellie')
输出结果:
ciri登陆成功
ellie登陆成功
类方法
作用:就是能获取执行的方法的类名
class Provice:
def __init__(self,name):
self.name = name
def show(self):
print('show')
# 类方法:至少要有一个cls参数,cls全称class
@classmethod
def xxoo(cls): #cls 会把当前类的类名传到cls里面
print('xxoo',cls)
# 类方法也是通过类进行访问
Provice.xxoo()
输出结果:
xxoo
三、属性
Python中的属性其实是普通方法的变种。可以说是完全没什么用处,但有些人写代码的时候用它装逼,还是了解一下,防止看源码的时候看不懂
1、属性的基本使用
################ 定义 ###############
classFoo:deffunc(self):pass
#定义属性
@propertydefprop(self):pass
################ 调用 ###############
foo_obj =Foo()
foo_obj.func()
foo_obj.prop#调用属性
属性的定义和使用
由属性的定义和调用要注意一下几点:
定义时,在普通方法的基础上添加@property 装饰器;
定义时,属性仅有一个self参数
调用时,无需括号
方法:foo_obj.func()
属性:foo_obj.prop
注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回,用方法也能办到,但是属性能去括号。haha!
2、属性的操作
对于字段来说,可以获取也能修改
classProvice:def __init__(self,name):
self.name=name
obj= Provice('alex')print(obj.name)
obj.name= "123" #对于字段来说,能获取也能修改
print(obj.name)
字段的取值和修改
因为特性的本质是讲方法伪造成字段,所以也能进行获取和修改
classFoo:def __init__(self):
self.name= 'a'@propertydefper(self):print('123')return 1obj=Foo()
r=obj.perprint(r)
输出结果:123
1
@property
classFoo:def __init__(self):
self.name= 'a'
#用于执行obj.per
@propertydefper(self):print('123456')#用于接收赋值给per的值
@per.setterdefper(self,val):print(val)
@per.deleterdefper(self):print(666)
obj=Foo()
obj.per= 123
delobj.per
输出结果:123456
123
666
@XXX.setter & @XXX.deleter
其实 @property 和 obj.per 是一种对应关系,同理 @per.setter 和obj.per = 123 以及 @per.deleter 和 del obj.per 也是一种对应关系看到了这样的代码,类中寻找对应的格式,并不执行实际操作。
像 del obj.per并不会执行删除操作,想要执行删除操作,就在对应的@property、@XXX.setter、@XXX.deleter 方法中写上指定的功能。并且也可以在 @property 方法中,写删除的操作,这个没有规定。
实例:根据用户输入的页码去显示数据
li =[]for i in range(10000):
li.append(i)whileTrue:#数据不会全部显示出来,一般的网站都会分页显示
p = input("请输入要查看的页码:") #每页10条
p =int(p)#1 0 - 10
#2页 10-20
#3页 20-30
start = (p-1)*10 #输入1 0-10,输入2 10-20,输入30 290-300
end = p * 10
print(li[start:end])
函数式编程
classPergination:def __init__(self,current_page):try:
p=int(current_page)exceptException as e:
p= 1self.page=int(current_page)defstart(self):
val= (self.page - 1) * 10
returnvaldefend(self):
val= self.page * 10
returnval
li=[]for i in range(10000):
li.append(i)whileTrue:
p= input("请输入要查看的页码:")
obj=Pergination(p)print(li[obj.start(),obj.end()])
面向对象(正常的代码)
因为最后的输出 print(li[obj.start(),obj.end()]) 太乱了,可以用 属性 让代码看上去不那么乱。
classPergination:def __init__(self,current_page):try:
p=int(current_page)exceptException as e:
p= 1self.page=int(current_page)
@propertydefstart(self):
val= (self.page - 1) * 10
returnval
@propertydefend(self):
val= self.page * 10
returnval
li=[]for i in range(10000):
li.append(i)whileTrue:
p= input("请输入要查看的页码:")
obj=Pergination(p)#print(li[obj.start(),obj.end()]) #看上去很凌乱,所以用属性
print(li[obj.start,obj.end]) #骗自己,看上去就不凌乱了
面向对象(属性)
这个实例非常详细的解释了属性的用处,去括号,是的就这么没用。并且什么时候都只有去括号这一个用处!!!!
3、属性的另一种写法
上面的例子是用@property的写法,下面介绍另一种写法
class Foo:
def f1(self):
return 123
per = property(fget=f1) #这里的f1就是上面的f1方法
obj = Foo()
r = obj.per
print(r)
class Foo:
# 上面两句代码就相当于下面的代码
@property
def per(self):
return 123
同理它也有@XXX.setter 、@XXX.deleter相对应的功能
classFoo:deff1(self):print('ciri')deff2(self,v):print(v)deff3(self):print('del')
per= property(fget=f1,fset=f2,fdel=f3)
obj=Foo()
obj.per
obj.per= 123
delobj.per#同理也是一一对应的关系
fget、fset、fdel
函数的参数最多有三个,还可以加一个注释参数,
per = property(fget=f1,fset=f2,fdel=f3,doc="我是注释,啦啦啦!!!!")
注意:Python WEB框架 Django 的视图中 request.POST 就是使用的静态字段的方式创建的属性
classWSGIRequest(http.HttpRequest):def __init__(self, environ):
script_name=get_script_name(environ)
path_info=get_path_info(environ)if notpath_info:#Sometimes PATH_INFO exists, but is empty (e.g. accessing
#the SCRIPT_NAME URL without a trailing slash). We really need to
#operate as if they'd requested '/'. Not amazingly nice to force
#the path like this, but should be harmless.
path_info = '/'self.environ=environ
self.path_info=path_info
self.path= '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/'))
self.META=environ
self.META['PATH_INFO'] =path_info
self.META['SCRIPT_NAME'] =script_name
self.method= environ['REQUEST_METHOD'].upper()
_, content_params= cgi.parse_header(environ.get('CONTENT_TYPE', ''))if 'charset' incontent_params:try:
codecs.lookup(content_params['charset'])exceptLookupError:pass
else:
self.encoding= content_params['charset']
self._post_parse_error=Falsetry:
content_length= int(environ.get('CONTENT_LENGTH'))except(ValueError, TypeError):
content_length=0
self._stream= LimitedStream(self.environ['wsgi.input'], content_length)
self._read_started=False
self.resolver_match=Nonedef_get_scheme(self):return self.environ.get('wsgi.url_scheme')def_get_request(self):
warnings.warn('`request.REQUEST` is deprecated, use `request.GET` or'
'`request.POST` instead.', RemovedInDjango19Warning, 2)if not hasattr(self, '_request'):
self._request=datastructures.MergeDict(self.POST, self.GET)returnself._request
@cached_propertydefGET(self):#The WSGI spec says 'QUERY_STRING' may be absent.
raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '')return http.QueryDict(raw_query_string, encoding=self._encoding)################ 看这里看这里 ###############
def_get_post(self):if not hasattr(self, '_post'):
self._load_post_and_files()returnself._post################ 看这里看这里 ###############
def_set_post(self, post):
self._post=post
@cached_propertydefCOOKIES(self):
raw_cookie= get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')returnhttp.parse_cookie(raw_cookie)def_get_files(self):if not hasattr(self, '_files'):
self._load_post_and_files()returnself._files################ 看这里看这里 ###############
POST =property(_get_post, _set_post)
FILES=property(_get_files)
REQUEST= property(_get_request)
Django源码
通过这句来执行不同的函数 -- POST = property(_get_post, _set_post)
obj.POST执行 _get_post
obj.POST = 123执行_set_post
成员小节
规则:自己去访问自己的成员,除了类中的方法
通过类访问的有:静态字段、静态方法、类方法
通过对象访问:普通字段、普通方法、属性
类成员的修饰符
对于每一个类的成员而言,都有两种形式:
共有成员,在任何地方都能访问
私有成员,只有在类的内部才能访问
私有成员和公有成员的定义不同:私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)
class Foo:
def __init__():
self.name = '公有字段'
self.__name = '私有字段'
私有成员和公有成员的访问的限制不同:
静态字段
公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
私有静态字段:仅内部可以访问
classC:
name= "公有静态字段"
deffunc(self):printC.nameclassD(C):defshow(self):printC.name
C.name#类访问
obj=C()
obj.func()#类内部可以访问
obj_son=D()
obj_son.show()#派生类中可以访问
公有静态字段
classC:__name = "公有静态字段"
deffunc(self):print C.__name
classD(C):defshow(self):print C.__nameC.__name #类访问 ==> 错误
obj=C()
obj.func()#类内部可以访问 ==> 正确
obj_son=D()
obj_son.show()#派生类中可以访问 ==> 错误
私有静态字段
普通字段
公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
私有普通字段:仅内部可以访问
classC:def __init__(self):
self.foo= "公有字段"
deffunc(self):print self.foo #类内部访问
classD(C):defshow(self):printself.foo # 派生类中访问
obj=C()
obj.foo#通过对象访问
obj.func() #类内部访问
obj_son=D();
obj_son.show()#派生类中访问
共有普通字段
classC:def __init__(self):
self.__foo = "私有字段"
deffunc(self):print self.foo #类内部访问
classD(C):defshow(self):printself.foo # 派生类中访问
obj=C()
obj.__foo #通过对象访问 ==> 错误
obj.func() #类内部访问 ==> 正确
obj_son=D();
obj_son.show()#派生类中访问 ==> 错误
私有普通字段
PS:如果想要强制访问私有字段,可以通过【对象._类名__私有字段名】访问(如obj._C__Foo),不建议这么用,忘记即可
class Foo:
def __init__(self):
self.__name = "ciri"
obj = Foo()
print(obj._Foo__name)
方法、属性的访问和上述方式相似,即:私有成员只能在类内部使用
ps:强制访问私有方法,私有属性
class Foo:
__ox = "ox"
def __init__(self):
self.__name = "ciri"
def __fetch(self):
print('私有普通方法')
@staticmethod
def __sta():
print('私有静态方法')
@property
def __per(self):
print('私有属性')
@__per.setter
def __per(self,val):
print(val)
obj = Foo()
print(obj._Foo__name) #强制访问私有字段
print(obj._Foo__ox) #强制访问静态私有字段
obj._Foo__fetch() #强制访问私有普通方法
obj._Foo__sta() #强制访问私有静态方法
obj._Foo__per #强制访问私有属性
obj._Foo__per = '赋值的对应'
类的特殊成员
上文介绍了Python的类成员以及成员修饰符,从而了解到类中有字段、方法和属性三大类成员,并且成员名前如果有两个下划线,则表示该成员是私有成员,私有成员只能由类内部调用。
无论人或事物往往都有不按套路出牌的情况,Python的类成员也是如此,存在着一些具有特殊含义的成员,详情如下:
1.__doc__
表示类的描述信息
class Foo:
"""我是类的描述信息"""
def func(self):
pass
print Foo.__doc__
输出:
我是类的描述信息
2.__module__ 和 __class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
#!/usr/bin/env python#-*- coding:utf-8 -*-
classC:def __init__(self):
self.name= 'wupeiqi'
我是被下面代码调用的文件,路径lib/aa.py
from lib.aa importC
obj=C()print obj.__module__ #输出 lib.aa,即:输出模块
print obj.__class__ #输出 lib.aa.C,即:输出类
index.py
3. __init__
构造方法.通过类创建对象时,自动触发执行。
class Foo:
def __init__(self, name):
self.name = name
self.age = 18
obj = Foo('ciri') # 加括号会自动执行类中的 __init__ 方法
4. __del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。但是对象什么时候被销毁是不知道的,所以用处不大。
class Foo:
def __del__(self):
pass
5. __call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
print('init')
def __call__(self, *args, **kwargs):
print('call')
r = Foo() #执行init方法
r() #对象后面加括号,只执行call
Foo()() #先执行init后执行call
输出:
init
call #对象加括号执行一个
init
call
6. __dict__
返回类或对象中的所有成员
上文中我们知道:类的普通字段属于对象;类中的静态字段和方法等属于类,即:
class Foo:
"""
我是类的解释
"""
def __init__(self,name,age):
self.name = name
self.age = age
self.n = 123
#以字典的形式返回,对象中封装的内容
obj = Foo('ciri',18)
print(obj.__dict__)
输出结果:
{'name': 'ciri', 'age': 18, 'n': 123}
#返回类里面的可见成员
ret = Foo.__dict__
print(ret)
输出结果:
{
'__module__': '__main__',
'__doc__': '\n 我是类的解释\n ',
'__init__': ,
'__dict__': ,
'__weakref__':
}
7. __int__、__str__
如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
class Foo:
def __init__(self):
pass
def __int__(self):
return 1111
def __str__(self):
return "ciri"
obj = Foo()
print(obj,type(obj))
# int后面加对象,会自动执行对象的__int__方法,并将返回值自动赋值给int的对象
r = int(obj)
print(r)
# str后面加对象,会自动执行对象的__str__方法,并将返回值自动赋值给str的对象
i = str(obj)
print(i)
__int__方法几乎用不到,__str__方法常用,比如:
class Foo:
def __init__(self,n,a):
self.name = n
self.age = a
# 将返回值写成类中都有哪些对象,
def __str__(self):
return "%s-%s" % (self.name , self.age)
obj = Foo('ciri',18)
print(obj)
输出结果:
ciri-18
print()怎么就执行了str?因为print内部执行了两个步骤
步骤一:内部执行print(str(obj))
步骤二:str(obj)在内部又会调用obj中的__str__并获取返回值
8、__getitem__、__setitem__、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo:
def __init__(self,name,age):
self.name = name
self.age = age
def __getitem__(self, item):
return item + 10
def __setitem__(self, key, value):
print(key,value)
def __delitem__(self, key):
print(key)
li = Foo('alex',18)
# 自动执行li对象类中的__getitem__方法,8当做参数传递给item
r = li[8] #索引格式和getitem是对应关系
print(r)
li[100] = "asdf" #和setitem是对应关系
del li[999] #和delitem是对应关系
上面的代码,就只有getitem方法有返回值,因为获取值要有东西接受它。但是设置和删除没必要。对应语法和方法名仅仅是个对应关系。
Python3中用__getitem__进行切片操作,当然对切片的操作比较少,用的多的还是索引。
classFoo():def __init__(self,name,age):
self.name=name
self.age=agedef __getitem__(self, item):print(item,type(item))
li= Foo('ciri',16)
li[123]
li[1:4:3] #把这三个值封装到对象里面了,再把对象传进去
输出结果:123 slice(1, 4, 3)
通过封装切片,对切片进行操作
classFoo():def __init__(self,name,age):
self.name=name
self.age=agedef __getitem__(self, item):#通过判断来区别是传入的参数是索引还是切片
#如果item是基本类型:str,int,则为索引
#如果是slice对象,这么切边类型
if type(item) ==slice:print("内部做切片处理")else:print("内部做索引处理")
li= Foo('ciri',16)
li[123]
li[1:4:3]
输出结果:
内部做索引处理
内部做切片处理
如何对切片和索引进行不同的操作?
classFoo():def __init__(self,name,age):
self.name=name
self.age=agedef __getitem__(self, item):if type(item) ==slice:print("内部做切片处理")print(item.start) #开头
print(item.stop) #结尾
print(item.step) #步长
else:print("内部做索引处理")
li= Foo('ciri',16)
li[123]
li[1:4:3]
如何拿到slice对象里面的值
slice对象中用start,stop,step来取切片中对应的开头,结尾和步长
9、__getslice__、__setslice__、__delslice__
python2中特有的,python3中还是用__getitem__操作列表
该三个方法用于分片操作,如:列表
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class Foo(object):
def __getslice__(self, i, j):
print '__getslice__',i,j
def __setslice__(self, i, j, sequence):
print '__setslice__',i,j
def __delslice__(self, i, j):
print '__delslice__',i,j
obj = Foo()
obj[-1:1] # 自动触发执行 __getslice__
obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__
del obj[0:2] # 自动触发执行 __delslice__
10. __iter__
用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 __iter__
回顾之前的内容:
什么是迭代器?
满足两个条件:1.有iter方法 2.有next方法
for循环后面加的是什么?
可迭代对象
什么是可迭代对象?
现象上——能进行 for循环的,都是可迭代对象
本质上——内部有iter方法的,就是可迭代对象
创建和执行迭代器
li = [1,2,3,4]
d = iter(li) #iter方法就做了一件事情,返回了一个迭代器对象
print(next(d))
print(next(d))
须知:
i = [11,22,33]
i = iter([11,22,33])
for item in i :
print(item)
对于for循环
遇到迭代器,可以直接循环
遇到可迭代对象,执行对象的类中__iter__方法获取迭代器,然后进行循环
定义:如果类中有__iter__方法,创建的对象就是可迭代对象
对象.__iter__()方法的返回值,是一个迭代器
__iter__方法的使用
class Foo:
def __init__(self,name,age):
self.name = name
self.age = age
def __iter__(self):
return iter([11,22,33]) #要返回一个迭代器
li = Foo('alex', 18)
for i in li:
print(i)
11. __new__ 和 __metaclass__
class Foo(object):
def __init__(self):
pass
obj = Foo() # obj是通过Foo类实例化的对象
上述代码中,obj 是通过 Foo 类实例化的对象,其实,不仅 obj 是一个对象,Foo类本身也是一个对象,因为在Python中一切事物都是对象。所以:
obj是对象,是Foo类的对象
Foo类也是一个对象,是type的对象
如果按照一切事物都是对象的理论:obj对象是通过执行Foo类的构造方法创建,那么Foo类对象应该也是通过执行某个类的 构造方法 创建。
print type(obj)
# 输出: 表示,obj 对象由Foo类创建
print type(Foo)
# 输出: 表示,Foo类对象由 type 类创建
所以,obj对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类对象 是通过type类的构造方法创建。
那么,创建类就可以有两种方式:
a). 普通方式
class Foo(object):
def func(self):
print 'ciri'
b).特殊方式(type类的构造函数)
def function(self):
print 'ciri'
Foo = type('Foo',(object,), {'func': function})
#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员
所以,类是由 type类实例化产生
那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?
答:类中有一个属性 __metaclass__,其用来表示该类由 谁 来实例化创建,所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看 类 创建的过程。同时也因为type是由C语言底层实现,在python中看不到源码,所以也需要间接去证明这一点。
创建一个MyType类,去继承type类,让Foo类创建的时候,指定一下,让MyType去创建。这样,就可以通过验证MyType去验证type的作用。
问题:为什么MyType要继承type类?
因为要继承type类创建类的功能,是类能够顺利创建出来。就是因为创建类需要type的init方法,继承了这个方法后创建Foo类的时候就可以调用这个方法,假如说不继承type类,就不知道怎么去创建类了。
class MyType(type):
def __init__(self,*args,**kwargs): #只要init方法被调用,就会输出123
print(123)
class Foo(object,metaclass=MyType): #表示该类由MyType实例化创建
def func(self):
print("ciri")
输出结果:
123
可以看创建Foo类,执行了MyType的init方法
由此验证:创建了一个类,要调用type的init方法
代码继续往下执行
class MyType(type):
def __init__(self,*args,**kwargs):
print(123)
def __call__(self, *args, **kwargs):
print(456)
class Foo(object, metaclass=MyType):
def __init__(self):
pass
def func(self):
print("ciri")
obj = Foo()
输出结果:
123
456
# 可以看出,Foo加括号执行了MyType的call方法
因为对象后面加括号会执行call方法,以及Foo类是MyType的对象。
所以Foo类后面加括号即Foo(),会执行MyType类的__call__方法
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
pass
obj = Foo()#执行init方法
obj()#执行call方法
执行到class Foo(object,metaclass)是调用init方法,执行到obj = Foo()调用call方法,此时class Foo(object,metaclass)里面的内容还没有被执行。要想执行里面的内容,要在call方法中调用里面的东西(__new__方法,在new方法中,真正的创建了obj)
#将程序写成这样是为了方便理解,并不能够运行,因为new并没有创建方法
class MyType(type):
def __init__(self,*args,**kwargs):
print(123)
def __call__(self, *args, **kwargs):
#self = Foo self就是Foo
r = self.__new__() #因为self就是Foo,所以self.__new__()就是Foo.__new__(),所以这句就是去Foo里执行new方法
print(r)
class Foo(object, metaclass=MyType):
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
return "对象" #这里返回的就是obj
def func(self):
print("ciri")
obj = Foo()
补充:MyType里的self参数是Foo,并不是obj,因为执行的时候obj还没有创建出来。
图片中的__metaclass__ = MyType是2.7中的写法
classMyType(type):def __init__(self, what, bases=None, dict=None):
super(MyType, self).__init__(what, bases, dict)def __call__(self, *args, **kwargs):
obj= self.__new__(self, *args, **kwargs)
self.__init__(obj)classFoo(object):__metaclass__ =MyTypedef __init__(self, name):
self.name=namedef __new__(cls, *args, **kwargs):return object.__new__(cls, *args, **kwargs)#第一阶段:解释器从上到下执行代码创建Foo类#第二阶段:通过Foo类创建obj对象
obj = Foo()
与图对应的代码
最后关于这记住两点就可以了
init执行之前还执行好几个方法
如果想自己定义一个type,搞一个mytype就可以了