python学习⑤[类和OOP]
类和OOP1.类是在Python实现支持继承的新种类对象的部件。类是Python面向对象程序设计(OOP)的主要工具。2.通过代码建立连接对象树,而每次使用object.attribute表达式时,Python会在运行期间去“爬树”,来搜索属性。3.类提供了所有子类共享的行为,但是因为搜索是由下而上,子类可能会在树中较低位置重新定义超类的变量名,从而覆盖超类定义的行为。4.基类(b...
类和OOP
1.类是在Python实现支持继承的新种类对象的部件。类是Python面向对象程序设计(OOP)的主要工具。
2.通过代码建立连接对象树,而每次使用object.attribute表达式时,Python会在运行期间去“爬树”,来搜索属性。
3.类提供了所有子类共享的行为,但是因为搜索是由下而上,子类可能会在树中较低位置重新定义超类的变量名,从而覆盖超类定义的行为。
4.基类(base class)= 超类,派生类(derived class)= 子类
5.类连接至超类的方式是,将超类列在类头部的括号内。其从左至右的顺序会决定树中的次序。
6.当def出现在类的内部时,通常称为方法,而且会自动接收第一个特殊参数(通常称为self),这个参数提供了被处理实例的参照值。
7.OOP就是在查找连接对象树中的属性。我们将这样的查找称为继承搜索。
8.类方法函数中的第一个参数之所以特殊,是因为它总是接受将方法调用视为隐含主体的实例对象。按惯例,通常称为self。
9.类名称总是存在于模块中。
10.类名称应该以一个大写字母开头。
11.模块反应了整个文件,而类只是文件内的语句。
12.类和模块的第三个主要差别:运算符重载。运算符重载就是让用类写成的对象,可截获并响应用在内置类型上的运算:加法、切片、打印和点号运算等。
13.以双下划线命名的方法(__X__)是特殊钩子
14.当实例出现在内置运算时,这类方法会自动调用。
15.类可覆盖多数内置类型运算
16.运算符覆盖方法没有默认值,而且也不需要。提供了一致性,以及与预期接口的兼容性。
17.除非类需要模仿内置类型接口,不然应该使用更简单的命名方法。
18.__add__返回的内容为+表达式的结果。对于Print,Python把要打印的对象传递给__str__中的self
19.几乎每个实际的类似乎都会出现的一个重载方法是:__init__构造函数
20.每个实例都连接至其类以便于继承,可以使用__class__查看
21.类的__bases__属性,它是其超类的元组。
22.方法函数中的特殊self参加__init__构造函数是Python中OOP的两个基石。
更多实例
在Python中,模块名使用小写字母开头,而类名使用一个大写字母开头,这是通用的惯例。
赋给实例属性第一个值的通常方法是:在__init__构造函数方法中将它们赋给self,构造函数方法包含了每次创建一个实例的时候Python会自动运行的代码。
一个函数定义中,在第一个拥有默认值的参数之后的任何参数,都必须拥有默认值。
步骤1.创建实例
class Person(AttrDisplay):
def __init__(self,name,job=None,pay=0):
self.name=name
self.job=job
self.pay=pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self,percent):
self.pay = int(self.pay *(1+percent))
class Manager(Person):
def __init__(self,name,pay):
Person.__init__(self,name,'mgr',pay)
def giveRaise(self,percent,bonus=.10):
Person.giveRaise(self,percent + bonus)
if __name__ == '__main__':
bob = Person('Bob Smith')
sue = Person('Sue Jones',job='dev',pay=100000)
print(bob)
print(sue)
print(bob.lastName(),sue.lastName())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones',50000)
tom.giveRaise(.10)
print(tom.lastName())
print(tom)
步骤2.添加行为方法
在类之外的硬编码操作可能会导致未来的维护问题。
分币操作:内置函数round(N,2)来舍入并保留分币、使用decimal类型来修改精度,或者把货币值存储为一个完整的浮点数并且使用一个%.2f或{0:2f}格式化字符串来显示它们从而显出分币。
步骤3.运算符重载
__repr__提供对象的一种代码低层级显示。
类提供了一个__str__以便实现用户友好的显示,同时也提供了一个__repr__以便让开发者看到额外的细节。
步骤4.通过子类定制行为
好的方法调用:常规方法instance.method(args...)
python自动地转换为如下形式:class.method(instance,args..)
如果直接通过类来调用,必须手动的传递实例。方法需要一个主体实例,python只是对通过实例调用的方式自动提供实例。通过类名调用的方式,需要自己给self发送一个实例。
步骤5.定制构造函数
OOP机制中重要的概念:
实例创建--填充实例属性。
行为方法--在类方法中封装逻辑。
运算符重载--为打印这样的内置操作提供行为。
定制行为--重新定义子类中方法以使其特殊化。
定制构造函数--为超类步骤添加初始化逻辑。
组合类其他方式:内置函数getattr来拦截未定义属性的访问,并将它们委托给嵌入的对象。
步骤6.使用内省工具
内置的instance.__class__属性提供了一个从实例到创建它的类的链接。类反过来有一个__name__(就像模块一样),还有一个__bases__序列,提供了超类的访问。
内置的object.__dict__属性提供了一个字典,带有一个键/值对
工具类命名考虑:为了减少名称冲突,python程序员常常对于不想做其他用途的方法添加一个单个下划线的前缀。
一种更好的但不太常用的方法是,只在方法名前面使用两个下划线符号:对我们的例子来说就是__gatherAttrs。Python自动扩展这样的名称,以包含类的名称,从而使它们变得真正唯一。这一功能通常叫做伪私有类属性。
class AttrDisplay:
def gatherAttrs(self):
attrs=[]
for key in sorted(self.__dict__):
attrs.append('%s=%s' %(key,getattr(self,key)))
return ','.join(attrs)
def __str__(self):
return '[%s:%s]' %(self.__class__.__name__,self.gatherAttrs())
# if __name__=='__main__':
# class TopTest(AttrDisplay):
# count=0
# def __init__(self):
# self.attr1 = TopTest.count
# self.attr2 = TopTest.count+1
# TopTest.count += 2
# class SubTest(TopTest):
# pass
步骤7.把对象存储到数据库中
pickle模块是一种非常通用的对象格式化和解格式化工具:对于内存中机会任何的Python对象,它都能聪明地把对象转换为字节串,这个字节串可以随后用来在内存中重新构建最初的对象。
Shelve使用pickle把一个对象转换为其pickle化的字符串,并将其存储在一个dbm文件中的键之下;随后载入的时候,shelve通过键获取pickle化的字符串,并用pickle在内存中重新创建最初的对象。
import shelve
db = shelve.open('persondb')
for object in (bob,sue,tom):
db[object.name] =object
db.close()
在shelve中,键可以是任何字符串,包括我们使用诸多处理ID和时间戳(可以在os中和time标准库模块中使用)的工具所创建的唯一字符串。键必须是字符串并且应该是唯一的。
交互式地探索shelve
当前目录下的文件,就是你的数据库。这些文件是你移动存储或备份时候需要复制和转移的内容。
import shelve
db = shelve.open('persondb')
print(len(db))
print(list(db.keys()))
bob =db['Bob Smith']
print(bob)
print(bob.lastName())
for key in db:
print(key,'==>',db[key])
for key in sorted(db):
print(key,'==>',db[key])
可以自由调用方法是因为python对一个类实例进行pickle操作,它记录了其self实例属性,以及实例所创建于的类的名字和类的位置。
更新shelve中的对象
import shelve
db = shelve.open('persondb')
for key in sorted(db):
print(key,'\t=>',db[key])
sue = db['Sue Jones']
sue.giveRaise(.10)
db['Sue Jones']=sue
db.close()
需要多执行几次,可看到改变。
GUI:Tkinter、WxPython和PyQt
Web站点:可以用python自带的基本CGI脚本编程工具来构建,也可以用像Django、TurboGears、Pylons、web2Py、Zope或Google's App Engine这样的全功能第三方web开发框架来完成。
在web上,数据仍然可以存储在shelve、pickle文件或其他基于python的媒介中;处理它的脚本直接自动在服务器上运行,以相应来自Web浏览器和其他客户端的请求,并且它们生产HTML来与一个用户交互,而不是直接或通过框架API与用户交互。
Web服务:尽管web客户端可以屏幕抓取(解析来自web站点的回复中的信息),还是要提供更直接的方法从web获取记录:通过像SOAP或XML-RPC这样一个web服务器接口来调用python自身或第三方开源域所支持的API。
数据库:可从shelve转移到像开源的ZODB面向对象数据库系统(OODB)中,或者像MySQL、Oracle、PostgreSQL或SQLite这样基SQL的数据库系统中。
ORM:如果要迁移到关系数据库中存储,不一定要牺牲python的OOP工具。像SQLObject和SQLAlchemy这样的对象关系映射器(ORM),可以自动实现关系表和行与python的类和实例之间的映射,就可以使用常规的python类语法来处理存储的数据。
注意:复制代码会产生冗余性,当代码改进的时候这是一个主要问题。通用性工具可以避免硬编码解决方案,而后者必须随着时间推移和类的改进保持与类的其他部分同步。
类代码编写细节
1.python的class不是声明式的。就像def一样,class语句是对象的创建者并且是一个隐含的赋值运算--执行时,它会产生类对象,并把其引用值存储在前面所使用的变量名。
class NextClass:
def printer(self,text):
self.message = text
print(self.message)
#实例调用
x = NextClass()
x.printer('abc')
#类调用
NextClass.printer(x,'abc')
2.调用超类构造函数
class Super:
def __init__(self,x):
...default code...
class Sub(Super):
def __init__(self,x,y):
Super.__init__(self,x)
...custom code...
I = Sub(1,2)
3.类接口技术
Super:定义一个method函数以及在子类中期待一个动作的delegate
Inheritor:没有提供任何新的变量名,因此会获得Super中定义的一切内容
Replacer:用自己的版本覆盖Super的method
Extender:覆盖并回调默认method,从而定制Super的method
Provider:实现Super的delegate方法预期的action方法
class Super:
def method(self):
print('in Super.method')
def delegate(self):
self.action()
class Inheritor(Super):
pass
class Replacer(Super):
def method(self):
print('in Replacer.method')
class Extender(Super):
def method(self):
print('starting Extender.method')
Super.method(self)
print('ending Extender.method')
class Provider(Super):
def action(self):
print('in Provider.action')
if __name__ == '__main__':
for klass in (Inheritor,Replacer,Extender):
print('\n'+klass.__name__+'...')
klass().method()
运行结果:
Inheritor...
in Super.method
Replacer...
in Replacer.method
Extender...
starting Extender.method
in Super.method
ending Extender.method
4.python2.6和python3.0的抽象超类
在class头部使用一个关键字参数,以及特殊的@装饰器语法(py3.0)
from abc import ABCMeta,abstractmethod
class Super(metaclass=ABCMeta):
@abstractmethod
def method(self,...):
pass
py2.0
class Super:
__metaclass__ = ABCMeta
@abstractmethod
def method(self,...):
pass
带有一个抽象方法的类是不能继承的(不能调用它来创建一个实例),除非所有抽象方法都已经在子类中定义了。
5.实例和类的特殊属性__class__和__bases__
这些属性可以在程序代码内查看继承层次。例如:显示类树
def classtree(cls,indent):
print('.'*indent + cls.__name__)
for supercls in cls.__bases__:
classtree(supercls,indent+3)
def instancetree(inst):
print('Tree of %s' %inst)
classtree(inst.__class__,3)
def selftest():
class A: pass
class B(A): pass
class C(A): pass
class D(B,C):pass
class E: pass
class F(D,E):pass
instancetree(B())
instancetree(F())
if __name__ =='__main__':
selftest()
Tree of <__main__.selftest.<locals>.B object at 0x0000024B0C6AF3C8>
...B
......A
.........object
Tree of <__main__.selftest.<locals>.F object at 0x0000024B0C6AF3C8>
...F
......D
.........B
............A
...............object
.........C
............A
...............object
......E
.........object
6.文档字符串
出现在各种结构的顶部的字符串常量,由python在相对应对象的__doc__属性自动保存。它适用于模块文件、函数定义,以及类和方法。
docstr.py
"I am:docstr.__doc__"
def func(args):
"I am:docstr.func.__doc__"
pass
class spam:
"I am:spam.__doc__ or docstr.spam.__doc__"
def method(self,arg):
"I am spam.method.__doc__ or self.method.__doc__"
pass
>>> docstr.__doc__
'I am:docstr.__doc__'
>>> docstr.func.__doc__
'I am:docstr.func.__doc__'
>>> docstr.spam.__doc__
'I am:spam.__doc__ or docstr.spam.__doc__'
>>> docstr.spam.method.__doc__
'I am spam.method.__doc__ or self.method.__doc__'
比#缺乏灵活性
运算符重载
构造函数和表达式:__init__和__sub__
class Number:
def __init__(self,start):
self.data = start
def __sub__(self,other):
return Number(self.data - other)
很多重载方法有好几个版本(例如,加法有__add__、__radd__、__iadd__)
常见的运算符重载方法:
__init__(构造函数):对象建立,X = Class(args)
__del__(析构函数):X对象收回
__add__(运算符+):如果没有__iadd__,X + Y,X += Y
__or__(运算符|,位OR):如果没有__ior__,X | Y ,X |= Y
__repr__,__str__(打印、转换):print(X)、repr(X)、str(X)
__call__(函数调用):X(*args,**kargs)
__getattr__(点号运算):X.underfined
__setattr__(属性赋值语句):X.any = value
__delattr__(属性删除):del X.any
__getattribute__(属性获取):X.any
__getitem__(索引运算):X[key],X[i:j],没__iter__时的for循环和其他迭代器
__setitem__(索引赋值语句):X[key] = value,X[i:j] = sequence
__delitem__(索引和分片删除):del X[key],del X[i:j]
__len__(长度):len(X),如果没有__bool__,真值测试
__bool__(布尔测试):bool(X),真测试(py2.6中为__nonzero__)
__lt__,__gt__,__le__,__ge__,__eq__,__ne__:特定值比较(py2.6只有__cmp__)
__radd__(右侧加法):Other + X
__iadd__(实地(增强的)加法):X += Y(or else __add__)
__iter__,__netx__(迭代环境),I=iter(X),next(I);for loops,in if no __contains__,all comprehensions,manp(F,X),其他(__next__在py2.6中称为next)
__contains__(成员关系测试):item in X(任何可迭代的)
__index__(整数值):hex(X),bin(X),oct(X),O[x],O[X:](替代py2.6中的__oct__、__hex__)
__enter__,__exit__(环境管理器):with obj as var:
__get__,__set__,__delete__(描述符属性):X.attr,X.attr = value,del X.attr
__new__(创建):在__init__之前创建对象
1.一般来讲,只有在对象不支持迭代器对象协议的时候(__iter__和__next__)才会尝试索引运算(__getitem__)
迭代器是用来迭代,不是随机的索引运算。迭代器根本没有重载索引表达式。迭代器只循环一次,循环之后就变为空。每次新的循环,都得创建一个新的迭代器对象。
__contains__方法优于__iter__方法,__iter__方法优于__getitem__方法
class Iters:
def __init__(self,value):
self.data = value
def __getitem__(self, i):
print('get[%s]:'%i,end=' ')
return self.data[i]
def __iter__(self):
print('iter=>',end=' ')
self.ix = 0
return self
def __next__(self):
print('next:',end=' ')
if self.ix == len(self.data):raise StopIteration
item = self.data[self.ix]
self.ix += 1
return item
def __contains__(self, x):
print('contains:',end=' ')
return x in self.data
X = Iters([1,2,3,4,5])
print(3 in X)
for i in X:
print(i,end='|')
print()
print([i **2 for i in X])
print(list(map(bin,X)))
I = iter(X)
while True:
try:
print(next(I),end='@')
except StopIteration:
break
2. __setattr__会拦截所有属性的赋值语句。可能会导致无穷的递归循环(最后就是堆栈溢出异常)。所以要通过对属性字典做索引运算来赋值任何势力属性。使用self.__dict__['name']=x,而不是self.name=x
模拟实例属性的私有性
class PrivateExc(Exception):pass
class Privacy:
def __setattr__(self, attrname, value):
if attrname in self.privates:
raise PrivateExc(attrname,self)
else:
self.__dict__[attrname] = value
class Test1(Privacy):
privates = ['age']
class Test2(Privacy):
privates = ['name','pay']
def __init__(self):
self.__dict__['name'] = 'Tom'
x =Test1()
y=Test2()
x.name = 'Bob'
y.name = 'Sue' #fails
y.age = 30
x.age =40 #fails
模拟private 声明。不准在类外对属性名进行修改。
可以使用类装饰器来更加通用的拦截和验证属性。
3.如果没有定义__str__,打印还是使用__repr__。但反之不成立,交互式相应模式,只是使用__repr__,并且根本不要尝试__str__。
str和repr都必须返回字符串。str可能只有当对象出现在一个打印操作顶层的时候才应用,嵌套到较大的对象中的对象可能用其repr或默认方法打印。
>>> class Printer:
def __init__(self,val):
self.val = val
def __str__(self):
return str(self.val)
>>> objs =[Printer(2),Printer(3)]
>>> for x in objs :print(x)
2
3
>>> print(objs)
[<__main__.Printer object at 0x0000014877FA1198>, <__main__.Printer object at 0x000001487801ED30>]
>>> objs
[<__main__.Printer object at 0x0000014877FA1198>, <__main__.Printer object at 0x000001487801ED30>]
>>> class Printer:
def __init__(self,val):
self.val = val
def __repr__(self):
return str(self.val)
>>> objs =[Printer(2),Printer(3)]
>>> for x in objs :print(x)
2
3
>>> print(objs)
[2, 3]
>>> objs
[2, 3]
4.右侧加法和原处加法__radd__和__iadd__
当不同实例混合出现在表达式时,python优先选择左侧那个类。
>>> class Number:
def __init__(self,val):
self.val = val
def __iadd__(self,other):
self.val += other
return self
>>> x = Number(5)
>>> x += 1
>>> x += 1
>>> x.val
7
>>> class Number:
def __init__(self,val):
self.val = val
def __add__(self,other):
return Number(self.val + other)
>>> x = Number(5)
>>> x += 1
>>> x += 1
>>> x.val
7
5.__call__
>>> class Callee:
def __call__(self,*pargs,**kargs):
print('Called:',pargs,kargs)
>>> C = Callee()
>>> C(1,2,3)
Called: (1, 2, 3) {}
>>> C(1,2,3,x=4,y=5)
Called: (1, 2, 3) {'x': 4, 'y': 5}
当需要为函数的API编写接口时,__call__可以编写遵循所需要的函数来调用接口对象,同时又能保留状态信息。
注:tkinter GUI工具箱(在py2.6中是Tkinter)可以把函数注册成事件处理器(也就是回调函数call back)。当事件发生时,tkinter会调用已注册的对象。如果想让事件处理器保存事件之间的状态,可以注册类的绑定方法(bound method)或者遵循所需接口的实例(使用__call__)。
import tkinter
top = tkinter.Tk()
class Callback:
def __init__(self,color):
self.color = color
def __call__(self):
print('turn',self.color)
cb1 = Callback('blue')
cb2 = Callback('green')
B1 = tkinter.Button(top,text='点我变蓝',command=cb1)
B2 = tkinter.Button(top,text='点我变绿',command=cb2)
B1.pack()
B2.pack()
top.mainloop()
6.比较:__lt__、__gt__
比较方法没有右端形式。
7.真值测试:__bool__、__len__
python3中 喜欢__bool__胜过__len__,因为它更具体。
8.对象析构函数:__del__
实例产生时,会调用__init__。实例空间被收回时,__del__会自动执行。
类的设计
1.类的OOP实现概括为:继承、多态、封装
python中的多态是基于对象接口的,而不是类型。
2.重访流处理器
编写类,使用组合机制工作,来提供更强大的结构并支持继承。
streams.py定义了一个转换器方法。
class Processor:
def __init__(self,reader,writer):
self.reader = reader
self.writer = writer
def process(self):
while 1:
data =self.reader.readline()
if not data:break
data = self.converter(data)
self.writer.write(data)
def converter(self,data):
assert False,'converter must be defined'
converters.py在子类提供转换器逻辑。
from streams import Processor
class Uppercase(Processor):
def converter(self,data):
return data.upper()
if __name__=='__main__':
import sys
obj = Uppercase(open('spam.txt'),sys.stdout)
obj.process()
3.pickle机制把内存中的对象转换成序列化的字节流,可以保存在文件中,也可以通过网络发送出去。解除pickle状态则是从字节流转换回同一个内存中的对象,Shelve也类似。但是它会自动把对象pickle生成按键读取的数据库,而此数据库会导出类似于字典的接口。
4.OOP和委托:‘包装’对象
委托(delegation),通常就是指控制器对象内嵌其他对象,而把运算请求传给那些对象。常以__getattr__钩子方法实现,因为这个方法会拦截对不存在属性的读取,包装类(有时称为代理类)可以使用__getattr__把任意读取转发给被包装的对象。包装类包有被包装对象的接口,而且自己也可以增加其他运算。
class wrapper:
def __init__(self,object):
self.wrapped = object
def __getattr__(self, attrname):
print('Trace:',attrname)
return getattr(self.wrapped,attrname)
getattr(X,N)就像X.N,只不过N是表达式,可在运行时计算出字符串,而不是变量。
getattr(X,N)类似于X.__dict__[N],但前者也会执行继承搜索,就像X.N,,而getattr(X,N)则不会。
5.类的伪私有属性
变量名压缩(mangling,相当于扩张),让类内某些变量局部化。主要是为了避免实例内的命名空间的冲突,而不是限制变量名的读取。所以是伪私有。编写内部名称(_X)。
变量名压缩只发生在class语句内,而且只针对开头有两个下划线的变量名。(self.__X会变成self._Spam(类名)__X)
6.方法是对象:绑定或无绑定
class Spam:
def doit(self,message):
print(message)
#绑定
object1 = Spam()
x = object1.doit
x('hello world')
#无绑定
object2 =Spam()
t = Spam.doit
t(object2,'howdy')
py2.6中无绑定方法默认的需要传递一个实例,3.0这样的方法会当作一个简单的函数,不需要一个实例
7.用__dict__列出实例属性
class ListInstance:
def __str__(self):
return '<Instance of %s,address %s:\n%s>'%(self.__class__.__name__,
id(self),self.__attrnames())
#通过id内置函数显示了实例的内存地址。
def __attrnames(self):
result = ''
for attr in sorted(self.__dict__):
result += '\tname %s=%s\n'%(attr,self.__dict__[attr])
return result
#使用伪私有命名模式:__attrnames
class Spam(ListInstance):
def __init__(self):
self.data1 = 'food'
x =Spam()
print(x)
8.用dir列出继承的属性
class ListInstance:
def __str__(self):
return '<Instance of %s,address %s:\n%s>'%(self.__class__.__name__,
id(self),self.__attrnames())
def __attrnames(self):
result = ''
for attr in dir(self):
if attr[:2] == '__' and attr[-2:] =='__':
result += '\tname %s=<>\n'%attr
else:
result += '\tname %s=%s\n'%(attr,getattr(self,attr))
return result
#使用getattr内置函数来获取属性
9.钻石继承变动
对经典类而言,继承搜索程序是绝对深度优先,然后才是由左至右。
在新式类中,搜索相对来说是宽度优先的。python先寻找第一个搜索的右侧的所有超类,然后才一路往上搜索至顶端共同的超类。
10.新式类的扩展
将字符串属性名称顺序赋值给特殊的__slots__类属性,新式类就有可能既限制类的实例将有的合法属性集,又能优化内存和速度性能。
Slot对于python动态特性来说是一种违背,而动态特性要求任何名称都可以通过赋值来创建。
11.类特性
特性(property)机制,提供另一种方式让新式类定义自动调用的方法,来读取或赋值实例属性。
特性一般都是在class语句顶层赋值[例如:name=property(...)]。这样赋值时,对类属性本身的读取(例如,obj.name),就会自动传给property的一个读取方法。
12.__getattribute__方法只适用于新式类,可以让类拦截所有属性的引用,而不局限于未定义的引用(如果__getattr__)
13.静态方法大致与一个类中的简单的无实例函数类似地工作,类方法传递一个类而不是一个实例。
python现在支持三种类相关方法:实例、静态和类。
类方法可能更适合处理对层级中的每个类不同的 数据。
14.函数装饰器(function decorator)提供了一种方式,替函数明确了特定的运算模式,也就是将函数包裹了另一层,在另一函数的逻辑内实现。
类装饰器类似于函数装饰器,但是在一条class语句的末尾运行,并且把一个类名重新绑定到一个可调用对象。
元类是一种类似于基于类的高级工具,其用途往往与类装饰器有所重合。它们提供了一个可选的模式,会把一个类对象的创建导向到顶级type类的一个子类,在一条class语句的最后。
元类通常重新定义type类的__new__或__init__方法,以实现对一个新的类对象的创建初始化的控制。
15.类(和类实例)是可改变的对象。就像内置列表和字典一样,可以给类属性赋值,冰洁进行再原处的修改,同时意味着修改类或实例对象,也会影响对它的多处引用。
由于类属性由所有实例共享,所以如果一个类属性引用一个可变对象,那么从任何实例来原处修改该对象都会立刻影响到所有实例。
16.正常(实例)方法会接受第一个self参数(隐含的实例),但是静态方法不是这样。静态方法只是嵌套在类对象中的简单函数。
更多推荐
所有评论(0)