一、面向对象的概念

在说python的面向对象相关知识之前,先来解释一下面向过程和面向对象。什么是面向对象?什么是面向过程?

面向过程: Procedure Oriented

一种以过程为中心的编程思想。
就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

面向对象:Object Oriented 简称OO

一种以事物为中心的编程思想。
是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

换句话来说,面向对象是以功能来划分问题,而不是步骤。

面向对象的学习:
面向对象的语法的学习
面对对象的思想的学习

面向对象的优势:
能够实现良好程序结构,方便程序的开发和管理维护!

效率:

运行效率:计算机    面向对象效率不如过程化的快
开发效率:程序员    面向对象比过程话开发要方便的多。 方便了程序员!

面向对象的结构特色:
高内聚(具有关联的功能,总结在一个结构当中)
低耦合(没有关联的功能,放在不同的结构中)

面向的对象的单词:
OO 面向对象
OOP 面向对象的开发 Object Oriented Programming
OOA 面向对象的分析 Object Oriented Analysis
OOD 面向对象的设计 Object Oriented Design
OOI 面向对象的实现 Object Oriented Implementation

OOA->OOD->OOI  面向对象程序开发的顺序(OOP)

学习面向对象离不开两个概念:类和对象

类:【类就是抽象的概念而已,(唯心主义)】

男人就是一个类
女人也是一个类
人类也是一个类
鸟类也是一个类
瓶子也是一个类
凳子也是一个类
...
以上内容都是一个简单的名词概念(语文)
【1.类是一个实物特征的集合,是一个抽象的名词概念。】

开车是一个类  (打火,挂挡,离合,加油门,走你~)
打人也是一个类 (脱掉上衣,拿起砖头,扔,赶快跑~)
做蛋糕是一个类 (和面,发酵,烘烤,切形状...抹奶油,放水果,打包~)
....
以上内容都是一个动词(语文)
【2.没有具体实施的行为也是一个类,抽象的概念,存在与脑海中】

对象:【对象是具体的实物或者行为,(唯物主义)】

明星林志玲这个美女      就是一个对象
小猪佩奇这个角色        也是一个对象
我手上这台电脑  		也是一个对象
我现在坐着的这个凳子    也是一个对象
...
【1.对象是真真正正存在的实物,你看得见摸得着,不需要想象。】

我昨天开车去菜市场          就是一个对象
刚刚去做了个拉皮			也是一个对象
我给姐姐做了的生日蛋糕      这是一个对象

【2.真实发生过的行为就是对象,不需要想像,因为真的做了】

类和对象的关系:

都是面向对象开发中必不可少的内容。
1.类是由对象总结而来的。这个过程叫做抽象化
2.对象是由类具体实施而来。这个过程叫做实例化

创建文件:
习惯使用小写字母当作文件名,可以使用下划线进行分割。

类名的书写规范:
使用驼峰命名法,
大驼峰: MyCar XiaoBaiTu…
小驼峰: myCar xiaoBaiTu…

类的组成:
女朋友:(类)
特征:性别女,肤白,貌美,大长腿…
sex = “女”
color = “皮肤白”
face = “貌美”
leg = “大长腿”

功能:洗衣,做饭,打扫卫生,陪...
        def wash():
            洗衣功能

        def cook():
            做饭功能

        def clean():
            打扫卫生功能
        ...


类中内容只有2个:属性和方法
    属性:用于描述特征的变量->成员属性
    方法:用于描述功能的函数->成员方法
    也有一种说法:类中只有属性

类的书写规则:
1.声明一个类必须使用class关键字
2.类名的声明需要符合驼峰命名法(推荐)
3.类中只能存在两种内容:属性和方法,除此之外不允许出现其他内容
4.声明成员属性的时候,变量必须赋值
5.声明成员方法的时候,按照函数声明规范即可(参数中会自动添加self参数)

实例化对象:
对象变量 = 类名()

python中有无数个数据类型,本质上所有的数据类型都是一个类

二、类和对象的属性介绍

获取类和对象中所属成员的信息
类:类名.dict
对象:对象名.dict

1.类成员的操作:(少用)
成员属性:(和变量一样)
访问 类名.成员属性名
修改 类名.成员属性名 = 新值
删除 del 类名.成员属性名
添加 类名.成员属性名 = 值

成员方法
    访问  类名.方法名(参数)
    修改  类名.方法名 = 新的函数 (不要加括号)(注意:仅限了解,不推荐使用)
    删除  del 类名.方法名  (不要加括号)
    添加  类名.新方法名 = 函数(也可以是lambda表达式)(注意:仅限了解,不推荐使用)

2.对象成员的操作:
成员属性:
访问 对象.成员属性名
修改 对象.新成员属性名 = 新值
添加 对象.成员属性名 = 值
删除 del 对象.成员属性名 (必须是属于当前对象的成员才可以)

成员方法:
    访问  对象.成员方法名()
    修改  对象.成员方法名 = 新的函数(不要加括号)
    添加  对象.成员方法名 = 函数 (不要加括号)
    删除  del 对象.成员方法名 (不要加括号)

注意事项:
1.实例化对象的时候通常情况下类中的成员不会在对象中复制一份
2.访问对象成员的时候,只要没有该成员,对象会向实例化他的类查找。
3.对象成员的添加和修改,都只会影响当前对象本身,不会影响类和其他对象
4.删除对象成员时候,必须是该对象自身的成员才可以删除,不可以删除类和其他对象~

1.类的相关知识

##1.类的数据属性
# class ChinesePeople:
#     dog = '小阳'
#
# print(ChinesePeople.dog)#小阳

#2.类的函数属性(又称之为方法)
class ChinesePeople:
    people = '人'
    def xixi():
        print('人喜欢哈哈哈!')

ChinesePeople.xixi()#人喜欢哈哈哈!

#类有一个属性字典
print(ChinesePeople.__dict__)#查看属性字典



print(ChinesePeople.__dict__['people'])
#人
ChinesePeople.__dict__['xixi']()
#人喜欢哈哈哈!


#从上面我们可以看到,其实执行ChinesePeople.dog,ChinesePeople.xixi(),
#找这个类的属性和方法,就是在这个类的属性字典中去找它们
class ChinesePeople:
    '这是一个中国人的类'  #类文档
    people = '人'
    def xixi(self):
        print('人喜欢哈哈哈!')

print(ChinesePeople.__name__)#ChinesePeople   # 类名
print(ChinesePeople.__doc__)#这是一个中国人的类    #类文档
print(ChinesePeople.__base__)#<class 'object'>  #超类
print(ChinesePeople.__module__)# __main__  #显示这个类在哪个模块
print(ChinesePeople.__class__)#<class 'type'>

2.对象的相关知识

class ChinesePeople:
    '这是一个中国人的类'  #类文档
    people = '人'
    def __init__(self,name,age,gender):
        self.mingzi = name
        self.nianji = age
        self.xingbie = gender

    def xixi():
        print('人喜欢哈哈哈!')

    def haha(self):
        print('%s 开始表演了'%self.mingzi)

p1 = ChinesePeople('chuan',18,'男')##类的实例化,本质上就是调用__init__函数的过程,
                #注意  对象p1传给了 __init__(self,name,age,gender)中的self


print(p1.__dict__) #{'mingzi': 'chuan', 'nianji': 18, 'xingbie': '男'}
                #所以,我们可以知道其实,__init__返回的(它自己会return)就是一个字典结构,
                #而且对象的字典只有属性,没有方法,方法属于类,不属于对象

print(p1.__dict__['mingzi'])#chuan
print(p1.mingzi)#chuan


print(p1.people)#人
#注意:这个实质过程是:
#首先,会在对象p1的属性字典({'mingzi': 'chuan', 'nianji': 18, 'xingbie': '男'})中去找,
#然后没有找到,会到类ChinesePeople的属性字典去找,然后找到了people


#注意:
#从上面我们知道,方法属于类的字典,不在对象的字典,那用对象调用方法肯定是不行的呀,那怎么办呢?

#不能直接用对象调用
# p1.xixi()#TypeError: xixi() takes 0 positional arguments but 1 was given
#    #从报错来看,我们可以知道,其实在调用这个方法的时候,默认给了这个函数一个
#    #参数,这个参数就是对象p1


#可以用类调用
# ChinesePeople.xixi()#人喜欢哈哈哈!

#直接用对象调用方法的解决办法:从上面的报错,我们就应该知道,给它接收的一个参数就可以
p1.haha()#chuan 开始表演了

3.类属性的增删改查

class ChinesePeople:
    '这是一个中国人的类'  #类文档
    people = '人'
    def __init__(self,name,age,gender):#
        self.mingzi = name
        self.nianji = age
        self.xingbie = gender

    def haha(self):
        print('%s 开始表演了'%self.mingzi)

    def eat(self,food):
        print('%s正在吃%s'%(self.mingzi,food))

p1 = ChinesePeople('chuan', 18, '男')  
# p1.eat('水果')

p2 = ChinesePeople('木质',20,'女')
# p2.eat('菠萝饭')

#查看类属性
print(ChinesePeople.people)#人

#修改类属性
# ChinesePeople.people = '大人'
# print(ChinesePeople.people)#大人

#增加类属性
# ChinesePeople.dang = '共产党'
# print(ChinesePeople.dang)#共产党
# print(p2.dang)#共产党

#删除类属性
# del ChinesePeople.dang

# print(ChinesePeople.dang)
#AttributeError: type object 'ChinesePeople' has no attribute 'dang',
#可以看到删除成功了
print(ChinesePeople.__dict__)

def eat_food(self,food):
    print("%s正在吃%s"%(self.mingzi,food))

ChinesePeople.eat = eat_food  #相当于eat指向了eat_food的内存地址,所以
                           #再运行就是运行ear_food函数

print(ChinesePeople.__dict__)

p1.eat("斌")

上述对类属性的操作,可以理解为对类的字典结构(dict)的字典化增删改查操作

4.实例对象属性的增删改查

class Chinese:
    country = 'China'
    def __init__(self,name):
        self.name = name

    def play_ball(self,ball):
        print('%s 正在打 %s'%(self.name,ball))

p1 = Chinese('传')
# print(p1.__dict__)#查看实例的属性字典(注意呀注意:不要修改属性字典(嘻嘻,它可以修改))

#查看实例
# print(p1.name)
# print(p1.play_ball)#

#增加
# p1.age = 18
# print(p1.__dict__)
# print(p1.age)

#注意:下面给实例添加函数属性是一个坑(一般不这样用,这里还是为了加深对此处的了解)
#    所以,总结      实例没有函数属性
#                    实例没有函数属性
#                    实例没有函数属性
#增加实例(对象)的函数属性
# def test(self):
#     print('我是来自实例的函数属性')
# p1.test = test
# print(p1.__dict__)#{'name': '传', 'test': <function test at 0x00B81E88>}
             #我们看到,实例(对象)的函数属性已经加进去了

# p1.test()#TypeError: test() missing 1 required positional argument: 'self'
#这里为什么会报一个少参数的错呢?  因为这个函数属性是实例(对象)的,只有类才会
#自动给它加一个位置参数self

# p1.test(p1)#我是来自实例的函数属性


#修改
# p1.age = 19
# print(p1.__dict__)
# print(p1.age)

5.实例的数据字典和类的数据字典

class Chinese:
    country = 'China'
    def __init__(self,name):
        self.name = name

    def play_ball(self,ball):
        print('%s 正在打 %s'%(self.name,ball))

p1 = Chinese('传')
print(p1.country)#China
p1.country = "日本"
# print(Chinese.country)#China           修改了实例的数据字典,并不影响类的实例字典
print(p1.country)#日本                #实例的数据字典被修改了

6.self这个特殊的参数
1.只是一个参数。
2.在对象使用方法的时候,当前对象会作为第一个参数的实参传入
3.self相当于语言中的代词,表示当前对象本身(其他语言中也有使用this)
4.self的作用连接整个对象的所有信息。桥梁的作用!
5.self不是关键字,只是一个参数变量名而已,可以使用其他单词代替(禁止代替)
6.方法的初步分类:
方法中具有接受对象的参数这个方法,叫做非绑定类的方法
方法中没有接受对象的参数这个方法,叫做绑定类的方法

变量误区点:

country = 'China'
class Chinese:

    def __init__(self,name):
        self.name = name
        print('--->',country) #注意:此处的country是可以打印出来的,
                 #因为此处的country即不是实例的数据属性,也不是类的
                 #数据属性,就是一个变量。此处仅仅是查一个全局变量而已。
                 #它与类没有任何关系,没有意义。

    def play_ball(self,ball):
        print('%s 正在打 %s'%(self.name,ball))

p1 = Chinese('传')

三、方法分类和@property

@property装饰器
这个装饰器功能就是把函数属性变成数据属性

class Room:
    def __init__(self,name,owner,width,length,height):
        self.name = name
        self.owner = owner
        self.width = width
        self.length = length
        self.height = height

    @property  #
    def cal_area(self):

        return  self.width * self.length


r1 = Room('厕所','alex',100,100,1000)
r2 = Room('大厅','kaka',1,1,1)
print(r1.cal_area)
print(r2.cal_area)  #我们看到函数加了@property之后,再调用函数时就不加括号了
#再注意,这种调用方式不就是调用属性的方式嘛,那么我们知道了,@property的
# 功能就是把函数变成属性(就是这种说法)

方法主要分为:静态方法和类方法

类方法:

class Student():
    sex = '男'
    def __init__(self,name,age):
        self.name = name
        self.age = age

    @classmethod   #声明为类方法,专门供类使用的方法
    def test(cls):
        print(cls)#<class '__main__.Student'>
        print('-->',cls.sex)  #类调用类的数据属性


Student.test()#hahha

s = Student('传',18)
s.test()  #注意:实例也可以调用类方法

静态方法
静态方法只是名义上的归属类管理

class A():
    name = "chuan"

    @staticmethod
    def haha():
        # print(name)  #会报错,静态方法不能使用类变量
                        #自然也无法使用实例变量
        print('我是静态方法1')
a = A()
a.haha()

四、面向对象之组合

软件重用的重要方式除了继承外,还有组合的方式
组合指的是在一个类中的数据属性的值是另外一个类的对象

class School(object):
    def __init__(self,name):
        self.name = name

    def zhaosheng(self):
        print('%s正在招生'%self.name)

class Course(object):
    def __init__(self,name,school):
        self.name = name
        self.school = school


s1 = School("北京")
c1 = Course("数据结构",s1)
print(c1.__dict__)
c1.school.zhaosheng()

组合与继承都是有效的利用已有类的资源的重要方式,但二者的概念和使用场景不同
1.继承的方式
通过继承建立了派生类与基类之间的关系,他们是一 种"是"的关系,比如人是动物
当类之间有很多相同功能,提取这些相同的功能做成基类,用继承比较好,例如男人是人,女人是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种"有"的关系,就是包含关系,比如人有生日

什么时候用组合?什么时候用继承?

#a.当类之间有显著不同,并且较小的类是较大的类的所需要的
#组件时,用组合比较好

#例如:描述一个机器人类,机器人这个大类是由很多互不相关的
#小类组成,如机械胳膊类、腿类、身体类、电池

#b.当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承较好

#例如
#猫可以:喵喵叫、吃、拉、撒
#狗可以:汪汪叫、吃、拉、撒

五、面向对象之继承

1.概念

#4.继承的两种含义

含义一、继承基类的方法,并且做出自己的改变或者扩展(优点是代码重用)
含义二、声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法

#实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

#继承的第二种含义非常重要。它又叫“接口继承”

#接口继承实质上是要求"做出一个良好的抽象,这个抽象规定了一个兼容接口,
#使得外部调用者无需关心具体细节 ,可一视同仁的处理实现了特定接口的
#所有对象 " -------这在程序设计上,叫做归一化。

2.接口继承

一般的继承这里不再累述,这里 讲解一下继承的第二种含义,接口继承

由于python 没有抽象类、接口的概念,所以要实现这种功能得abc.py 这个类库

@abstractmethod:抽象方法,
1.含abstractmethod方法的类不能实例化,
2.继承了含abstractmethod方法的子类必须复写所有abstractmethod装饰的方法,未被装饰的方法可以不重写

import abc

class All_file(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def read(self):
        pass
    @abc.abstractmethod
    def write(self):
        pass

    def xi(self):
        pass

class Disk(All_file):
    def read(self):
        print('disk read')
    def write(self):
        print('disk write')

3.多继承

#1.python的类可以继承多个类
#2.python的类如果继承了多个类,那么其寻找方法的
#方式有两种,分别是:深度优先和广度优先

#新式类(继承object)用广度优先 python3以上都是新式类
#经典类 用深度优先

#终极解密:python到底是如何实现继承的,对于你定义的每一个
#类,python会计算出一个方法解析顺序(MRO)列表,这个MRO
#列表就是一个简单的所有基类的线性顺序列表

#为了实现继承,python会在MRO列表上从左到右开始查找基类,
#直到找到第一个匹配这个属性的类为止

class A(object):
    pass
class B(A):
    pass
class C(A):
    pass
class D(B):
    pass
class E(C):
    pass
class F(D,E):
    pass

print(F.__mro__)#类的__mro__属性展示了,查找顺序

#(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>,
#  <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>,
#  <class 'object'>)

六、面向对象之多态

总结:不同的对象调用相同的方法,产生相同的行为
多态是建立在继承的基础上的,没有继承就没有多态。

七、面向对象之封装

第一个层面的封装:类本身就是一种封装
第二个层面的封装:类中定义私有的,只在类的内部使用,外部无法访问

python不依赖语言特性去实现第二层面的封装,而是通过遵循一定的数据属性和函数属
性的命名约定来达到封装的效果

约定一:任何以单下划线开头的名字是受保护的,

约定二:双下划线开头的名字是私有的

注意:python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块
名以单下划线开头,那么from module import * 时不能被导入,但是你from module
import _private_module依然是可以导入的

第三个层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,
并且为封装到内部的逻辑提供一个访问接口给外部使用(这才是真正的封装,)

class Student:
    __id = '123'
    _sex = 'nan'
    def __init__(self,name):
        self.name = name

s1 = Student('传')

print(s1._sex)  #受保护的可以直接访问


print(s1.__id)#
#
# #注意:当以'__'在开头命名的数据属性,python会自动在
# #变量的前面加一个'_类名'
print(s1._Student__id)#123

八、类的一些内置方法补充

1.类的内置attr方法

class Foo:
    x = 1
    def __init__(self,y):
        self.y = y

    def __getattr__(self, item):
        print('执行__getattr__')

    def __delattr__(self, item):
        print('删除操作')

    def __setattr__(self, key, value):
        print('__setattr__执行')
        # self.key = value
        self.__dict__[key] = value

f1 = Foo(10)  #触发一次__setattr__
print(f1.y)
# # print(getattr(f1,'y'))
# # f1.sssssssss #执行__getattr__    #当调用一个根本不存在的属性时,才会触发__getattr__
#
# # del f1.y  #删除的时候会触发__delattr__
#
# f1.z = 2  #触发一次__setattr__
#

2. __getattribute__方法

class Foo:
    name = "chuan"
    def __getattribute__(self, item):
        print(item)
f1 = Foo()
f1.name  #属性存在
f1.age   #属性不存在

#总结,属性存在与否都会触发__getattribute__方法

getattribute__的特性:
1.不管属性存不存在都会触发
2.触发分为两种情况:
a.属性存在的情况,会在当前实例的属性字典里找到
这个值并返回
b.如果不存在的情况,会raise一个异常,由__getattr

来接收这个异常

3.item系列方法

#下面的这些方法是字典方式对对象属性进行操作
class Foo:
    def __getitem__(self, item):
        print('getitem')
        return self.__dict__[item]

    def __setitem__(self, key, value):
        print('setitem')
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('delitem')
        self.__dict__.pop(key)
    pass
f1 = Foo()
print(f1.__dict__)
f1['name'] = 'egon'   #字典方式对对象属性进行操作
# f1['age'] = 18
print(f1.__dict__)

#对象.属性这种方式触发的是__***attr__这类型的方法
# del f1.name
# print(f1.__dict__)
# print(f1.age)

del f1['name']
print(f1.__dict__)

# print(f1['age'])

4._str__和__repr

str,__repr__都是控制输出的(通过return)

l = list(‘list’)
print(l)#[‘l’, ‘i’, ‘s’, ‘t’]

照理说,list(‘list’)不应该是类list返回的一个对象吗?可为什么打印结果却是[‘l’, ‘i’, ‘s’, ‘t’]呢?

其实就是__str__进行的操作造成的

下面我们来看下面的代码

class Foo:
    def __str__(self):
        return '自定制的对象的显示方式'

f1 = Foo()
print(f1)#执行的本质就是str(f1)--->f1.__str__() #自定制的对象的显示方式
#我们看到打印的值就发生了改变

s =str(f1)
print(s)#自定制的对象的显示方式
#我们看到和上面的打印结果是一样的

print() 触发__str__
解释器 触发__repr__

__str__主要是为了面向用户的,而__repr__主要是为了面向程序员的

当print()的时候,它首先会找__str__,
如果找不到__str__,它就会找__repr__

5.__slots__方法

3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,
为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过
一个很小的固定大小的数组来构建,而不是为每一个实例定义一个字典,这跟元组和列表很类似。
在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用
__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用
在__slots__中定义的那些属性名

4.注意事项:
slots__的很多特性都依赖于普通的基于字典的实现。另外,定义
了__slots__后的类不再支持一些普通类特性了,比如多继承。大多数情况,
你应该只在那些经常被使用到的用作数据结构的类上定义__slots
。比如在
程序中需要创建某个类的几百万个实例对象。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实
例增加新的属性。尽管__slots__可以达到这样的目的,但是这个并不是它
的初衷。更多的是用来作为一个内存优化工具

class Foo:
    __slots__ = 'name'#设置这个类的实例只能有一个属性
    # __slots__ = ['name','age']#设置这个类的实例只能有着两个属性

f1 = Foo()
f1.name = 'egon'
print(f1.name)

# f1.age = 18
#AttributeError: 'Foo' object has no attribute 'age'
#我们发现报错了,说明age这个属性并没有存进f1这个属性字典
#我们在看下面的代码

# print(f1.__dict__)
#AttributeError: 'Foo' object has no attribute '__dict__'
#我们看到,连这个字典属性也没有,所以说,属性肯定是存不
#进去的。


#以上,我们就看到了slots的魔力
print(Foo.__slots__)
print(f1.__slots__)

6.__call__方法

class Foo:
    def __call__(self, *args, **kwargs):
        print('实例执行了')

f1 = Foo() #其实实例对象就是__call__方法返回的
f1()#只要有call方法,就可以加小括号执行

'''
f1() #实质上是执行Foo 下的__call__方法
'''