面向对象
1 面向对象的核心思想
面向过程编程思想的核心思想是将程序流程化,分步骤解决问题。
面向对象编程思想的核心思想是对程序中的数据与功能进行整合。
什么是对象?
对象是是用来存储数据与功能的容器。
模块,列表,字典等都可以用于存放数据和功能,因此都是对象。
什么是整合?
可以将具有相互联系的能实现一定功能的代码整合到容器中存起来,需要这个功能时可以通过容器的名字来找到这些代码,这个容器就是函数。
程序中一些数据与一些功能(函数)是有关联的,即一些函数专门用于操作一些数据。
整合的意思是将代码中分散的数据以及相关联的功能存储在专门的容器中。
面向对象思想优点:
程序的整合程度越高,解耦合程度就越高,组织结构就越清晰,扩展性更强。
面向对象思想缺点:
设计过程复杂。
下面以字典来举例
def set_stu_info(stu_dict, name, age, course):
stu_dict['name'] = name
stu_dict['age'] = age
stu_dict['course'] = course
def print_stu_info(stu_dict):
print(f'Name: {stu_dict["name"]}, Age: {stu_dict["age"]}, Course: {stu_dict["course"]}')
stu1_dict = {
'name': None,
'age': None,
'course': None,
'school': 'Inner-City Wizard School',
'print_stu_info': print_stu_info,
'set_stu_info': set_stu_info
}
stu1_dict['set_stu_info'](stu1_dict, 'Dean', 20, 'History of Magic')
stu1_dict['print_stu_info'](stu1_dict) # Name: Dean, Age: 20, Course: History of Magic
希望将功能(函数)的代码也整合到容器中。
2 类
如果两个对象在逻辑上可以视为同一种类别,这意味着这两个对象可能拥有相同的数据和功能,这就出现了相同代码重复的问题。
类是容器,用于存储同类对象所共有的数据和功能。
因此类也是对象。
类中存储的同类对象所共有的数据和功能本质上还是属于各个对象本身,类只是为了解决重复代码问题。
3 面向对象编程
3.1 思考方法和编写方法
思考方法
编写代码前,先思考程序中哪些数据和功能需要整合在一起,存储到对象中,再从相似的对象中提取出共有的数据和功能,存放到类中。
编写方法
先定义类,再调用类产生对象。
class Student:
school = 'Inner-City Wizard School'
def set_stu_info(stu_obj, name, age, course):
stu_obj['name'] = name
stu_obj['age'] = age
stu_obj['course'] = course
def print_stu_info(stu_obj):
print(f'Name: {stu_obj["name"]}, Age: {stu_obj["age"]}, Course: {stu_obj["course"]}')
print(f'Welcome to {school}') # Welcome to Inner-City Wizard School
- 类是相似的对象的数据和功能的集合体,因此类中最常见的是变量(属性)定义与函数(方法)定义,但类体中可以包含任意代码。
- 类的代码在定义阶段运行,运行时会产生类的名称空间,因此类的名称空间是在类的定义阶段产生的。
3.2 内置属性__dict__
字典__dict__用于查看类/对象的名称空间中存在的变量名和方法名。
print(Student.__dict__)
# {'__module__': '__main__', 'school': 'Inner-City Wizard School', 'set_stu_info': <function Student.set_stu_info at 0x地址>, 'print_stu_info': <function Student.print_stu_info at 0x地址>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
访问类的属性
print(Student.__dict__['school'])
print(Student.__dict__['set_stu_info'])
print(Student.__dict__['print_stu_info'])
print(Student.school)
print(Student.set_stu_info)
print(Student.print_stu_info)
3.3 产生对象
调用类来产生新对象,建立类与新对象之间的关联。
obj = ClassName()
stu1 = Student()
print(stu1.__dict__) # {} 新对象的名称空间为空
向新对象中传入其特有的属性
- 操作字典__dict__
stu1.__dict__['name'] = 'Dean'
stu1.__dict__['age'] = 20
stu1.__dict__['course'] = 'History of Magic'
print(stu1.__dict__) # {'name': 'Dean', 'age': 20, 'course': 'History of Magic'}
- 属性访问方式
stu1.name = 'Dean'
stu1.age = 20
stu1.course = 'History of Magic'
print(stu1.__dict__) # {'name': 'Dean', 'age': 20, 'course': 'History of Magic'}
问题1,初始化新对象时存在代码重复问题
问题2,属性查找顺序
3.4 使用__init__方法
方案1
对于初始化新对象时存在的代码重复问题,可以使用函数。
def init(stu_obj, name, age, course):
stu_obj.name = name
stu_obj.age = age
stu_obj.course = course
init(stu1, 'Dean', 20, 'History of Magic')
print(stu1.__dict__) # {'name': 'Dean', 'age': 20, 'course': 'History of Magic'}
方案2
将对象初始化的功能(函数)整合到类中。
最理想的情况是调用类产生对象同时也完成对象的初始化操作。
即调用类同时自动调用初始化函数。
- 方法__init__用于对新对象的属性赋值,即初始化,并且会在调用类时自动触发。
- 实例化时需要执行的代码都可以放在方法__init__内。
class Student:
school = 'Inner-City Wizard School'
def __init__(stu_obj, name, age, course):
stu_obj.name = name
stu_obj.age = age
stu_obj.course = course
def print_stu_info(stu_obj):
print(f'Name: {stu_obj.name}, Age: {stu_obj.age}, Course: {stu_obj.course}')
# stu1 = Student() 报错
stu1 = Student('Dean', 20, 'History of Magic')
print(stu1.__dict__) # {'name': 'Dean', 'age': 20, 'course': 'History of Magic'}
方法__init__定义时的参数列表有4个形参,但初始化时只需要3个实参。
-
调用类产生新对象时会自动触发__init__方法,并将新对象作为第一个参数传入__init__方法。
-
调用类时的返回值是新对象,不是方法__init__的返回值,因为方法__init__的返回值只能是None,否则报错。
3.5 实例化过程
实例化是调用类产生新对象的过程。一个对象也可以称为一个实例。
- 产生一个空对象;
- 自动调用类中的__init__()方法,将空对象和调用类时产生的参数传入__init__()方法;
- 返回完成初始化的对象。
3.5 属性查找顺序
先从对象的名称空间中找,再去类的名称空间中寻找。
类中的属性和方法是提供给对象使用的。
- 类的数据属性是共享给所有对象使用的。
通过对象能访问到的属性或方法不一定存在于这个对象的名称空间中,有可能属于类的名称空间中。
class Student:
school = 'Inner-City Wizard School'
count = 0 # 用于统计对象数量
def __init__(stu_obj, name, age, course):
stu_obj.name = name
stu_obj.age = age
stu_obj.course = course
Student.count += 1
def print_stu_info(stu_obj):
print(f'Name: {stu_obj.name}, Age: {stu_obj.age}, Course: {stu_obj.course}')
stu1 = Student('Dean', 20, 'History of Magic')
print(stu1.count) # 1
stu2 = Student('Sam', 21, 'Charms')
print(stu2.count) # 2
print(id(stu1.school) == id(Student.school)) # True
print(id(stu1.school) == id(stu2.school)) # True
print(stu1.school) # Inner-City Wizard School
print(Student.school) # Inner-City Wizard School
# 修改类属性,共享的
Student.school = 'Hogwarts School of Witchcraft and Wizardry'
print(stu1.school) # Hogwarts School of Witchcraft and Wizardry
# 对象的名称空间中没有属性school,相当于在对象的名称空间中添加属性school,独有的
stu2.school = 'Jason Ruiz School for Wayward Boys'
print(stu1.school) # Hogwarts School of Witchcraft and Wizardry
Student.school = 'Hogwarts School of Witchcraft and Wizardry'
# 先从对象的名称空间中找
print(stu2.school) # Jason Ruiz School for Wayward Boys
- 类的函数属性是绑定给所有对象使用的。
绑定给哪个对象就是在操作哪个对象。
class Student:
school = 'Inner-City Wizard School'
def __init__(stu_obj, name, age, course):
stu_obj.name = name
stu_obj.age = age
stu_obj.course = course
def print_stu_info(stu_obj):
print(f'Name: {stu_obj.name}, Age: {stu_obj.age}, Course: {stu_obj.course}')
stu1 = Student('Dean', 20, 'History of Magic')
stu2 = Student('Sam', 21, 'Charms')
通过类调用
Student.print_stu_info(stu1) # Name: Dean, Age: 20, Course: History of Magic
通过对象调用
stu1.print_stu_info() # Name: Dean, Age: 20, Course: History of Magic
哪一个对象调用方法,就会将这个对象作为第一个实参自动传给这个方法。
类中的方法的第一个形参的名字约定俗成为self。