Python系列(8)- Python 类和对象、模块和包
1. 类和对象
Python 语言在设计之初,就定位为一门面向对象的编程语言,"Python 中一切皆对象" 就是对 Python 这门编程语言的完美诠释。
类和对象是 Python 的重要特征,相比其它面向对象语言,Python 很容易就可以创建出一个类和对象。同时,Python 也支持面向对象的三大特征:封装、继承和多态。
1) 面向对象
面向对象编程(Object-oriented Programming,简称 OOP),是一种代码封装的方法。
函数封装是一种将程序段或功能打包成可重用的代码块的过程。函数是一种面向过程的编程方法,它将一段可以执行特定任务的代码封装起来,形成一个独立的单元。
类封装是面向对象编程的核心概念之一。类是一种用户定义的应用数据类型,它包含了属性(变量)和方法(函数)。类封装将数据和操作封装在一起,形成一个有机的整体。类提供了更高的抽象层次,可以定义对象的行为和状态。
类封装更适合于复杂系统的开发,而函数封装则适用于简单的功能实现。
面向对象相关术语:
(1) 类(Class): 用来描述具有相同的属性 (变量)和方法(函数)的对象的集合;
(2) 对象:对象是类的实例化,类是一个模板,对象是根据这个模板创建的具体实例;
(3) 类属性(或称类变量): 在类体中方法(函数)之外的变量,它是类的所有实例化对象共享的变量;
(4) 实例属性(或称实例变量):在类体中方法(函数)之内,以 "self.变量名=值" 的方式定义的变量;
(5) 局部变量:在类体中方法(函数)之内,以 "变量名=值" 的方式定义的变量;
(6) 方法:;
(7) 继承:即一个派生类(derived class)继承基类(base class)的属性和方法。继承也允许把一个派生类的对象作为一个基类对象对待;
(8) 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写;
(9) 多态:;
注:属性和变量是两个不同的概念,变量是面向过程编程、面向对象编程中都会用到的概念,属性则是面向对象编程中的一个概念,通常在类中一个属性对应一个变量,并通过 getter 和 setter 方法进行读写。即在面向对象编程时,定义/读写属性,需要比定义/读写变量采用更严谨的规范要求。
方法和函数也是两个不同的概念,函数是面向过程编程、面向对象编程中都会用到的概念,方法则是面向对象编程中的一个概念,通常在类中一个方法对应一个函数。
2) 类的定义和实例化
Python 定义类使用 class 关键字,语法格式如下:
class 类名:
[类属性 = 值]
def __init__(self, [参数列表]):
[self.实例属性 = 参数值]
def 方法(self, [参数列表]):
[self.实例属性 = 参数值]
[局部变量 = 参数值]
名词解释:
(1) 类名:一个符合 Python 语法的标识符,类名要能够体现出该类的特征;
(2) [类属性 = 值]: 是可选项,表示定义一个类属性,并赋值;
(3) __init__(self, [参数列表]): __init__ (双下划线开头/结尾)是类的构造方法,创建类的对象时,会自动调用这个方法;self 表示类实例化的对象本身;[参数列表] 是可选项,和函数参数列表一样,多个参数之间用逗号分隔;
(4) [self.实例属性 = 参数值]:是可选项,表示定义一个实例属性,并赋值;
(5) 方法(self, [参数列表]): 方法是类的成员函数;self 表示类实例化的对象本身;[参数列表] 是可选项,和函数参数列表一样,多个参数之间用逗号分隔;
注:[] 括表示非必选项。
类的实例化(即创建对象),语法格式如下:
变量 = 类名([参数列表])
名词解释:
(1) 类名: 就是定义类时的类名;
(2) [参数列表]: 和函数参数列表一样,多个参数之间用逗号分隔;
(3) 变量: 类实例化的对象。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Dog: def display(self): print('This is a dog') class Person: def __init__(self, name): self.name = name def display(self): print('name:', self.name) if __name__ == "__main__": dog = Dog() dog.display() person = Person('Python') person.display()
输出结果如下:
This is a dog
name: Python
注:Dog 类没有 __init__() 构造方法,Python 也会自动给 Dog 类添加一个仅包含 self 参数的构造方法,这种构造方法被称为类的默认构造方法。
3) 类中的属性
根据变量定义的位置不同,以及定义的方式不同,类中的属性可细分为:类属性、实例属性、局部变量、内置属性。
上文讲到,属性是面向对象编程中的一个概念,通常在类中一个属性对应一个变量。局部变量是类中方法内的变量,根据面向对象编程的概念,我们也可以把局部变量称为局部属性。
但是,局部变量和类的属性在定义和使用上有明显的区别,为了不把问题复杂化,我们这里就不使用局部属性这个概念。
(1) 类属性
类属性或称类变量,在类体中方法(函数)之外的变量,它是类的所有实例化对象共享的变量。类变量既可以使用 '类名.变量名' 方式调用,也可以使用 '对象.变量名' 方式调用(不推荐这种方式,见下文)。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Dog: def display(self): print('This is a dog') class Person: count = 0 __count = 0 def __init__(self, name): self.name = name def display(self): print('name:', self.name) if __name__ == "__main__": dog = Dog() Dog.count = 10 # 动态为 Dog 类添加类变量 count print('dog.count:', dog.count) person1 = Person('Python') person2 = Person('Java') print('Person.count:', Person.count) Person.count = 1 print('Person.count = 1') print('Person.count:', Person.count, ', person1.count:', person1.count, ', person2.count:', person2.count) person1.count = 3 # 这不是给 Person 的类变量 count 赋值,而是给对象 person1 定义新的实例变量 count print('person1.count = 3') print('Person.count:', Person.count, ', person1.count:', person1.count, ', person2.count:', person2.count) Person.count = 5 print('Person.count = 5') print('Person.count:', Person.count, ', person1.count:', person1.count, ', person2.count:', person2.count)
输出结果如下:
dog.count: 10 Person.count: 0 Person.count = 1 Person.count: 1 , person1.count: 1 , person2.count: 1 person1.count = 3 Person.count: 1 , person1.count: 3 , person2.count: 1 Person.count = 5 Person.count: 5 , person1.count: 3 , person2.count: 5
注:Python 的类变量,在默认情况下可以直接读写,尽量使用 '类名.变量名' 方式调用类变量。使用 '对象.变量名' 方式调用类变量,可能会导致给对象添加同名的实例变量的误操作。实例变量和类变量可以同名,但这种情况下,对象无法调用同名的类变量,它会首选实例变量。
(2) 实例属性
实例属性或称实例变量,在类体中方法(函数)之内,以 "self.变量名=值" 的方式定义的变量。实例变量只能使用 '对象.变量名' 方式调用,无法使用 '类名.变量名' 方式调用。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Person: name='Person' def __init__(self, name): self.name = name def display(self): print('name:', self.name) if __name__ == "__main__": person1 = Person('Python') person2 = Person('Java') print('Person.name:', Person.name, ', person1.name:', person1.name, ', person2.name:', person2.name) Person.name = 'Person 2' person1.name = 'Python 2' person2.name = 'Java 2' print('Person.name:', Person.name, ', person1.name:', person1.name, ', person2.name:', person2.name) person1.age = 20 print('person1.age:', person1.age) if hasattr(Person, 'age'): print('Person.age:', Person.age) else: print('age not in Person') if hasattr(person2, 'age'): print('person2.age:', person2.age) else: print('age not in person2')
输出结果如下:
Person.name: Person , person1.name: Python , person2.name: Java Person.name: Person 2 , person1.name: Python 2 , person2.name: Java 2 person1.age: 20 age not in Person age not in person2
注:给对象添加实例变量,或修改对象的实例变量的值,不会影响类的其它实例化对象,也不会影响同名的类变量。hasattr() 函数用来判断属性或方法是否存在。
(3) 局部变量
在类体中方法(函数)之内,以 "变量名=值" 的方式定义的变量。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Person: def __init__(self, name): self.name = name def display(self): age = 20 print('name:', self.name, ', age:', age) if __name__ == "__main__": person = Person('Local') if hasattr(person, 'age'): print('before: person.age:', person.age) else: print('before: age not in person') person.display() if hasattr(person, 'age'): print('after: person.age:', person.age) else: print('after: age not in person')
输出结果如下:
before: age not in person
name: Local , age: 20
after: age not in person
注:局部变量 age 的作用域就是在 disply() 之内的范围,方法执行完成后,age 即被销毁
(4) 内置属性
内置属性或称内置变量,是指在 Python 类中用双下划线开头/结尾的特殊属性,如 __name__、__doc__ 等。
这些属性通常用于定义类的元数据,控制类的行为,或者在类的不同部分之间交互。使用 dir() 函数看类或对象的内置属性,运行如下代码:
print(dir(Person)) # 查看类
print(dir(person)) # 查看对象
内置方法也是用双下划线开头/结尾的特殊方法,如 __call__, __del__ 等,也可以使用同样方式查看。
4) 类中的方法
类中的方法可分为实例方法、类方法、静态方法、。
(1) 实例方法
在类中定义的方法默认都是实例方法,它至少包含一个 self 参数,用于绑定调用此方法的实例对象(Python 会自动完成绑定)。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Person: def __init__(self, name): self.name = name def display(self, str): print(self.name, str) if __name__ == "__main__": person = Person('Hello') person.display('Java') Person.display(person, 'Python')
输出结果如下:
Hello Java
Hello Python
注:可以使用实例对象调用,第一个参数无需传入对象引用;也可以使用类调用,第一个参数要传入对象引用。
(2) 类方法
类方法需要使用 @classmethod 修饰,它至少包含一个 cls 参数,用于绑定调用此方法的类本身(Python 会自动完成绑定)。类方法推荐使用类名直接调用,也可以使用实例对象来调用(不推荐使用这种方式)。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Person: name = 'class' def __init__(self, name): self.name = name @classmethod def display(cls, str): print(cls.name, str) if __name__ == "__main__": Person.display('Python') person = Person('Hello') person.display('Java')
输出结果如下:
class Python
class Java
(3) 静态方法
静态方法需要使用 @staticmethod 修饰,无需添加 self 参数或 cls 参数。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Person: @staticmethod def display(str): print(str) if __name__ == "__main__": Person.display('Python') person = Person() person.display('Java')
输出结果如下:
Python
Java
5) 类的继承
Python 中,实现继承的类称为子类,被继承的类称为父类(也可称为基类、超类)。
类继承只需在定义类的时候,将父类作为参数传给子类,语法格式如下:
class 类名(父类1, 父类2, ...):
# 类体
注:类定义时没有传父类参数,则默认继承 object 类,object 类是 Python 中所有类的父类。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Person: def display(self, str): print(str) class Man(Person): pass if __name__ == "__main__": man = Man() man.display('Python')
输出结果如下:
Python
6) 方法重写和多态
方法重写就是在子类中添加与父类同名的方法,两个同名方法可以拥有不同数量的参数。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- class Person: def __init__(self, prefix): self.prefix = prefix def display(self, str): print(self.prefix, ':', str) class Man(Person): def __init__(self, prefix): super().__init__(prefix) def display(self, str): print(self.prefix, ':', str) def display(obj, str): obj.display(str) if __name__ == "__main__": man = Man('Man') man.display('Python') # 通过父类调用到父类 display() 函数 Person.display(man, 'Java') # 多态 person = Person('Person') display(person, 'polymorphic') display(man, 'polymorphic')
输出结果如下:
Man : Python
Man : Java
Person : polymorphic
Man : polymorphic
注:通过子类的对象 man 无法访问被重写的父类 display() 函数,参考上文的 '实例方法' 部分,可以通过父类 Person 调用到父类 display() 函数。
子类 Man 的 __init__() 初始化时,把 prefix 值通过 super() 方式传给了父类 Person。
全局函数 display(obj, str) 中,obj.display() 表现出了多态。如果 obj 是 Person 类型,就使用 Person 的 display(); 如果 obj 是 Man 类型,就是用 Man 的 display().
2. 模块和包
Python 模块 (Module),是一个 Python 文件 (*.py), 可以包含多个类、函数、变量。
函数是对 Python 代码的封装,类是对方法和属性的封装,模块是比函数、类更大范围的封装。
1) import 语句
import 语句用于导入 Python 标准库的模块、第三方库的模块或自定义模块,语法格式如下:
import module1, module2 as mod2, ...
解释说明:
(1) import 语句可以导入一个模块,也可以同时导入多个模块,as 语句是给模块取别名;
(2) 多行 import 语句导入同名模块,该同名模块只会被导入一次;
(3) import 会导入模块中的所有成员(包括变量、函数、类等);
2) from ... import 语句
from ... import 语句用于导入指定模块内的类、函数或变量,语法格式如下:
from module import name1, name2 as n2, ...
解释说明:
(1) 该语句可以导入一个模块的单个成员,也可以同时导入多个成员;
(2) from ... import * 可以导入模块中所有成员,不推荐使用这种方式。
3) 自定义模块
示例,创建一个 Python 模块文件 mod.py, 代码如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- _var1 = 5 __var2 = 9 data = 'Test data' message = 'It is custom module' def display(str): print(str) class Format: def display(self, str): print(str) __all__ = ['message', 'display', 'Format']
注:单下划线 “_” 或者双下划线 “__” )开头的变量 _var1、__var2, 仅在 from ... import * 导入方式时会被忽略而不导入。
仅在 from ... import * 导入方式时,如果由 __all__ 变量,只能导入 __all__ 变量指定的成员。
创建一个使用 mod 模块的 Python 文件 run_mod.py,和 mod.py 在同一级目录下, 代码如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import mod from mod import message, display, Format # import mod.display(mod.message) format = mod.Format() format.display('Format: ' + mod.message) # from ... import display('from ... import -> ' + message) format2 = Format() format.display('from ... import -> Format: ' + message)
输出结果如下:
It is custom module
Format: It is custom module
from ... import -> It is custom module
from ... import -> Format: It is custom Module
4) 包 (package)
Python 包(package)就是一个存放多个 Python 模块的文件夹,该文件夹下必须存在一个名为 '__init__.py' (前后都是双下划线) 的文件,__init__.py 文件可以是个空文件。
(1) 创建包
创建一个 demo_package 目录,在 demo_package 目录下创建 3 个 Python 文件 __init__.py、mod1.py、mod2.py。
文件 __init__.py, 内容如下:
__all__ = ['mod1']
文件 mod1.py, 内容如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- message = 'It is custom module' def display(str): print(str)
文件 mod2.py, 内容如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- def display(str): print(str)
demo_package 包的文件结构如下:
demo_package
|-- __init__.py
|-- mod1.py
|-- mod2.py
(2) 导入包
创建一个 Python 文件 test.py 和 demo_package 在同一级目录,test.py 内容如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import demo_package as demoPackage # 包的本质就是模块,可以用 import 导入 import demo_package.mod1 as demoMod1 import demo_package.mod2 as demoMod2 print(demoPackage.__all__) # 此时 demoPackage 对应 __init__.py 文件 demoMod1.display('1 -> ' + demoMod1.message) demoMod2.display('2 -> ' + demoMod1.message)
输出结果如下:
['mod1']
1 -> It is custom module
2 -> It is custom module
修改 test.py 内容如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import demo_package as demoPackage # 包的本质就是模块,可以用 import 导入 from demo_package import * print(demoPackage.__all__) # 此时 demoPackage 对应 __init__.py 文件 mod1.display('1 -> ' + mod1.message) mod2.display('2 -> ' + mod2.message)
输出结果如下:
['mod1'] 1 -> It is custom module Traceback (most recent call last): File "d:/pythonDemo/test.py", line 16, in <module> mod2.display('2 -> ' + mod2.message) NameError: name 'mod2' is not defined
注:__init__.py 文件里的 __all__ 变量,仅在 from demo_package import * 导入方式时,只允许 mod1 模块被访问,