⑤[类和OOP]

类和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参数(隐含的实例),但是静态方法不是这样。静态方法只是嵌套在类对象中的简单函数。

猜你喜欢

转载自blog.csdn.net/Zhou_ZiZi/article/details/81534028