08-01 面向对象编程

一. 对象的概念

1. 从"过程"过度到"对象"的概念介绍

# 前言: 了解面向过程
"""
面向过程:
    核心是"过程"二字
    目的: 过程的终极奥义就是将程序流程化
    作用: 过程是"流水线",用来分步骤解决问题的
"""

# 面向对象
"""
面向对象:
	核心是"对象"二字
    目的: 对象的终极奥义就是将程序"整合"
    作用: 对象是"容器",用来盛放数据与功能的
面向对象编程的好处: 
	提高程序的解耦合性, 进而提升程序的可扩展性(注意: 面向对象解决的仅仅是程序的可扩展性问题, 它并不是万能的.)
"""

2. 了解"对象"之前我们的数据和功能是分开的

# 在"了解到"对象之前, 程序的数据与功能是分离开的
# 数据: stu_name, stu_age, stu_sex
stu_name = 'egon'
stu_age = 18
stu_sex = 'male'

# 功能(函数): tell_info, set_info
# 此时若想执行查看个人信息的功能,需要同时拿来两样东西,一类是功能tell_info,另外一类则是多个数据name、age、sex,然后才能执行,非常麻烦
def tell_info():
    print('名字:%s 性别:%s 年龄:%s' % (stu_name, stu_age, stu_sex))
    
def set_info():
    stu_name = 'EGON'
    stu_age = 20
    stu_sex = "female"

3. 了解了"对象"以后我们把数据和功能进行整合

# 学生的容器 = 学生的数据 + 学生的功能
def tell_info():
    print('名字:%s 性别:%s 年龄:%s' % (stu1_obj['stu_name'], stu1_obj["stu_age"], stu1_obj['stu_sex']))


def set_info(name, age, sex):
    stu1_obj['stu_name'] = name
    stu1_obj['stu_age'] = age
    stu1_obj['stu_sex'] = sex


stu1_obj = {
    'stu_name': 'EGON',
    'stu_age': 20,
    'stu_sex': 'female',
    'tell_info': tell_info,
    'set_info': set_info,
}

二. 类与对象

# 什么是类?
"""
类也是"容器",该容器用来存放同类对象共有的数据与功能
"""

# 对象归类的好处
"""
极大地节省了空间和减少代码冗余: 把同一类对象相同的数据与功能存放到类里,而无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可.
"""

三. 面向对象编程

1. 类的定义与实例化 + 属性访问之类属性与对象属性

"""
# 提示: 类是同类对象们相似功能的集合体
# 类体代码的范围:
	类体代码最常见的是变量与函数名的定义, 但是类体代码其实是可以包含任意其它代码的.
# 类体代码的生效时间(注意):  类体代码在定义阶段就会立即执行, 会产生类的名称空间. 我们可以是用 类名.__dict__ 访问到产生的类体代码的名称空间.
"""


# 一. 类的定义
class Student:
    # 变量的定义
    stu_school = 'oldboy'

    # 函数功能的定义
    def tell_stu_info(stu_obj):
        print('学生信息:名字:%s 年龄:%s 性别:%s' % (
            stu_obj['stu_name'],
            stu_obj['stu_age'],
            stu_obj['stu_gender']
        ))

    def set_info(stu_obj, x, y, z):
        stu_obj['stu_name'] = x
        stu_obj['stu_age'] = y
        stu_obj['stu_gender'] = z

    print('========>')  # 类体代码在定义阶段就会立即执行


# 1. 类名.__dict__ 访问到产生的类体代码的名称空间.
print(Student.__dict__)
# 2. 属性访问(属性访问的语法)
# 访问数据属性
print(Student.stu_school)  # 等同于Student.__dict__['stu_school']
# 访问函数属性
print(Student.set_info)  # 等同于Student.__dict__['set_info']

# 3. 属性修改与添加(Student类体代码中 没有如下x数据属性就添加 有就修改)
Student.x = 1111  # Student.__dict__['x']=111
print(Student.__dict__)

# 二. 调用类产生对象
stu1_obj = Student()
stu2_obj = Student()
stu3_obj = Student()

# 因为这三个对象没有定制自己独有的属性, 因此都是空对象, 它们的名称空间都是{}
print(stu1_obj.__dict__)
print(stu2_obj.__dict__)
print(stu3_obj.__dict__)

# 为对象定制自己独有的属性
# 问题1: 代码冗余
# 问题2: 属性的查找顺序
stu1_obj.stu_name = 'egon'  # stu1_obj.__dict__['stu_name']='egon'
stu1_obj.stu_age = 18  # stu1_obj.__dict__['stu_age']=18
stu1_obj.stu_gender = 'male'  # stu1_obj.__dict__['stu_gender']='male'
print(stu1_obj.__dict__)

stu2_obj.stu_name = 'lili'
stu2_obj.stu_age = 19
stu2_obj.stu_gender = 'female'
print(stu2_obj.__dict__)

stu3_obj.stu_name = 'jack'
stu3_obj.stu_age = 20
stu3_obj.stu_gender = 'male'
print(stu2_obj.__dict__)


# 解决问题一: 既然是代码冗余, 我们可以定义函数, 解决这个问题
def init(stu_obj, name, age, sex):
    stu_obj.stu_name = name
    stu_obj.stu_age = age
    stu_obj.stu_sex = sex


init(stu1_obj, 'egon', 18, 'male')
init(stu2_obj, 'lili', 19, 'female')
init(stu3_obj, 'jack', 20, 'male')

print(stu1_obj.__dict__)  # 通过以上我们定义的init函数我们成功为3者对象创建了属于它们独有的属性
print(stu2_obj.__dict__)
print(stu3_obj.__dict__)


# 解决问题二:
class Student:
    # 在类Student中使用__init__方法会在调用阶段就会执行__init__的功能并进行如下方式把参数传入.
    # 空对象, 'egon', 18, 'male'
    def __init__(stu_obj, name, age, sex):
        stu_obj.stu_name = name  # 空对象.__dict__['stu_name'] = 'egon'
        stu_obj.stu_age = age  # 空对象.__dict__['stu_age'] = 18
        stu_obj.stu_sex = sex  # 空对象.__dict__['stu_sex'] = 'male

    def tell_stu_info(stu_obj):
        print('学生信息:名字:%s 年龄:%s 性别:%s' % (
            stu_obj['stu_name'],
            stu_obj['stu_age'],
            stu_obj['stu_gender']
        ))

    def set_info(stu_obj, x, y, z):
        stu_obj['stu_name'] = x
        stu_obj['stu_age'] = y
        stu_obj['stu_gender'] = z

# 三. 再调用类产生对象
"""
# 调用类的过程中又称之为实例化, 这里发生的3件事
1. 先产生一个空对象
2. python会自动调用__init__方法, 然后将空对象和调用类时时括号内的参数以一同当作实参传给__init__方法
3. 最后返回初始化完成的对象
"""
stu_obj = Student('egon', 18, 'male')  # Student.__init__(空对象, 'egon', 18, 'male')
stu_obj1 = Student('lili', 19, 'female')
stu_obj2 = Student('jack', 20, 'male')

print(stu_obj.__dict__)
print(stu_obj1.__dict__)
print(stu_obj2.__dict__)


# 总结__init__方法
"""
1. 会在调用类时自动触发执行, 用来为对象初始化自己独有的数据
2. __init__内应该存放的是为对象初始化属性的功能, 但是是可以存放任意其他代码, 想要在类调用是就立刻执行的代码都可以放到该方法内
3. __init__方法的返回值必须返回值None(提示: 无需指定, 默认返回None)
"""

2. 属性访问之属性查找顺序与绑定方法

class Student:
    stu_school = 'oldboy'
    count = 0
    # 注意:绑定到对象方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但命名为self是约定俗成的。
    # 空对象,'egon',18,'male'
    def __init__(self, name, age, sex):
        Student.count += 1

        self.stu_name = name  # 空对象.__dict__['stu_name'] = 'egon'
        self.stu_age = age  # 空对象.__dict__['stu_age'] = 18
        self.stu_sex = sex  # 空对象.__dict__['stu_sex'] = 'male

    def tell_stu_info(self):
        print('学生信息:名字:%s 年龄:%s 性别:%s' % (
            self.stu_name,
            self.stu_age,
            self.stu_sex
        ))

    def set_info(self, name, age, sex):
        self.stu_name = name
        self.stu_age = age
        self.stu_sex = sex

    def choose(self, x):
        print(f'{self.stu_name}正在选课:{x}')
        self.course = x


stu1_obj = Student('egon', 18, 'male')  # Student.__init__(空对象, 'egon', 18, 'male')
stu2_obj = Student('lili', 19, 'female')
stu3_obj = Student('jack', 20, 'male')

# 这里统计被实例化的的次数。我们看到上面实例化了三次,所以下面的访问结果都是: 3
print(stu1_obj.count)
print(stu1_obj.count)
print(stu1_obj.count)


# 类中存放的是对象共有的数据和功能
# 一:类可以访问:
# 1、类的数据属性
print(Student.stu_school)
# 2、类的函数属性
print(Student.tell_stu_info)
print(Student.set_info)


# 二:但其实类中的东西是给对象用的
# 1、类中定义的数据属性是共享给所有对象用的,大家访问的地址都一样.
print(id(Student.stu_school))
print(id(stu1_obj.stu_school))
print(id(stu2_obj.stu_school))
print(id(stu3_obj.stu_school))


# 类的数据属性只能通过类改变, 一旦改变类的数据属性, 所有对象共用的类的数据属性也将会被改变
print(stu1_obj.stu_school)  # oldboy
print(stu2_obj.stu_school)  # oldboy
print(stu3_obj.stu_school)  # oldboy
Student.stu_school = 'OLDBOY'
print(stu1_obj.stu_school)  # OLDBOY
print(stu2_obj.stu_school)  # OLDBOY
print(stu3_obj.stu_school)  # OLDBOY


# 但是对象想改变类中的属性是不被允许的, 对象对默认不存在的属性会在自己局部中申明, 并不会影响其他对象, 更不会修改类的数据属性.
stu1_obj.stu_school = 'XXX'
print(stu1_obj.stu_school)  # XXX
print(Student.stu_school)  # OLDBOY
print(stu2_obj.stu_school)  # OLDBOY
print(stu3_obj.stu_school)  # OLDBOY


# 2. 类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同
# 类调用自己的函数属性必须严格按照函数的用法来
print(Student.tell_stu_info)  # <function Student.tell_stu_info at 0x00000209620C8DC0>
print(Student.set_info)   # <function Student.set_info at 0x00000209620C8EE0>

Student.tell_stu_info(stu1_obj)  # 学生信息:名字:egon 年龄:18 性别:male
Student.tell_stu_info(stu2_obj)  # 学生信息:名字:lili 年龄:19 性别:female
Student.tell_stu_info(stu3_obj)  # 学生信息:名字:jack 年龄:20 性别:male

Student.set_info(stu1_obj, 'EGON', 99, 'female')
Student.tell_stu_info(stu1_obj)  # 学生信息:名字:EGON 年龄:99 性别:female

# 绑定方法的特殊之处在于:谁来调用绑定方法就会将谁当做第一个参数自动传入
print(Student.tell_stu_info)   # <function Student.tell_stu_info at 0x000002126A568DC0>
print(stu1_obj.tell_stu_info)  # <bound method Student.tell_stu_info of <__main__.Student object at 0x000002126A53A6A0>>
print(stu2_obj.tell_stu_info)  # <bound method Student.tell_stu_info of <__main__.Student object at 0x000002126A5504F0>>
print(stu3_obj.tell_stu_info)  # <bound method Student.tell_stu_info of <__main__.Student object at 0x000002126A550D90>>


stu1_obj.tell_stu_info()  # tell_stu_info(stu1_obj)
stu2_obj.tell_stu_info()  # tell_stu_info(stu2_obj)
stu3_obj.tell_stu_info()  # tell_stu_info(stu3_obj)


stu1_obj.choose('python从入门到放弃')
stu2_obj.choose('python爬虫之从入门到入狱')
stu3_obj.choose('python自动化开发到迎娶白富美')


# 拓展: Python中一切皆为对象,且Python3中"类"与"类型"是一个概念,因而绑定方法我们早就接触过
li1 = [1, 2, 3]
li2 = list([1, 2, 3])  # 上面其实就是在调用类list实例化了一个对象li1

# 每个对象下面都可以访问到类"list"中的公共的函数属性, 基于对象的角度我们称之为绑定方法, 且绑定到不同的对象就是不同的绑定方法, 内存地址个不相同
print(li1.append)  # <built-in method append of list object at 0x000002E2C8070C80>
print(li2.append)  # <built-in method append of list object at 0x000002E2C8129140>

# 每个实例化出来的对象都是独立的个体, 因此我们往li1对象中添加值, 并不会影响到li2
li1.append(4)
print(li1)  # [1, 2, 3, 4]
print(li2)  # [1, 2, 3]

# Python3中"类"与"类型"是一个概念
print(type(li1))  # <class 'list'>
print(type(li2))  # <class 'list'>

3. 小结

"""
在上述介绍类与对象的使用过程中, 我们更多是站在底层原理的角度去介绍类与对象之间的关联关系, 如果知识站在使用的角度, 我们无需考虑语法"对象.属性"中的属性到底来自哪里, 只需要直到通过对象获取到的就可以了, 其实通过上面的刨析我们知道如果是数据属性,有对象自己独有的,也有所有对象共有的也就是在类中定义的数据属性. 如果是函数属性, 一般都是在类中定义的, 且通过"对象.函数属性()"调用, 如果是对象调用, 这个函数属性就是改对象的绑定方法, 就会把对象作为第一个参数传入, 而如果是类调用我们它就是一个普通的函数, 函数怎么传值的, 类就必须按照这中方式传值, 这个时候就没有绑定方法这么一说了. 所以说对象是一个由数据+功能高度整合后的产物, 有了对象, 我们就只需要使用"对象.XXX"的语法就可以得到跟这个对象相关的所有数据与功能, 十分方便. 且对于整个程序来说, 提升了解耦合性, 进而极大的提高了可扩展性.
"""
posted @ 2020-04-07 17:07  给你加马桶唱疏通  阅读(186)  评论(0编辑  收藏  举报