Python 3 – 面向对象编程
Python 自诞生就是一种面向对象的语言。因此,创建和使用类以及对象非常容易。本章将帮助您成为使用 Python 面向对象编程支持的专家。
如果您之前没有任何面向对象编程 (OO) 的经验,那么您可能需要在引入课程或者至少是一些教程上查看一些基本概念的介绍。
不过,在这里提供一个小小的面向对象编程 (OOP) 介绍来帮助您 −
OOP 术语概述
- 类(Class) − 一个定义了一组用于描述该类任何对象的属性的用户自定义的原型。这些属性是数据成员(类变量和实例变量)和方法,可通过点符号访问。
-
类变量(Class Variable) − 一种被所有该类实例共享的变量。类变量在类内部定义,但不在任何类方法内部。使用类变量的频率不如实例变量高。
-
数据成员(Data Member) − 类变量或实例变量,保存与类及其对象相关的数据。
-
函数重载(Function Overloading) − 为特定函数分配多个行为。所执行的操作因涉及的对象或参数类型而变化。
-
实例变量(Instance Variable) − 一种定义在方法内部并且属于类的当前实例的变量。
-
继承(Inheritance) − 将一种类的特性传递给从它派生的其他类。
-
实例(Instance) − 某个类的个体。例如,属于类 Circle 的对象 obj 是类 Circle 的一个实例。
-
实例化(Instantiation) − 创建一个类的实例。
-
方法(Method) − 在类定义中定义的一种特殊类型的函数。
-
对象(Object) − 由其类定义的数据结构的独特实例。一个对象包括数据成员(类变量和实例变量)和方法。
-
运算符重载(Operator Overloading) − 为特定运算符分配多个函数。
创建类
class 语句创建一个新的类定义。类的名称紧随关键词 class 后面,后面是一个冒号,如下所示 −
class ClassName:
'可选的类文档字符串'
class_suite
- 类具有文档字符串,可以通过 _ClassName.__doc__ _ 访问。
-
_class_suite _ 包括定义类成员、数据属性和函数的所有组件语句。
示例
下面是一个简单 Python 类的示例−
class Employee:
'Common base class for all employees'
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print ("Total Employee %d" % Employee.empCount)
def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)
- 变量 empCount 是一个类变量,其值在此类的所有实例之间共享。可以从类内部或类外部访问此变量,如 Employee.empCount 。
-
第一个方法 init() 是一个特殊方法,称为类构造函数或初始化方法。Python在创建此类的新实例时调用此方法。
-
您可以像普通函数一样声明其他类方法,唯一的异常是每个方法的第一个参数为 self 。Python为您将 self 参数添加到列表中;在调用方法时无需包含它。
创建实例对象
要创建类的实例,请使用类名调用类,并传递其 init 方法接受的任何参数。
#这会创建Employee类的第一个对象
emp1 = Employee("Zara", 2000)
#这会创建Employee类的第二个对象
emp2 = Employee("Manni", 5000)
访问属性
您可以使用对象的点运算符访问对象的属性。使用类名作为以下形式访问类变量:
emp1.displayEmployee()
emp2.displayEmployee()
print("Total Employee %d"% Employee.empCount)
现在,将所有概念放在一起 –
#!/usr/bin/python3
class Employee:
'所有员工的基本类'
empCount = 0
def __init__(self, name ,salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print("Total Employee %d"% Employee.empCount)
def displayEmployee(self):
print("Name: ", self.name, ", Salary: ", self.salary)
#这会创建Employee类的第一个对象
emp1 = Employee("Zara", 2000)
#这会创建Employee类的第二个对象
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print("Total Employee %d"% Employee.empCount)
当执行以上代码时,将会显示以下结果 –
Name: Zara , Salary: 2000
Name: Manni , Salary: 5000
Total Employee 2
您可以随时添加、删除或修改类和对象的属性 –
emp1.salary = 7000 #添加一个'salary'属性。
emp1.name = 'xyz' #修改'age'属性。
del emp1.salary #删除'age'属性。
与使用常规语句访问属性不同,您可以使用以下函数 –
- getattr(obj,name [,default]) - 用于访问对象的属性。
-
hasattr(obj,name) - 用于检查属性是否存在。
-
setattr(obj,name,value) - 用于设置属性。如果属性不存在,则会创建它。
-
delattr(obj,name) - 用于删除属性。
hasattr(emp1,'salary' ) #如果'salary'属性存在,则返回True
getattr(emp1,'salary' ) #返回'salary'属性的值
setattr(emp1,'salary',7000) #将属性“工资”设置为7000
delattr(emp1,'salary' ) #删除属性“工资”。
内置类属性
每个Python类都保留以下内置属性,并且可以像其他属性一样使用点运算符进行访问 –
- __dict__ − 包含类的命名空间的字典。
-
__doc__ − 类的文档字符串或 None(如果未定义)。
-
__name__ − 类名。
-
__module__ − 类定义的模块名称。该属性在交互模式下为 “main“。
-
__bases__ − 一个可能为空的元组,其中包含基类,按其在基类列表中的出现顺序。
对于上面的类,让我们尝试访问所有这些属性 –
#!/usr/bin/python3
class Employee:
'Common base class for all employees'
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print ("Total Employee %d" % Employee.empCount)
def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)
emp1 = Employee("Zara", 2000)
emp2 = Employee("Manni", 5000)
print ("Employee.__doc__:", Employee.__doc__)
print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__ )
当执行上面的代码时,它会产生以下结果 –
Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {
'displayCount': <function Employee.displayCount at 0x0160D2B8>,
'__module__': '__main__', '__doc__': 'Common base class for all employees',
'empCount': 2, '__init__':
<function Employee.__init__ at 0x0124F810>, 'displayEmployee':
<function Employee.displayEmployee at 0x0160D300>,
'__weakref__':
<attribute '__weakref__' of 'Employee' objects>, '__dict__':
<attribute '__dict__' of 'Employee' objects>
}
销毁对象(垃圾收集)
Python自动删除不需要的对象(内置类型或类实例)以释放内存空间。Python定期恢复不再使用的内存块的过程称为垃圾收集。
Python的垃圾收集器在程序执行期间运行,并在对象的引用计数达到零时触发。当指向它的别名的数量发生变化时,对象的引用计数发生变化。
当对象被分配一个新名称或放置在容器(列表、元组或字典)中时,它的引用计数增加。当对象被删除时,其引用被重新分配或其引用超出范围时,对象的引用计数减少。对象的引用计数达到零时,Python会自动收集它。
a = 40 # 创建对象 <40>
b = a # 增加 <40> 的引用计数
c = [b] # 增加 <40> 的引用计数
del a # 减少 <40> 的引用计数
b = 100 # 减少 <40> 的引用计数
c[0] = -1 # 减少 <40> 的引用计数
当垃圾收集器销毁一个孤立的实例并回收其空间时,通常不会注意到。但是,类可以实现特殊方法 del() ,称为析构函数,在实例即将被销毁时调用。此方法可用于清理实例使用的任何非内存资源。
例子
这个 __del__()
析构函数打印即将被销毁的实例的类名 –
#!/usr/bin/python3
class Point:
def __init__( self, x=0, y=0):
self.x = x
self.y = y
def __del__(self):
class_name = self.__class__.__name__
print(class_name, "被销毁")
pt1 = Point()
pt2 = pt1
pt3 = pt1
print(id(pt1), id(pt2), id(pt3)) # 打印对象的 id
del pt1
del pt2
del pt3
当以上代码执行时,它会产生以下结果 −
140338326963984 140338326963984 140338326963984
Point 被销毁
注意 − 理想情况下,你应该在一个单独的文件中定义类,然后使用 import 语句在主程序文件中导入它们。
在上面的例子中,假设Point类的定义包含在 point.py 文件中,而且其中没有其他可执行代码。
#!/usr/bin/python3
import point
p1 = point.Point()
类继承
你可以通过在新的类名后面用括号列出父类来从现有类派生出新类,而不是从头开始创建一个类。
子类继承了父类的属性,你可以像在子类中定义的那样使用这些属性。子类还可以覆盖来自父类的数据成员和方法。
语法:
派生类的声明与其父类类似。但是在类名称后面要加上基类名称,并在其后跟一个用逗号分隔的基类列表 −
class SubClassName (ParentClass1[, ParentClass2, ...]):
'可选的类文档字符串'
class_suite
范例:
#!/usr/bin/python3
class Parent: # 定义父类
parentAttr = 100
def __init__(self):
print ("调用父类构造函数")
def parentMethod(self):
print ('调用父类方法')
def setAttr(self, attr):
Parent.parentAttr = attr
def getAttr(self):
print ("父类属性 :", Parent.parentAttr)
class Child(Parent): # 定义子类
def __init__(self):
print ("调用子类构造函数")
def childMethod(self):
print ('调用子类方法')
c = Child() # 实例化子类
c.childMethod() # 调用子类方法
c.parentMethod() # 调用父类方法
c.setAttr(200) # 再次调用父类方法
c.getAttr() # 再次调用父类方法
当以上代码执行时,它会产生以下结果 −
调用子类构造函数
调用子类方法
调用父类方法
父类属性 : 200
同样,你可以从多个父类派生一个类,如下所示 −
class A: # 定义类A
.....
class B: # 定义类B
.....
class C(A, B): # 类C从A和B两个类继承
.....
你可以使用issubclass()或isinstance()函数来检查2个类和实例之间的关系。
- 布尔函数 issubclass(sub, sup) 返回 True,如果给定的子类 sub 实际上是超类 sup 的一个子类。
-
布尔函数 isinstance(obj, Class) 返回 True,如果 obj 是 Class 类的实例或 Class 类的子类的实例。
覆写方法
你总是可以覆盖父类方法。覆盖父类方法的一个原因是你可能希望在子类中拥有特殊或不同的功能。
范例:
#!/usr/bin/python3
class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')
c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
当上述代码被执行时,它将产生以下结果 −
调用子类方法
基本重载方法
以下表列出了一些您可以在自己的类中覆盖的常规功能。
序号 | 方法,描述和示例调用 |
---|---|
1 | __init__ ( self [,args...] ) 构造函数(带任何可选参数) 示例调用: obj = className(args) |
2 | __del__( self ) 析构函数,删除一个对象 示例调用: del obj |
3 | __repr__( self ) 可评估字符串表示 示例调用: repr(obj) |
4 | __str__( self ) 可打印的字符串表示 示例调用: str(obj) |
5 | __cmp__ ( self, x ) 对象比较 示例调用: cmp(obj, x) |
重载运算符
假设您创建了一个Vector类来表示二维向量。当您使用加号运算符将它们相加时会发生什么? 多数情况下,Python会向您大喊大叫。
然而,您可以在自己的类中定义 add 方法来执行矢量加法,那么加号运算符将按预期行事 −
举例
#!/usr/bin/python3
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
当上述代码被执行时,它将产生以下结果 −
Vector(7,8)
数据隐藏
对象的属性可能在类定义之外可见,也可能不可见。 您需要以双下划线前缀命名属性,然后这些属性对外部人员将不会直接可见。
举例
#!/usr/bin/python3
class JustCounter:
__secretCount = 0
def count(self):
self.__secretCount += 1
print (self.__secretCount)
counter = JustCounter()
counter.count()
counter.count()
print (counter.__secretCount)
当上述代码被执行时,它将产生以下结果 −
1
2
Traceback (most recent call last):
File "test.py", line 12, in <module>
print counter.__secretCount
AttributeError: JustCounter instance has no attribute '__secretCount'
Python通过在内部更改名称以包含类名来保护这些成员。 您可以使用 object._className__attrName 访问这些属性。 如果您将最后一行替换为以下内容,则适用于您 −
.........................
print (counter._JustCounter__secretCount)
当上述代码被执行时,将产生以下结果 −
1
2
2