30、初识面向对象编程

面向对象 / 2020-12-24

面向对象编程

此前曾经初识面向过程、函数式的编程思想,今天主题的面向对象也是一种编程思想!编程思想是一种概念,因此所有编程语言都可以套用,而不是专属于某种编程语言!Python这门编程语言注重面向对象的思路!

一、面向对象基本概念

面向对象:顾名思义它的核心是“对象”,对象即是容器,有容器技术基础的朋友应该非常熟悉容器这概念!我的博客中也有关于容器技术基础的几篇文章,有兴趣可以去看看!这里简单总结一下:

程序结构由数据+指令(功能)组成!Python而言,将关联性比较紧密的功能和数据打包到一块,就可以称为容器,也就是对象!此前所学能将数据与功能组合在一起的方法有哪些呢,首先浮出我脑海的是模块!函数不行,函数是存放功能的,虽然也能用定义变量方式提供数据,但只有调用时才生效!字典、列表等容器类型数据可以存放任何数据,但是搞起来也是失去了代码可读性与清晰度!所以Python为我们提供了一种语法,允许我们将数据和功能很好地组合打包到一起,使得代码整体逻辑更清晰!

二、类和对象

类,即类别,可将对象分门归类,用于存放同类对象之间共同的属性等,所以类它也是一种容器!还是像上次一样举例吧,人类是一个类别,那么人类对象之间共同属性有哪些?很明显啦:长期直立行走、拥有双手双脚、能使用工具等等属性,作为人类这个类别下的具体的个人比如我自己,就是人类的中一个对象!

2.1、类的定义

程序中先定义类,后调用类产生对象!使用class来定义类,与定义函数名不一样的是,类的名称推荐使用驼峰体而不是纯小写!

class Student:
    province = 'GuangDong'
    city = 'GZ'
    school = 'Python'

类是对象,是关联性较强的数据与功能的集合体,所以类中最常见的是变量与函数的定义,但是类体中其实是可以包含任意其它的代码的,与函数不一样的是类体代码是在定义阶段就会立即执行!

class Student:
    province = 'GuangDong'
    city = 'GZ'
    school = 'Python'
    print('我是类Student')
    
# 执行得到结果
我是类Student

2.2、访问类的属性

方式一:dict

使用__dict__方法可以得到类的所有名称空间,也可以在赋值变量后单独查看某个属性所对应的值!

var = Student.__dict__
print(var)
print(var['province'])

# 执行得到结果
{'__module__': '__main__', 'province': 'GuangDong', 'city': 'GZ', 'school': 'Python', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
GuangDong
方式二:直接访问

使用Python专门的类属性访问语法,类名+点+名称!

print(Student.province)

# 执行得到结果
GuangDong

2.3、类的实例化

实例化其实就是调用类产生新对象,弄一堆乱七八糟的让人看不懂的术语真的烦!

sanxi = Student()
print(sanxi.school)

# 执行得到结果
Python

2.4、为对象定制独有属性

2.4.1、直接赋值新属性

新对象调用__dict__方法用字典创建新元素方式创建新值,这样当然麻烦,可以用上面的方式!下面是两种方式的混合演示:

# 强调:类存放的是同类对象中的相同属性,下例中属性为对象自行定制属性,改的是它自己,所以其它对象是没有这些属性的!
sanxi.__dict__['age'] = 18
sanxi.gender = 'male'
print(sanxi.gender)
print(sanxi.__dict__['age'])

# 执行得到结果
male
18
2.4.2、函数定义新属性

像上面那样,每次都要手动定义属性,麻烦!但是直接定义函数,该函数又是独立于类而存在,这样契合程度就不够高了,因为我们上面说过把关联性比较紧密的数据和功能打包到一块就可以称之为容器或者说对象!因此我们需要把这个函数纳入类中,但是函数不调用是不会生效的,如何让其自动生效?在函数名前后各加两个__即可,调用类时会自动调用这个加料的函数,Python自动会将调用者当作第一个参数传入!

这就是为什么这么多模块里的函数第一个参数为self的真正意义,self即自己,不用手动传,但是必须得定义这个位置参数!

class Student:
    province = 'GuangDong'
    city = 'GZ'
    school = 'Python'

    def __init__(self, name, age, gender):  # init不是固定名称,只是用它可增强代码可读性,一看便知是初始化!
        self.name = name
        self.age = age
        self.gender = gender


stu1 = Student('sanxi', 18, 'male')
print(stu1.__dict__)

# 执行得到结果
{'name': 'sanxi', 'age': 18, 'gender': 'male'}

2.5、实例化发生的事情

以2.4.2为例

2.5.1、产生空对象

先产生一个空对象stu1

2.5.2、传入参数

Python会自动调用类中的函数,并将括号里的参数一并传给该函数,在调用类时自动触发执行,用来为对象初始化自己独有的属性或者数据!虽然函数内可以存放任意代码,除非有需要调用时就立即执行的代码,否则该函数内只应该存放为对象初始化属性的功能

2.5.3、返回对象

该函数必须返回None,不能是其它,有则报错!意味着不能设定返回值,因为Python默认返回None!

三、属性查找与绑定方法

从2.4.2中可得知,对象中仅存放自身独有的属性,对象之间共有的属性存放于类中,这是为了解决空间!那么对象在访问属性时,优先从自身找起,无则去类中找!因此可得出以下结论:

3.1、共有变量属性内存地址一致

即类调用共有属性和对象调用共有属性,它们的内存地址是一样的,很容易理解,既然大家都是用的一个东西,那就只保留一个就好了,其它人需要调用的时候直接读取就行!

print(id(Student.province))
print(id(stu1.province))

# 执行得到结果
20604872
20604872

3.2、函数属性内存地址不一致

类中定义的函数是给对象来调用的,而且是绑定给对象使用的(还记得self参数吗!),因此类直接调用函数跟对象调用函数是不一样的,所以内存地址也是不一样的!

# 假设我们在Student类中添加以下函数
def print_str(self):
    print('我是', self)
# 多加一个对象更好显示区别
stu1 = Student('sanxi', 18, 'male')
stu2 = Student('chrystal', 18, 'female')

print(id(Student.print_str))
print(id(stu1.print_str))
print(id(stu2.print_str))
stu1.print_str()
stu2.print_str()

# 执行得到结果
19837760
19197384
19197384
我是 <__main__.Student object at 0x0129E4F0>  # 不同对象的内存地址肯定不一样啦,因为独有属性都不一样!
我是 <__main__.Student object at 0x012EC028>
世间微尘里 独爱茶酒中