面向对象之面向对象编程
一、面向过程与面向对象
[1]面向过程
- 面向过程着重于过程,就是将程序“流程化”
- 所谓“流程化”就是将功能、问题等一步一步地逐步实现,像流水线一样,整个过程从开始到结尾线状排序
(1)面向程序的优点
- 将复杂的问题逐步拆分,进而简单化
(2)面向对象的缺点
- 一套流程就用来解决一个问题,如果需要实现其他功能或者解决其他问题就要重新再设置另一套流程
- 如果流程中其中一步出错,需要检查整个流程来排除错误,非常麻烦
[2]面向对象
- 面向对象着重于过程,就是将程序“整合”
- 对象就是“容器”,用来盛放数据和功能
(1)面向对象的理解
- 我们进行面向对象的编程相当于上帝来创造万物,我们想要什么样功能和特性的对象就可以设计怎么样,就像上帝想创造什么就创造什么。
(2)面向对象的简单设计
- 如果我们要设计一个关于学校的模拟器,那么学校里各种各样的人,比如学生、老师、各类职工等就是我们的对象。
- 不同的人有不同的名字、性别、年龄等也能做不同的事,就有不同的属性和功能:
- 学生有姓名、年龄、班级等,可以上课、学作业、运动等。
- 老师有也有姓名、年龄、班级等,可以授课,批改作业,组织考试等
- 我们将整个学生所有的信息(属性)和能做的事(功能)全部封装起来就形成了一个关于学生的类;当然整个老师所有的信息(属性)和能做的事(功能)全部封装起来就形成了一个关于老师的类
(3)面向对象的优点
- 解决了程序的扩展性。
- 对某一个对象单独修改,会立刻反映到整个体系中。
(4)面向对象的缺点
- 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。
- 一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
- 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。
- 于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
(5)小结
- 在了解了对象的基本概念之后,理解面向对象的编程方式就相对简单很多了
- 面向对象编程就是要造出一个个的对象,把原本分散开的相关数据与功能整合到一个个的对象里
- 这么做既方便使用,也可以提高程序的解耦合程度,进而提升了程序的可扩展性(需要强调的是,软件质量属性包含很多方面,面向对象解决的仅仅只是扩展性问题)
二、类与对象
[1]引言
- 类即类别/种类,是面向对象分析和设计的基石,如果多个对象有相似的数据与功能,那么该多个对象就属于同一种类。
- 有了类的好处是:
- 我们可以把同一类对象相同的数据与功能存放到类里,而无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可,极大地节省了空间。
- 所以,如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能的容器。
- 综上所述,虽然我们是先介绍对象后介绍类,但是需要强调的是:
- 在程序中,必须要事先定义类,然后再调用类产生对象(调用类拿到的返回值就是对象)。
- 产生对象的类与对象之间存在关联,这种关联指的是:对象可以访问到类中共有的数据与功能
- 所以类中的内容仍然是属于对象的,类只不过是一种节省空间、减少代码冗余的机制
- 面向对象编程最终的核心仍然是去使用对象。
[2]什么是类
- 类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体
- 所以,先有鸡和先有蛋的问题就出来了
- 先有的一个个具体存在的对象(比如一个具体存在的人)
- 还是先有的人类这个概念,这个问题需要分两种情况去看
(1)现实中
- 在现实世界中:先有对象,再有类
- 世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念
- 也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在
(2)程序中
-
类的关键字(
class
) -
在程序中:务必保证先定义类,后产生对象
-
这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类
-
不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
三、面向对象编程
[1]引入
- 按照上述的理论我们来定义一个类
- 我们构建一个学校:先有对象,后有类
(1)对象的设计
-
对象1:桃源氏
-
信息(属性):
学校 : 世外桃源
姓名:桃源氏
性别:男
班级:101
-
能做的事(功能):上课、写作业、运动
-
-
对象2:小桥
-
信息(属性):
学校 : 世外桃源
姓名:小桥
性别:女
班级:101
-
能做的事(功能):上课、写作业、运动
-
(2)类的设计
- 构建的学校类
- 相同的信息(属性):学校:世外桃源
- 能做的相同的事(功能):上课、写作业、运动
- 这样我们就可以总结出一个学生类,用来存放学生相同的数据和功能
[2]类的实现
- 基于上述分析的结果,我们接下来需要做的就是在程序中定义出类,然后调用类产生对象
# 1. 在程序中特征用变量标识,技能用函数标识
# 2. 因而类中最常见的无非是:变量和函数的定义
# 在程序中定义一个类
# 类的命名应该使用“驼峰体”
class XanaduStudent(object):
school = '世外桃源'
def attend_class(self):
print('可以上课')
def repair_homework(self):
print('可以修作业')
def sport(self):
print('可以运动')
- 注意:
- 类中可以有任意python代码,这些代码在类定义阶段便会执行
- 因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过
XanaduStudent.__dict__
查看 - 对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法
- 点是访问属性的语法,类中定义的名字,都是类的属性
[3]类的使用
(1)查看类的名称空间
- 类体最常见的是变量的定义和函数的定义,但其实类体可以包含任意Python代码,类体的代码在类定义阶段就会执行,因而会产生新的名称空间用来存放类中定义的名字,可以打印
XanaduStudent.__dict__
来查看类这个容器内盛放的东西
print(XanaduStudent.__dict__)
# {'__module__': '__main__', 'school': '世外桃源',
# 'attend_class': <function XanaduStudent.attend_class at 0x000001D4E5DA28C0>,
# 'repair_homework': <function XanaduStudent.repair_homework at 0x000001D4E5DA36D0>,
# 'sport': <function XanaduStudent.sport at 0x000001D4E6071AB0>,
# '__dict__': <attribute '__dict__' of 'XanaduStudent' objects>,
# '__weakref__': <attribute '__weakref__' of 'XanaduStudent' objects>,
# '__doc__': None}
(2)对象的实例化
- 调用类的过程称为将类实例化
- 拿到的返回值就是程序中的对象,或称为一个实例
student_1 = XanaduStudent() # 每实例化一次Student类就得到一个学生对象
student_2 = XanaduStudent() # 但是这样实例化出来的对象全都一样没有各自独特的数据了
- 想在实例化的过程中就为三位学生定制各自独有的数据:
- 姓名,性别,年龄,需要我们在类内部新增一个
__init__
方法,如下
- 姓名,性别,年龄,需要我们在类内部新增一个
class XanaduStudent(object):
school = '世外桃源'
# 该方法会在对象产生之后自动执行,专门为对象进行初始化操作,可以有任意代码,但一定不能返回非None的值
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
def attend_class(self):
print('可以上课')
def repair_homework(self):
print('可以修作业')
def sport(self):
print('可以运动')
- 现在我们实例化出来的学生就可以有自己独特的数据
student_1 = XanaduStudent('桃源氏', "男", 22)
print(student_1.__dict__) # {'name': '桃源氏', 'sex': '男', 'age': 22}
student_2 = XanaduStudent('小桥', '女', 20)
print(student_2.__dict__) # {'name': '小桥', 'sex': '女', 'age': 20}
- 使用
__dict__
方法我们可以看到对象的名称空间
[4]属性访问
(1)类属性与对象属性
- 在类中定义的名字,都是类的属性,细说的话,类有两种属性:
- 数据属性和函数属性
- 可以通过
__dict__
访问属性的值,比如XanaduStudent.__dict__[‘school’]
,但Python提供了专门的属性访问语法
print(XanaduStudent.school) # 世外桃源
print(XanaduStudent.attend_class) # <function XanaduStudent.attend_class at 0x000001AD305F36D0>
# 除了查看属性外,我们还可以使用Student.attrib=value(修改或新增属性),用del Student.attrib删除属性。
- 操作对象的属性也是一样
print(student_1.name) # 桃源氏
student_1.grades = 100 # 新增,等同于obj1.__dict__[‘grades']='100'
print(student_1.grades) # 100
student_1.age = 18 # 修改,等同于obj1.__dict__[‘age']=18
print(student_1.age) # 18
del student_1.grades # 修改,等同于del obj1.__dict__['grades']
print(student_1.grades) # AttributeError: 'XanaduStudent' object has no attribute 'grades'
(2)属性查找
- 对象的名称空间里只存放着对象独有的属性,而对象们相似的属性是存放于类中的。
- 对象在访问属性时,会优先从对象本身的
__dict__
中查找,未找到,则去类的__dict__
中查找
1、类中定义的变量是类的数据属性,是共享给所有对象用的,指向相同的内存地址
print(id(XanaduStudent)) # 2378796475824
print(id(student_1.school)) # 2378796475824
print(id(student_2.school)) # 2378796475824
2、类中定义的函数是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数
student_1.repair_homework() # 桃源氏可以修作业
student_2.repair_homework() # 小桥可以修作业
- 但其实类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同
print(id(XanaduStudent.repair_homework)) # 2054066543280
print(id(student_1.repair_homework)) # 2054066526080
print(id(student_2.repair_homework)) # 2054066526016
- 绑定到不同对象的
repair_homework
技能- 虽然都是选课,但桃源氏选的课,不会选给小桥,这正是”绑定“二字的精髓所在。
- 注意:绑定到对象方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但命名为self是约定俗成的。
[5]小结
在上述介绍类与对象的使用过程中,我们更多的是站在底层原理的角度去介绍类与对象之间的关联关系
如果只是站在使用的角度,我们无需考虑语法“对象.属性"中”属性“到底源自于哪里,只需要知道是通过对象获取到的就可以了
所以说,对象是一个高度整合的产物,有了对象,我们只需要使用”对象.xxx“的语法就可以得到跟这个对象相关的所有数据与功能,十分方便且解耦合程度极高。
[6]绑定方法与非绑定方法
- 类中的属性有两种:一种是变量类型的属性,称为数据属性:另一种是函数类型的属性,称为函数属性,也可以称之为方法
- 类中的函数,也就是方法,有其特殊的特性和分类,一般通过不同角色调用方式的不同分为绑定方法和非绑定方法;绑定方法有通过绑定目标的不同分为绑定给对象的方法和绑定给类的方法
方法(函数属性):
- 绑定方法(动态方法)
- 绑定给对象的方法
- 绑定给类的方法
- 非绑定方法(静态方法)
(1)绑定方法(动态方法)
1.绑定给对象的方法
- 绑定给对象的方法是最常见的一种方法:就是直接在类中定义的函数
class Student(object):
def __init__(self, name):
self.name = name
# 绑定给对象的方法(默认参数需要将self也就是对象的自身传入函数内部)
def attend_class(self):
print(f'{self.name}可以上课。')
# 实例化出一个对象并调用,不要需要传入额外参数,因为实例方法默认第一个参数是self会自动传入
stu_1 = Student('桃源氏')
stu_1.attend_class() # 桃源氏可以上课。
# 直接使用类调用就需要传入self参数,不然就会报错
Student.attend_class() # TypeError: Student.attend_class() missing 1 required positional argument: 'self'
Student.attend_class(stu_1) # 桃源氏可以上课。
2.绑定给类的方法
- 绑定给类的方法需要在定义的函数前加上
@classmethod
语法糖来装饰
class Student(object):
def __init__(self, name):
self.name = name
# 绑定给类的方法(默认参数需要将cls也就是对象的自身传入函数内部)
@classmethod
def do_homework(cls):
print(f'做作业。')
# 实例化出一个对象并调用绑定给类的方法,不需要额外传入参数
stu_1 = Student('桃源氏')
stu_1.do_homework() # 做作业。
# 直接使用类调用绑定给类的方法,也不需要额外传入参数
Student.do_homework() # 做作业。
#这样就可以在不实例化对象的前提下,直接使用类中的方法
(2)非绑定方法(静态方法)
- 绑定给类的方法需要在定义的函数前加上
@staticmethod
语法糖来装饰
class Student(object):
def __init__(self, name):
self.name = name
# 非绑定方法(静态方法)
# 静态方法没有默认的第一个参数,与一般函数类似
@staticmethod
def run():
print('跑步')
# 实例化出一个对象并调用非绑定方法,不需要额外传入参数
stu_1 = Student('桃源氏')
stu_1.run() # 跑步
# 直接使用类调用非绑定方法,也不需要额外传入参数
Student.run() # 跑步
[7]__init__(self)
方法初识
- 为了使类能实例化出带有独特属性的对象我们可以在类的内部使用
__init__(self)
方法
class Person(object):
country = 'China'
def __init__(self, name, age):
self.name = name
self.age = age
people_1 = Person('桃源氏', 22)
people_2 = Person('小桥', 20)
print(people_1.name) # 桃源氏
print(people_2.name) # 小桥
# 强调:
# 1、该方法内可以有任意的python代码
# 2、一定不能有返回值
[8]注意
1. 站的角度不同,定义出的类是截然不同的
2. 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类......
3. 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类
标签:__,对象,self,绑定,面向对象,面向对象编程,print,属性 From: https://www.cnblogs.com/taoyuanshi/p/17994489