---恢复内容开始---
类和实例
类对象来自于语句,而实例来自于调用。每次调用一个类,就会得到这个类的新的实例。
- class语句创建类对象并将其赋值给变量名。class语句是可执行语句,执行时会产生新的类对象,并将其赋值给class头部的变量名。
- class语句内的赋值语句会创建类的属性。
- 类属性提供对象的状态和行为。类中的def函数会生成方法,方法将会处理实例。
当调用类对象时,就可以生成实例对象。
- 像函数那样调用类对象会创建新的实例对象。
- 每个实例对象继承类的属性并获得了自己的命名空间。
- 在方法内对self属性对赋值运算会产生每个实例自己的属性。在类方法函数内,第一个参数(即self)会引用正处理的实例对象。对self的属性做赋值运算,会创建或修改实例内的数据,而不是类的数据。
例1:
class FirstClass: # define a class object def setdata(self, value): # define class methods self.data = value # self is the instance def display(self): print(self.data) # self.data: per instance
代码分析:
- class语句开头一行会列出类的名称,后面再接一个或多个内嵌并且缩进的语句的主体。(在这里,嵌套的语句是def,定义类要实现导出的行为的函数)
- def其实是赋值运算,此处是将函数对象赋值给变量名setdata,而且display位于class语句范围内,因此会产生附加在类上的属性:FirstClass.setdata 和 FirstClass.display。在类嵌套的代码块中顶层的赋值的任何变量名,都会变成类的属性。
- 位于类中的函数通常称为方法,方法是普通def,在方法函数中,调用时,第一个参数自动接收隐含的实例对象:调用的主体。
用一些实例来解释这个类:
x = FirstClass()
y = FirstClass()
以这种方式调用类时(注意小括号),会产生实例对象,也就是可读取类属性的命名空间。确实地讲,此时有三个对象:两个实例和一个类。以OOP的观点来看,x是一个FirstClass对象,y也是。命名空间如下:
X.data -----------------FirstClass.setdata FirstClass.display
Y.data------------------FirstClass.setdata FirstClass.display
data属性会在实例内找到,但setdata和display则是在它们之上的类中找到。如果对实例以及类对象内的属性名称进行点号运算,python会通过继承搜索从类取得变量名:
x.setdata('King Arthur') # call methods: self is x
x本身没有setdata属性,python在调用这个方法时,会顺着实例到类的连接搜索,这就是继承。继承是在属性点号运算时发生的,而且只与查找连接对象内的变量名有关。
在FirstClass的setdata函数中,传入的值会赋给self.data,在方法中,self会自动引用正在处理的实例(x),所以赋值语句会把值储存在实例的命名空间,而不是类的命名空间。
在类内时,通过方法内对self进行赋值运算,在类外时,通过对实例对象进行赋值运算。(通过在类方法函数外对变量名进行赋值运算,我们甚至可以在实例外命名空间内产生全新的属性)。
类通常是对self参数进行赋值运算从而建立实例的所有属性的,但不是必须如此。
逻辑的修改是通过创建子类,而不是修改超类。
例2:
class SecondClass(FirstClass): #从FirstClass继承了setdata def display(self): # 在子类中修改了display print('Current value = %s' % self.data)
因为子类SecondClass中重新定义了display方法,而继承搜索会从实例往上执行,之后到子类,再之后到超类,直到所找的属性名称首次出现为止,所以SecondClass中的display会覆盖掉FirstClass中的display。这种在树中较低处发生的重新定义的、取代属性的动作称为重载。
z = SecondClass() z.setdata(42) z.display() # prints ''Current value = '42'''
其中:z.data从实例z中找到,z.display从子类SecondClass中找到,z.setdata从超类FirstClass中找到。
而SecondClass引入的专有化完全是在FirstClass外部完成的,所以不会影响存在的或未来修改的FirstClass对象。所以类所支持的扩展和重用比函数或模块更好。
例3:
class ThirdClass(SecondClass): def __init__(self, value): # on 'ThirdClass(value)' self.data = value def __add__(self, other): # on 'self + other' return ThirdClass(self.data + other) def __str__(self): # on 'print(self)', 'str()' return '[ThirdClass: %s]' % self.data def mul(self, other): # in-place change: names self.data *= other a = ThirdClass('abc') # 调用__init__ a.display() # 从SecondClass继承display方法,打印出'Current value = 'abc'' print(a) # 调用__str__ 返回'[ThirdClass: abc]' b = a + 'xyz' # 调用__add__,并产生一个新的实例b b.display() # 新实例b拥有ThirdClass的所有方法,打印出'Current value = 'abcxyz'' print(b) # 调用__str__,返回'[ThirdClass: abcxyz]' a.mul(3) # 原地修改实例 print(a) # 返回'[ThirdClass: abcabcabc]'
代码分析: ThirdClass是一个SecondClass对象,所以其实例会继承SecondClass的display方法,但是ThirdClass生成的调用会传递一个参数给__init__构造函数中的value,并将其赋值给self.data。直接效果是ThirdClass计划在构建时自动设置data属性,而不是在构建之后请求setdata调用。
此外,ThirdClass对象现在出现在‘+’表达式的print调用中,对于‘+’, python把左侧的实例对象传给__add__中的self参数,而把右边的值传给other。对于print,python把要打印的对象传递给__str__中的self,该方法返回的字符串看做是对象的打印字符串。
最常见的重载方法是__init__构造函数,这个函数可以让类立即在新建的实例内添加属性。
例4:
class rec: pass # empty namespace object rec.name = 'Bob' # 在class语句外,通过变量赋值变量名给类增加属性 rec.age = 40 print(rec.name) # 返回 Bob 。此时类还没有实例,类本身也是对象,是独立的命名空间。 x = rec() y = rec() # 建立两个实例 x.name y.name # 返回'Bob'。因继承获取附加在类上的属性 x.name = 'sue' # 如果把一个属性赋值给一个实例,就会在该对象内修改这个属性。 rec.name # 'Bob' x.name # 'sue' y.name # 'Bob' 属性赋值运算只会影响属性赋值所在的对象,所以x得到自己的name,而y依然继承附加在它的类上的name
类的__x__内部名称集合:
rec.__dict__.keys() # ['__module__', 'name', 'age', '__dict__', '__weakref__', '__doc__'] list(x.__dict__.keys()) # ['name'] list(y.__dict__.keys()) # [] x.__class__ # 查看x的连接所继承的类<class '__main__.rec'> rec.__bases__ # 显示rec的超类的元组(<class 'object'>,)
即使是方法(通常是在类中通过def创建)也可以独立地在任意类对象的外部创建:
def upperName(self): return self.name.upper() # 需要传入一个self参数 upperName(x) # 传入一个带有name属性的x,返回'sue'
若把这个函数的赋值成类的属性,就会变成方法,可以由任何实例调用:
rec.method = upperName x.method() # 返回'sue'。调用方法处理x y.method() # 返回'Bob' rec.method(x) # 返回'sue'。
总结:
- 类属性的创建是通过把属性赋值给类对象实现的,通常是由class语句中的顶层赋值语句产生(如class中def的setdata或display等,setdata等即成为了类属性),也可以于任何引用类对象的地方对其属性赋值创建类属性。
- 实例属性的创建是通过对实例对象赋值属性来创建的,通常是在class语句中对self参数赋值属性而创建的(如setdata中的self.name = name,name即成为了实例属性),也可以在外部引用实例参过赋值语句来创建属性。一般来说,所有实例属性都是在__init__构造函数中初始化的。
- self通常是给与类方法函数中的第一个参数的名称,python会自动填入实例对象。
- 运算符重载是通过__x__这种特定名称的方法实现的。常见的__init__构造函数与__str__显示函数。