【一】面向对象编程

【一】面向对象编程

【一】引言

【1】什么是面向过程?

(1)面向过程介绍

面向过程,核心在于 “过程” 二字

  • 过程的终极奥义就是将程序 “流程化”
  • 过程是 “流水线” ,用来分步骤解决问题的
  • 过程指的是解决问题的步骤,即先干什么再干什么......
  • 面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。

(2)面向过程的优点

  • 复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)

(3)面向过程的缺点

  • 一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。

【2】什么是面向对象?

(1)面向对象介绍

面向过程,核心在于“对象”二字

  • 对象的终极奥义就是将程序 “整合”
  • 对象就是 “容器” ,用来盛放数据与功能

面向对象理解文字版:

​ 面向对象就相当于上帝,在上帝的视角,人是对象,动物是对象,石头是对象,水是对象,山是对象.....存在的可以扩充,不存咋的可以创造。

面向对象设计理解文字版:

​ 面向对象的设计就好比我们要设计一个小校园模拟器,我们需要在校园中创造各种生活场景,包括上课、做饭、打球等等。这些场景对应的角色就是对象,比如老师、学生、运动员、厨师等。

​ 而每一个角色都会对应其具有的责任和功能

​ 比如一个学生具有姓名、年龄、性别、所在班级等。

​ 学生还有能做的活动,读书、写作、跑步、打篮球等。

​ 比如一个老师可以教书、批改作业等。

​ 比如运行员可以参加训练和比赛等。

​ 当我们的角色和功能构建好以后,我们又需要创建几个学生、一些老师和运行员,让他们进行各项活动。

​ 比如一个学生可以上课、写作业、运动;

​ 比如一个老师可以教课、批改作业;

​ 比如一个运动员可以训练、参加比赛

(2)面向对象的优点

  • 解决了程序的扩展性。
  • 对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

(3)面向对象的缺点

  • 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。
    • 一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
  • 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。
    • 于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。

【3】什么是程序?

程序 = 数据 + 功能

  • 编写程序的本质
    • 就是定义出一系列的数据,然后定义出一系列的功能来对数据进行操作。

【4】小结

  • 在了解了对象的基本概念之后,理解面向对象的编程方式就相对简单很多了,面向对象编程就是要造出一个个的对象,把原本分散开的相关数据与功能整合到一个个的对象里,这么做既方便使用,也可以提高程序的解耦合程度,进而提升了程序的可扩展性(需要强调的是,软件质量属性包含很多方面,面向对象解决的仅仅只是扩展性问题)

【二】类与对象

【】引言

  • 类即类别/种类,是面向对象分析和设计的基石,如果多个对象有相似的数据与功能,那么该多个对象就属于同一种类。
  • 有了类的好处是:
    • 我们可以把同一类对象相同的数据与功能存放到类里,而无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可,极大地节省了空间。
  • 所以,如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能的容器。

  • 综上所述,虽然我们是先介绍对象后介绍类,但是需要强调的是:
    • 在程序中,必须要事先定义类,然后再调用类产生对象(调用类拿到的返回值就是对象)。
    • 产生对象的类与对象之间存在关联,这种关联指的是:对象可以访问到类中共有的数据与功能
    • 所以类中的内容仍然是属于对象的,类只不过是一种节省空间、减少代码冗余的机制
  • 面向对象编程最终的核心仍然是去使用对象。

【1】什么是类

  • 类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体

  • 所以,先有鸡和先有蛋的问题就出来了

    • 先有的一个个具体存在的对象(比如一个具体存在的人)
    • 还是先有的人类这个概念,这个问题需要分两种情况去看
在现实世界中:先有对象,再有类

世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念

也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在
在程序中:务必保证先定义类,后产生对象

这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类

不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象

【三】面向对象编程

【1】案例驱动 - 类的定义与实例化

  • 按照上述的理论我们来定义一个类

我们构建一个学校:先先有对象,后有类

对象1:
	梦梦
    特征:
        学校=梦想学城
        姓名=梦梦
        性别=男
        年龄=18
    权力:
        读书
        写作
        跑步
对象2:
	梦梦
    特征:
        学校=梦想学城
        姓名=萌萌
        性别=女
        年龄=30
    权力:
        读书
        写作
        跑步        
对象3:
	梦梦
    特征:
        学校=梦想学城
        姓名=朦朦
        性别=女
        年龄=20
    权力:
        读书
        写作
        跑步 
        
构建的学校的类:
    相似的特征:
        学校=梦想学城
    相似的技能:
        读书
        写作
        跑步 
  • 我们可以总结出一个学生类,用来存放学生相同的数据和功能
# 学生类
    相同的特征:
        学校=梦想学城
    相同的功能:
        读书
        写作
        跑步 
  • 基于上述分析的结果,我们接下来需要做的就是在程序中定义出类,然后调用类产生对象
# 在程序中,我们要先声明一个类,在类中创建一个个对象

'''
PS
	1. 在程序中特征用变量标识,技能用函数标识
  	2. 因而类中最常见的无非是:变量和函数的定义
'''


# 在程序中定义一个类
# 类的命名应该使用“驼峰体”
class DreamStudent(object):
    school = '梦想学城'

    def read_books(self):
        print('is reading books')

    def wtite_nodes(self):
        print('is wtite nodess')

    def running(self):
        print('is running')


'''

注意:

1.类中可以有任意python代码,这些代码在类定义阶段便会执行
2.因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过DreamStudent.__dict·__查看
3.对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法
4.点是访问属性的语法,类中定义的名字,都是类的属性

  • 类体最常见的是变量的定义和函数的定义,但其实类体可以包含任意Python代码,类体的代码在类定义阶段就会执行,因而会产生新的名称空间用来存放类中定义的名字,可以打印Student.__dict__来查看类这个容器内盛放的东西
print(DreamStudent.__dict__)
# {'__module__': '__main__', 'school': '梦想学城', 'read_books': <function DreamStudent.read_books at 0x0000015F610BB4C0>, 'wtite_nodes': <function DreamStudent.wtite_nodes at 0x0000015F610BBAF0>, 'running': <function DreamStudent.running at 0x0000015F610BBB80>, '__dict__': <attribute '__dict__' of 'DreamStudent' objects>, '__weakref__': <attribute '__weakref__' of 'DreamStudent' objects>, '__doc__': None}
  • 调用类的过程称为将类实例化
    • 拿到的返回值就是程序中的对象,或称为一个实例
>>> stu1=DreamStudent() # 每实例化一次Student类就得到一个学生对象
>>> stu2=DreamStudent()
>>> stu3=DreamStudent()
  • 如此stu1、stu2、stu3全都一样了(只有类中共有的内容,而没有各自独有的数据)
  • 想在实例化的过程中就为三位学生定制各自独有的数据:
    • 姓名,性别,年龄,需要我们在类内部新增一个__init__方法,如下
class DreamStudent(object):
    school = '梦想学城'
    
    # 该方法会在对象产生之后自动执行,专门为对象进行初始化操作,可以有任意代码,但一定不能返回非None的值
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

    def read_books(self):
        print('%s is reading books' % self.name)

    def wtite_nodes(self):
        print('%s is wtite nodess' % self.name)

    def running(self):
        print('%s is running' % self.name)
        
    def choose(self): 
        print('%s is choosing a course' %self.name)
  • 然后我们重新实例出三位学生
s1 = DreamStudent('梦梦', '男', 18)  # 先调用类产生空对象s1,然后调用DreamStudent.__init__(s1,'梦梦','男',18)
s2 = DreamStudent('萌萌', '女', 30)
s3 = DreamStudent('朦朦', '女', 20)
  • 单拿stu1的产生过程来分析,调用类会先产生一个空对象stu1,然后将stu1连同调用类时括号内的参数一起传给DreamStudent.__init__(stu1,’梦梦’,’男’,18)
def __init__(self, name, sex, age):
    self.name = name  # stu1.name = '梦梦'
    self.sex = sex    # stu1.sex = '男'
    self.age = age    # stu1.age = 18
  • 会产生对象的名称空间,同样可以用__dict__查看
>>> stu1.__dict__
{'name': '梦梦', 'sex': '男', 'age': 18}
  • 至此,我们造出了三个对象与一个类,对象存放各自独有的数据,类中存放对象们共有的内容

存的目的是为了用,那么如何访问对象或者类中存放的内容呢?

【2】属性访问

(2.1)类属性与对象属性

  • 在类中定义的名字,都是类的属性,细说的话,类有两种属性:
    • 数据属性和函数属性
    • 可以通过__dict__访问属性的值,比如DreamStudent.__dict__[‘school’],但Python提供了专门的属性访问语法
>>> DreamStudent.school # 访问数据属性,等同于Student.__dict__['school']
'清华大学'
>>> DreamStudent.choose # 访问函数属性,等同于Student.__dict__['choose']
<function Student.choose at 0x1018a2950>
# 除了查看属性外,我们还可以使用Student.attrib=value(修改或新增属性),用del Student.attrib删除属性。
  • 操作对象的属性也是一样
>>> stu1.name # 查看,等同于obj1.__dict__[‘name']
'李建刚'
>>> stu1.course=’python’ # 新增,等同于obj1.__dict__[‘course']='python'
>>> stu1.age=38 # 修改,等同于obj1.__dict__[‘age']=38
>>> del obj1.course # 删除,等同于del obj1.__dict__['course']

(2.2)属性查找与绑定方法

  • 对象的名称空间里只存放着对象独有的属性,而对象们相似的属性是存放于类中的。
  • 对象在访问属性时,会优先从对象本身的__dict__中查找,未找到,则去类的__dict__中查找

1、类中定义的变量是类的数据属性,是共享给所有对象用的,指向相同的内存地址

# id都一样
print(id(DreamStudent.school)) # 4301108704

print(id(stu1.school)) # 4301108704
print(id(stu2.school)) # 4301108704
print(id(stu3.school)) # 4301108704

2、类中定义的函数是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数

DreamStudent.choose(stu1) # 梦梦 is choosing a course
DreamStudent.choose(stu2) # 萌萌 is choosing a course
DreamStudent.choose(stu3) # 朦朦 is choosing a course
  • 但其实类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同
print(id(DreamStudent.choose)) # 4335426280

print(id(stu1.choose)) # 4300433608
print(id(stu2.choose)) # 4300433608
print(id(stu3.choose)) # 4300433608
  • 绑定到对象的方法特殊之处在于,绑定给谁就应该由谁来调用,谁来调用,就会将’谁’本身当做第一个参数自动传入(方法__init__也是一样的道理)
stu1.choose()  # 等同于DreamStudent.choose(stu1)
stu2.choose()  # 等同于DreamStudent.choose(stu2)
stu3.choose()  # 等同于DreamStudent.choose(stu3)
  • 绑定到不同对象的choose技能
    • 虽然都是选课,但梦梦选的课,不会选给萌萌,这正是”绑定“二字的精髓所在。
# 注意:绑定到对象方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但命名为self是约定俗成的。
  • Python中一切皆为对象,且Python3中类与类型是一个概念,因而绑定方法我们早就接触过
# 类型list就是类
>>> list
<class 'list'>

# 实例化的到3个对象l1,l2,l3
>>> l1=list([1,2,3])
>>> l2=list(['a','b','c'])
>>> l3=list(['x','y'])

# 三个对象都有绑定方法append,是相同的功能,但内存地址不同
>>> l1.append
<built-in method append of list object at 0x10b482b48>
>>> l2.append
<built-in method append of list object at 0x10b482b88>
>>> l3.append
<built-in method append of list object at 0x10b482bc8>

# 操作绑定方法l1.append(4),就是在往l1添加4,绝对不会将4添加到l2或l3
>>> l1.append(4) #等同于list.append(l1,4)
>>> l1
[1,2,3,4]
>>> l2
['a','b','c']
>>> l3
['x','y']

【3】小结

在上述介绍类与对象的使用过程中,我们更多的是站在底层原理的角度去介绍类与对象之间的关联关系

如果只是站在使用的角度,我们无需考虑语法“对象.属性"中”属性“到底源自于哪里,只需要知道是通过对象获取到的就可以了

所以说,对象是一个高度整合的产物,有了对象,我们只需要使用”对象.xxx“的语法就可以得到跟这个对象相关的所有数据与功能,十分方便且解耦合程度极高。


# 程序中类的用法
# . : 专门用来访问属性,本质操作的就是__dict__
DreamStudent.school  # 等于经典类的操作DreamStudent.__dict__['school']
DreamStudent.school = '梦想学城'  # 等于经典类的操作DreamStudent.__dict__['school']='梦想学城'
DreamStudent.x = 1  # 等于经典类的操作DreamStudent.__dict__['x']=1
del DreamStudent.x  # 等于经典类的操作DreamStudent.__dict__.pop('x')

# 程序中的对象
# 调用类,或称为实例化,得到对象
s1 = DreamStudent()
s2 = DreamStudent()
s3 = DreamStudent()


# 如此,s1、s2、s3都一样了,而这三者除了相似的属性之外还各种不同的属性,这就用到了__init__
# 注意:该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值
class DreamStudent(object):
    '''......'''

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

    '''......'''




# 程序中对象的用法
# 执行__init__,s1.name='牛榴弹',很明显也会产生对象的名称空间
s2.__dict__
    {'name': '梦梦', 'age': 18, 'sex': '男'}

s2.name  # s2.__dict__['name']
s2.name = '小梦梦'  # s2.__dict__['name']='小梦梦'
s2.course = 'python'  # s2.__dict__['course']='python'
del s2.course  # s2.__dict__.pop('course')

【4】__init__(self) 方法初识

# 方式一、为对象初始化自己独有的特征
class People:
    country = 'China'
    x = 1

    def run(self):
        print('----->', self)


# 实例化出三个空对象
obj1 = People()
obj2 = People()
obj3 = People()

# 为对象定制自己独有的特征
obj1.name = 'dream'
obj1.age = 18
obj1.sex = 'male'

obj2.name = 'mengmeng'
obj2.age = 38
obj2.sex = 'female'

obj3.name = 'chimeng'
obj3.age = 38
obj3.sex = 'female'


# print(obj1.__dict__)
# print(obj2.__dict__)
# print(obj3.__dict__)
# print(People.__dict__)


# 方式二、为对象初始化自己独有的特征
class People:
    country = 'China'
    x = 1

    def run(self):
        print('----->', self)


# 实例化出三个空对象
obj1 = People()
obj2 = People()
obj3 = People()


# 为对象定制自己独有的特征
def chu_shi_hua(obj, x, y, z):  # obj=obj1,x='dream',y=18,z='male'
    obj.name = x
    obj.age = y
    obj.sex = z


chu_shi_hua(obj1, 'dream', 18, 'male')
chu_shi_hua(obj2, 'mengmeng', 38, 'female')
chu_shi_hua(obj3, 'chimeng', 38, 'female')


# 方式三、为对象初始化自己独有的特征
class People:
    country = 'China'
    x = 1

    def chu_shi_hua(obj, x, y, z):  # obj=obj1,x='dream',y=18,z='male'
        obj.name = x
        obj.age = y
        obj.sex = z

    def run(self):
        print('----->', self)


obj1 = People()
# print(People.chu_shi_hua)
People.chu_shi_hua(obj1, 'dream', 18, 'male')

obj2 = People()
People.chu_shi_hua(obj2, 'mengmeng', 38, 'female')

obj3 = People()
People.chu_shi_hua(obj3, 'chimeng', 38, 'female')


# 方式四、为对象初始化自己独有的特征
class People:
    country = 'China'
    x = 1

    def __init__(obj, x, y, z):  # obj=obj1,x='dream',y=18,z='male'
        obj.name = x
        obj.age = y
        obj.sex = z

    def run(self):
        print('----->', self)


obj1 = People('dream', 18, 'male')  # People.__init__(obj1,'dream',18,'male')
obj2 = People('mengmeng', 38, 'female')  # People.__init__(obj2,'mengmeng',38,'female')
obj3 = People('chimeng', 38, 'female')  # People.__init__(obj3,'chimeng',38,'female')


# __init__方法
# 强调:
#   1、该方法内可以有任意的python代码
#   2、一定不能有返回值
class People:
    country = 'China'
    x = 1

    def __init__(obj, name, age, sex):  # obj=obj1,x='dream',y=18,z='male'
        # if type(name) is not str:
        #     raise TypeError('名字必须是字符串类型')
        obj.name = name
        obj.age = age
        obj.sex = sex

    def run(self):
        print('----->', self)


# obj1=People('dream',18,'male')
obj1 = People(3537, 18, 'male')

# print(obj1.run)
# obj1.run() #People.run(obj1)
# print(People.run)

【5】注意

1. 站的角度不同,定义出的类是截然不同的

2. 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类......

3. 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类

【6】类的特殊属性

类名.__name__
  • 类的名字(字符串)
类名.__doc__
  • 类的文档字符串
类名.__base__
  • 类的第一个父类(在讲继承时会讲)

类名.__bases__

  • 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__
  • 类的字典属性
类名.__module__
  • 类定义所在的模块
类名.__class__
  • 实例对应的类(仅新式类中)

【7】案例驱动 - 从代码角度看类

1、在没有学习类这个概念时,数据与功能是分离的

def exc1(host, port, db, charset):
    conn = connect(host, port, db, charset)
    conn.execute(sql)
    return xxx


def exc2(host, port, db, charset, proc_name)
    conn = connect(host, port, db, charset)
    conn.call_proc(sql)
    return xxx
# 每次调用都需要重复传入一堆参数
exc1('127.0.0.1', 3306, 'db1', 'utf8', 'select * from tb1;')
exc2('127.0.0.1', 3306, 'db1', 'utf8', '存储过程的名字')

2、我们能想到的解决方法是,把这些变量都定义成全局变量

HOST =‘127.0
.0
.1’
PORT = 3306
DB =‘db1’
CHARSET =‘utf8’

def exc1(host, port, db, charset):
    conn = connect(host, port, db, charset)
    conn.execute(sql)
    return xxx


def exc2(host, port, db, charset, proc_name)
    conn = connect(host, port, db, charset)
    conn.call_proc(sql)
    return xxx


exc1(HOST, PORT, DB, CHARSET, 'select * from tb1;')
exc2(HOST, PORT, DB, CHARSET, '存储过程的名字')

3、解决办法

  • 2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量,这些全局变量并没有做任何区分,即能够被所有功能使用
  • 然而事实上只有HOST,PORT,DB,CHARSET是给exc1和exc2这两个功能用的。
  • 言外之意:
    • 我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了
class MySQLHandler:
    def __init__(self, host, port, db, charset='utf8'):
        self.host = host
        self.port = port
        self.db = db
        self.charset = charset

    def exc1(self, sql):
        conn = connect(self.host, self.port, self.db, self.charset)
        res = conn.execute(sql)
        return res

    def exc2(self, sql):
        conn = connect(self.host, self.port, self.db, self.charset)
        res = conn.call_proc(sql)
        return res


obj = MySQLHandler('127.0.0.1', 3306, 'db1')
obj.exc1('select * from tb1;')
obj.exc2('存储过程的名字')


# 改进
class MySQLHandler:
    def __init__(self, host, port, db, charset='utf8'):
        self.host = host
        self.port = port
        self.db = db
        self.charset = charset
        self.conn = connect(self.host, self.port, self.db, self.charset)

    def exc1(self, sql):
        return self.conn.execute(sql)

    def exc2(self, sql):
        return self.conn.call_proc(sql)


obj = MySQLHandler('127.0.0.1', 3306, 'db1')
obj.exc1('select * from tb1;')
obj.exc2('存储过程的名字')
posted @ 2023-06-19 20:12  Chimengmeng  阅读(76)  评论(0编辑  收藏  举报