Python语法查缺补漏 第三章:面向对象
一、类和对象的概念
与C、C++等语言不同,Python中提供的基本数据类型,如int、float等,也是类。
二、类的属性
1. 属性的定义、访问及更改
class C:
num = 1 # 定义类C中的num属性
c = C()
C.num = 2 # 更改类中的属性
print(C.num, c.num)
输出为:
1 2
class C:
num = 1 # 定义类C中的num属性
c = C()
c.num = 3
C.num = 2 # 更改类属性
print(C.num, c.num) # 类属性访问
输出为:
2 3
即,当实例化的对象中的属性被更改后,就与类中该属性的值无关了。
2. 动态绑定新属性
Python作为一种动态语言,除了可以在定义类时指定类属性外,还可以动态的为已经创建的对象绑定新的属性。
class C:
num = 1
c = C()
c.name = 'Lihua' # 加入新的属性name
print(c.name)
输出为:
Lihua
三、类中的方法
类中的方法分为两类:普通方法和内置方法。
- 普通方法需要通过类的实例对象根据方法名调用。
- 内置方法是在特定情况下由系统自动执行的。
自己定义的函数都是普通方法,如下所示。
class Student:
name = 'Unknown'
def getname(self, newname):
self.name = newname
四、私有属性
私有属性,是指在类内可以直接访问,而在类外无法直接访问的属性。Python中规定,在定义类的时候,如果一个类属性名是以__(两个下划线)开头,则该类属性为私有属性。
实际上,Python中并不存在无法访问的私有属性。如果我们在类中定义了一个私有属性,则在类外访问该私有属性时需要在私有属性名前加上“_类名”(单下划线)。此原理是编译器在发现有私有属性时,会做个小trick,将其名称改为“_类名__私有属性名”。
class Student:
__id = 123
student = Student()
# print(student.__id) # 如果取消注释则会报错
print(student._Student__id)
五、构造与析构
1. 构造函数
构造方法是Python类中的内置方法之一,它的方法名为__init__,在创建一个类对象时会自动执行,负责完成新创建对象的初始化工作。
2. 析构函数
析构方法是类的另一个内置方法,它的方法名为__del__,在销毁一个类对象时会自动执行,负责完成待销毁对象的资源清理工作,如关闭文件等。
类对象销毁有如下三种情况:
- 局部变量的作用域结束;
- 使用del删除对象;
- 程序结束时,程序中的所有对象都将被销毁。
注意: 如果多个变量对应同一片内存空间,则只有这些变量都删除后才会销毁这片内存空间中所保存的对象,也才会自动执行析构方法。
class C:
def __init__(self, id):
self.id = id
def __del__(self):
print('Have deleted c%d'%self.id)
c1 = C(1)
c2 = C(2)
c3 = c2
del c2
del c1
del c3
输出为:
Have deleted c1
Have deleted c2
六、其他常用内置方法
1. __str__方法
调用str函数对类对象进行处理时或者调用Python内置函数format()和print()时会被自动调用执行,__str__方法的返回值必须是字符串。
如print一个numpy对象时会返回其内存地址,就是调用了numpy类中的__str__方法。
class Complex:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __str__(self):
return str(self.real) + '+' + str(self.imag) + 'j'
c = Complex(1, 2)
print(c)
2. 比较运算的内置方法
内置方法 | 功能描述 |
---|---|
__gt__(self, other) | 进行self>other运算时自动执行 |
__lt__(self, other) | 进行self<other运算时自动执行 |
__ge__(self, other) | 进行self>=other运算时自动执行 |
__le__(self, other) | 进行self<=other运算时自动执行 |
__eq__(self, other) | 进行self==other运算时自动执行 |
__ne__(self, other) | 进行self!=other运算时自动执行 |
class Complex:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __gt__(self, other):
norm_self = self.real ** 2 + self.imag ** 2
norm_other = other.real ** 2 + other.imag ** 2
return norm_self > norm_other
c1 = Complex(1, 2)
c2 = Complex(1, 1)
print(c1 > c2)
七、继承
1. 子类的定义
继承时可以在括号中可以添加多个父类。如就一个父类,则为单继承;否则为多重继承。
class Person:
def Setname(self, name):
self.name = name
class Student(Person): # 单继承
def Setid(self, idnum):
self.idnum = idnum
class Teacher(Person): # 单继承
def Setsub(self, subject):
self.subject = subject
class TA(Student, Teacher): # 多重继承
def Setsal(self, salary):
self.salary = salary
s = TA()
s.Setname('Lihua')
s.Setid('10010')
s.Setsub('Math')
s.Setsal(1000)
print(s.name, s.idnum, s.subject, s.salary)
2. 方法重写
方法重写是指子类可以对从父类中继承过来的方法进行重新定义,从而使得子类对象可以表现出与父类对象不同的行为。
多态,是指在执行同样代码的情况下,系统会根据对象实际所属的类去调用响应类中的方法。
class Person:
def __init__(self, name):
self.name = name
def printinfo(self):
print(self.name)
class Student(Person):
def __init__(self, name, sno):
self.name, self.sno = name, sno
def printinfo(self): # 同样的函数名,但会执行不同的效果
print(self.name, self.sno)
p = Person('George')
s = Student('Bruce', 10010)
p.printinfo()
s.printinfo()
3. 鸭子类型
在鸭子类型中,关注的不是对象所属的类,而是一个对象能够如何使用。
在Python中编写一个函数,传递实参前其参数的类型并不确定,在函数中使用形参进行操作时只要传入的对象能够支持该操作程序就能正常执行。
实际上,Python中的多态也是借助鸭子类型实现的,与C++、Java等语言中的多态并不是同一含义。
class C1:
def Print(self):
print('This is C1')
class C2:
def Print(self):
print('This is C2')
def showPrint(c):
c.Print()
c1 = C1()
c2 = C2()
showPrint(c1)
showPrint(c2)
4. super方法
super方法用于获取父类的代理对象,以执行已在子类中被重写的父类方法。
super方法有两个参数:第一个参数是要获取父类代理对象的类名;第二个参数如果传入对象名,则该对象所属的类必须是第一个参数指定的类或该类的子类,找到的父类对象的self会绑定到这个对象上;如果传入类名,则该类必须是第一个参数指定的类的子类。
在一个类A的定义中调用super方法时,可以将两个参数都省略,此时super()等价于super(A, self),即获取A的父类代理对象中的self绑定到当前A类对象的self上。
class Person:
def __init__(self, name):
print('Person类的对象已被实例化')
self.name = name
class Student(Person):
def __init__(self, name, sno):
print('Student类的对象已被实例化')
super().__init__(name) # 调用父类的构造方法
# super(Student, self).__init__(name) # 与以上语句效果相同
self.sno = sno
class Postgraduate(Student):
def __init__(self, name, sno, tutor):
print('Postgraduate类的对象已被实例化')
super().__init__(name, sno) # 调用父类的构造方法
# super(Postgraduate, self).__init__(name, sno) # 与以上语句效果相同
self.tutor = tutor
p = Postgraduate('Lihua', 10010, 'Zhangwei')
print('名字:%s,学号:%d,导师:%s'%(p.name, p.sno, p.tutor))
输出结果为:
Postgraduate类的对象已被实例化
Student类的对象已被实例化
Person类的对象已被实例化
名字:Lihua,学号:10010,导师:Zhangwei
5. 判断子类、父类的内置函数
函数 | 用法 |
---|---|
isinstance(对象, 指定类) | 判断一个对象所属的类是否是指定类或指定类的子类 |
issubclass(类, 指定类) | 判断一个类是否是另一个类的子类 |
type(对象) | 获取一个对象所属的类 |
class Father:
pass
class Son(Father):
pass
class Flower():
pass
son = Son()
print(isinstance(son, Son), isinstance(son, Father), isinstance(son, Flower))
print(issubclass(Son, Son), issubclass(Son, Father), issubclass(Son, Flower)) # 需要注意,一个类是其本身的子类
print(type(son))
输出为:
True True False
True True False
<class ‘main.Son’>
八、类方法与静态方法
1. 类方法
类方法是指使用@classmethod修饰的方法,其第一个参数是类本身(而不是类的实例对象)。
类方法的特点是既可以通过类名直接调用,也可以通过类的实例对象调用。
class Complex:
def __init__(self, real=0, imag=0):
self.real = real
self.imag = imag
@classmethod # 声明以下是类方法
def add(cls, c1, c2): # cls是类方法中自带参数
print(cls) # 从输出结果可以看出cls是Complex类
c = Complex()
c.real = c1.real + c2.real
c.imag = c1.imag + c2.imag
return c
c1 = Complex(1.5, 2.5)
c2 = Complex(1.7, 3.1)
c = Complex.add(c1, c2) # 可以使用类直接调用类方法
# c = c1.add(c1, c2) # 也可以使用对象来调用类方法
# c = Complex().add(c1, c2) # 创建一个新的父类对象来调用类方法也是可以的
print('%.1f+%.1fi'%(c.real, c.imag))
输出为:
<class ‘main.Complex’>
3.2+5.6i
2. 静态方法
静态方法是指使用@staticmethod修饰的方法。
与类方法相同,静态方法既可以通过类名直接调用,也可以通过类的实例对象调用。
与类方法不同的地方在于,静态方法中没有类方法中的第一个类参数,即无隐含参数。除此之外,在使用上静态方法和类方法都是一样的。
class Complex:
def __init__(self, real=0, imag=0):
self.real = real
self.imag = imag
@staticmethod
def add(c1, c2): # 无默认参数cls了
c = Complex()
c.real = c1.real + c2.real
c.imag = c1.imag + c2.imag
return c
c1 = Complex(1.5, 2.5)
c2 = Complex(1.7, 3.1)
c = Complex.add(c1, c2) # 可以使用类直接调用类方法
# c = c1.add(c1, c2) # 也可以使用对象来调用类方法
# c = Complex().add(c1, c2) # 创建一个新的父类对象来调用类方法也是可以的
print('%.1f+%.1fi'%(c.real, c.imag))
九、动态扩展类
Python作为一种动态语言,除了可以在定义类时定义属性和方法外,还可以动态地为已经创造的对象绑定新的属性和方法。
1. 动态绑定新方法
再给对象绑定方法时,需要使用types模块中的MethodType方法,其第一个参数是需要绑定的函数名,第二个参数是绑定的对象名。给一个对象绑定方法后,只能通过该对象调用此方法,其他未绑定此方法的对象则不能调用;而给一个类绑定方法后,整个类的所有实例对象都有该方法。
from types import MethodType
class C:
pass
def check(self):
print('Hello World!')
c1, c2 = C(), C()
c1.check = MethodType(check, c1) # 为c1对象绑定check方法
c1.check()
# c2.check() # 此语句会报错,因为c2没有被绑定check函数,而C类中也没有check函数
C.check = MethodType(check, C) # 为C类绑定check方法
c2.check()
2. __slots__变量
在定义类时,Python提供了__slots__变量以限制可动态扩展的属性。
注意: __slots__中所做的动态扩展属性限制只对__slots__所在类的实例对象有效。如果子类中没有__slots__定义,则子类的实例对象可以进行任意属性的动态扩展;如果子类中有__slots__定义,则子类的实例对象可动态扩展的属性包括子类中通过__slots__定义的属性和其父类中通过__slots__定义的属性。
class Person:
__slots__ = ('name') # Person类只可以动态扩展name属性
class Student(Person):
__slots__ = ('sno') # Student类只可以动态扩展name(从父辈的__slots__里继承)和sno属性
class Postgraduate(Student):
pass # 本身没定义__slots__变量,则可以任意动态扩展
stu, pg = Student(), Postgraduate()
stu.sno = 10010
stu.name = 'Lihua'
# stu.tutor = 'George' # tutor不在__slots__变量中,取消注释则会报错
pg.tutor = 'George'
十、@property装饰器
类中的属性可以直接访问和赋值,这为类的使用者提供了方便,但也带来了问题:类的使用者可能会给一个属性赋上超出有效范围的值。
为了解决这个问题。Python提供了@property装饰器,可以将类中属性的访问和赋值操作自动转为方法调用,这样可以在方法中对属性值的取值范围做一些条件限定。直接使用@property就可以定义一个用于获取属性值的方法(即getter)。
如果要定义一个设置属性值的方法(setter),则需要使用名字“@属性名.setter”的装饰器。如果一个属性只有用于获取属性值的getter方法,而没有用于设置属性值的setter方法,则该属性是一个只读属性,只允许读取该属性的值,而不能设置该属性的值。
class Student:
@property # 用#property装饰器定义一个用于获取score值的方法
def score(self): # 函数名要与要获得的值的名称一样
return self._score # 要加下划线
@score.setter
def score(self, score): # 函数名要与要获得的值的名称一样
if score < 0 or score > 100:
print('成绩必须在0~100之间!')
else:
self._score = score # 赋值
@property
def age(self):
return self._age
stu = Student()
stu.score = 80
stu.score = 120 # 只会输出报错提示,而不会真正改变score的值
# stu.age = 19 # 只定义了getter方法,但没有定义setter方法,所以此语句会报错
print(stu.score)
输出为:
成绩必须在0~100之间!
80