python基础(二十二):面向对象编程

一、对象

”面向对象“的核心是“对象”二字,而对象的精髓在于“整合“,什么意思?

# 所有的程序都是由”数据”与“功能“组成,因而编写程序的本质就是定义出一系列的数据,然后定义出一系列的功能来对数据进行操作。在学习”对象“之前,程序中的数据与功能是分离开的,如下

# 数据:name、age、sex
name='lili'
age=18
sex='female'

# 功能:tell_info
def tell_info(name,age,sex): 
    print('<%s:%s:%s>' %(name,age,sex))

# 此时若想执行查看个人信息的功能,需要同时拿来两样东西,一类是功能tell_info,另外一类则是多个数据name、age、sex,然后才能执行,非常麻烦
tell_info(name,age,sex)

在学习了“对象”之后,我们就有了一个容器,该容器可以盛放数据与功能,所以我们可以说:对象是把数据与功能整合到一起的产物,或者说”对象“就是一个盛放数据与功能的容器。

# 我们的函数里面可以存放数据和内置函数,字典、列表、元组、集合等等都可以把变量名、函数名存进去,因此他们都是对象!

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

二、类与对象

类即类别/种类,是面向对象分析和设计的基石,对象是类的实例,人是一个种类,但我们自己就是人这个种类的一个实例,一个人就是一个对象。有了类的好处是:我们可以把同一类对象相同的数据与功能存放到类里,而无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可,极大地节省了空间。所以,如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能的容器。
在这里插入图片描述​ 综上所述,虽然我们是先介绍对象后介绍类,但是需要强调的是:在程序中,必须要事先定义类,然后再调用类产生对象(调用类拿到的返回值就是对象)。产生对象的类与对象之间存在关联,这种关联指的是:对象可以访问到类中共有的数据与功能,所以类中的内容仍然是属于对象的,类只不过是一种节省空间、减少代码冗余的机制,面向对象编程最终的核心仍然是去使用对象。

三、面向对象编程

我们以开发一个清华大学的选课系统为例,来简单介绍基于面向对象的思想如何编写程序

# 学生1:
    数据:
        学校=清华大学
        姓名=李建刚
        性别=男
        年龄=28
    功能:
        选课

# 学生2:
    数据:
        学校=清华大学
        姓名=王大力
        性别=女
        年龄=18
    功能:
        选课

我们可以总结出一个学生类,用来存放学生们相同的数据与功能

# 学生类
    相同的特征:
        学校=清华大学
    相同的功能:
        选课

类中只保存相同的,不同的呢?不同的放在类中的init函数里,如下:

class Student:
    school='清华大学'

    #该方法会在对象产生之后自动执行,专门为对象进行初始化操作,可以有任意代码,但一定不能返回非None的值
    def __init__(self,name,sex,age): # 数据值有改变的放在init方法里,传值初始化对象
        self.name=name
        self.sex=sex
        self.age=age

    def choose(self):  # 一般来说一个种类的方法都一样,只是方法的具体执行效果因对象的不同而异,不一样的话只能加方法,或者说没有的方法不调用就行了
        print('%s is choosing a course' %self.name)

然后我们实例出三位学生:

>>> stu1=Student('李建刚','男',28)
>>> stu2=Student('王大力','女',18)
>>> stu3=Student('牛嗷嗷','男',38)

单拿stu1的产生过程来分析,调用类会先产生一个空对象stu1,然后将stu1连同调用类时括号内的参数一起传给Student.__init__(stu1,’李建刚’,’男’,28),进行空对象stu1的初始化

def __init__(self, name, sex, age):
    self.name = name  # stu1.name = '李建刚'
    self.sex = sex    # stu1.sex = '男'
    self.age = age    # stu1.age = 28

同时会产生对象的名称空间,同样可以用__dict__查看:

>>> stu1.__dict__
{'name': '李建刚', 'sex': '男', 'age': 28}

至此,我们造出了三个对象与一个类,对象存放各自独有的数据,类中存放对象们共有的内容
在这里插入图片描述对象名称空间产生于对象初始化后,我们都知道py程序执行分为定义阶段和执行阶段,函数在定义阶段,只检查语法,不执行代码。类名称空间在定义阶段就产生了,且注意类在定义阶段就会执行类中的代码

def haha(): # 定义函数
	print('定义阶段函数体中的代码被执行')
# haha() # 调用函数,只要我们不要这句,运行程序,看看会不会打印就可以看出来函数定义阶段是否执行了函数体代码

class heihei(): # 定义类
	def haha():
		print('定义阶段类中的函数的函数体代码被执行')
	print('定义阶段类中代码被执行')
# 一样的我们只要不实例化类,不执行类中的函数,直接运行上面定义类的代码,如果...

那么有人就会问类中的函数在定义阶段执行代码吗?

1. 函数在定义阶段不会执行函数体代码,在执行阶段如果有调用函数的语句,那么执行阶段才会执行函数体代码
2. 如果类中有调用函数的语句(但是一般不会出现这种情况),那么在定义阶段函数体代码也会被执行,如果没有,那么定义阶段类中的函数的函数体代码不会执行。

四、属性访问

1、类属性和对象属性

在这里插入图片描述
上面这个是我小学时代,2010年左右,火的一塌糊涂的地下城与勇士,博主在以前也是地下城骨灰级玩家,这个游戏博主也很久没玩过了,60版本是400万勇士永远的情怀。好了,废话少说,上面这些就是人物角色的属性面板。我们类与对象也有自己的属性。类的属性是对象的共有属性,对象的属性是对象自己的特有属性
在这里插入图片描述在类中定义的名字,都是类的属性(对象特有属性是在类中init函数中定义的,不算是在类中定义的),细说的话,类有两种属性:数据属性和函数属性,可以通过__dict__访问属性的值,比如Student.__dict__[‘school’],但Python提供了专门的属性访问语法:

>>> Student.school # 访问数据属性,等同于Student.__dict__['school']
'清华大学'
>>> Student.choose # 访问函数属性,等同于Student.__dict__['choose']
<function Student.choose at 0x1018a2950>
# 除了查看属性外,我们还可以使用Student.school=value修改属性值或新增属性(类中不存在该属性即为新增),用del Student.school删除类的属性。

在这里插入图片描述操作对象的特有属性也是一样(增删改查)

>>> stu1.course=’python’ # 新增,等同于obj1.__dict__[‘course']='python'
>>> del obj1.course # 删除,等同于del obj1.__dict__['course']
>>> stu1.age=38 # 修改,等同于obj1.__dict__[‘age']=38
>>> stu1.name # 查看,等同于obj1.__dict__[‘name']
'李建刚'

接下来测试一下你对属性的是否了解(怎样实现检测一个类被实例化了多少次?

class Student:
    school='清华大学'
    count = 0
    def __init__(self,name,sex,age): 
        Student.count += 1
        self.name=name
        self.sex=sex
        self.age=age
# 因为是计算类被实例化了多少次,因此,这个count应该是类的属性,对象的初始化入口是在类中的init方法中,因此应该在init方法中执行加1操作!

2、属性查找顺序与绑定方法

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

(1)类的数据属性

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

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

print(id(stu1.school)) # 4301108704
print(id(stu2.school)) # 4301108704
print(id(stu3.school)) # 4301108704
(2)类中定义的函数一般都至少有一个参数,且一般叫self
1. self代表的是传入一个类的对象,一般类的函数都是给对象用的,因此函数需要知道调用我这个函数的到底是那个对象,因为函数中可能会用到对象的一些特有属性
2. 如果类中定义的函数没有参数,那么该函数只能被类调用,不能被对象调用。
(3)类使用类中函数的传参问题

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

Student.choose(stu1) # 李建刚 is choosing a course
Student.choose(stu2) # 王大力 is choosing a course
Student.choose(stu3) # 牛嗷嗷 is choosing a course
(4)类的函数属性与对象的绑定问题

类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同

# 定义类
class Student:
    school='清华大学'
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age

    def choose(self):
        print('%s is choosing a course' %self.name)
# 类的实例化
stu1=Student('李建刚','男',28)
stu2=Student('王大力','女',18)
stu3=Student('牛嗷嗷','男',38)
print(Student.choose)
print(stu1.choose)
print(stu2.choose)
print(stu3.choose)

print(id(Student.choose))
print(id(stu1.choose))
print(id(stu2.choose))
print(id(stu3.choose))
# 执行结果
<function Student.choose at 0x10c165170>
<bound method Student.choose of <__main__.Student object at 0x10c1ca150>>
<bound method Student.choose of <__main__.Student object at 0x10c1ca190>>
<bound method Student.choose of <__main__.Student object at 0x10c1ca1d0>>
4497756528 # 0x10c165170这个是16进制,转换成十进制就是这里的4497756528
4496251472
4496251472
4496251472
# 对于上面的现象可能就会有问题了

既然说绑定给不同的对象,内存地址不一样,为什么id值又是一样的?为什么Student类调用和对象调用的id值又不一样呢?

答:
1. 首先我们知道了无论是print(id(Student.choose)),还是函数直接print(Student.choose),内存地址都是一样的,这个没说的,都是打印的类名称空间中相应函数名中存的内存地址。
2. 我们需要知道,类中的方法既然绑定给了一个对象,那么这个方法就是属于此对象的,因此类的每个对象的相同绑定方法内存地址肯定是不同的,那么肯定和类名称空间的函数内存地址肯定不同了,因为绑定方法都在不同对象的对象名称空间里。
3. 上面直接打印,三个对象内存地址各不相同,那么为什么下面三个对象的id值一样呢?这就是python的一个内存优化机制,对象调用类里的方法,方法会压栈,用完之后会弹栈,用完之后又一个对象过来调用这个方法,python认为之前按那个对象的调用这个方法的内存已经废弃掉了,成了空内存,此时这个对象调用这个方法直接复用以前的内存,因此三个id值一样了,至于为何print(stu1.choose)三个不一样,需要后面的描述器知识后面补充。

绑定到对象的方法特殊之处在于,绑定给谁就应该由谁来调用,谁来调用,就会将’谁’本身当做第一个参数自动传入(方法__init__也是一样的道理)

stu1.choose()  # 等同于Student.choose(stu1)
stu2.choose()  # 等同于Student.choose(stu2)
stu3.choose()  # 等同于Student.choose(stu3)

绑定到不同对象的choose技能,虽然都是选课,但李建刚选的课,不会选给王大力,这正是”绑定“二字的精髓所在

#注意:绑定到对象方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但命名为self是约定俗成的。

猜你喜欢

转载自blog.csdn.net/weixin_44571270/article/details/106289759