欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

关于 python 类和对象的最完整解释(持续修订中)

最编程 2024-05-26 16:12:30
...

文章目录

  • 类和对象
    • 面向对象概述
      • 对象
      • 面向对象程序设计的特点
    • 类的定义和使用
      • 定义类
      • 创建类的实例(创建对象)
      • 命名空间
      • 魔术方法
      • 创建类的成员并访问
      • 访问限制
    • 属性
      • 创建用于计算的属性
      • 为属性添加安全保护机制
    • 类与类之间的关系
      • 依赖(关联)
      • 组合(聚合)
      • 继承(实现)
        • 继承的基本语法
        • 淇淇的进阶课堂
        • 方法重写
        • 派生类中调用基类的方法
        • 淇淇的进阶课堂
      • 多继承和多重继承的区别
      • 补充内容`isinstance()及issubclass() `
      • super
    • 封装性
      • 栈:先进后出
      • 队列:先进先出
        • 淇淇的进阶课堂
      • 封装的定义
        • 私有属性
        • 私有方法
      • 破解私有属性
    • 多态性
      • 多态
      • 多态性
    • 设计模式
      • 单例模式
        • 实现单例模式的几种方式
      • 工厂模式
    • 约束
      • 约束的定义
    • 实例方法
    • 类方法
    • 静态方法
      • property
    • 反射.
    • 内置方法
    • 异常机制

类和对象

面向对象概述

什么叫做面向对象?

​ 所谓的面向对象其实就是把属性和方法封装起来,以供重复调用。

比如人:

​ 人的属性:姓名,年龄,性别

​ 人的方法:唱,跳,rap

之前我们没有学习面向对象的时候会这样定义:

伪代码:

姓名 = “帅帅”
年龄 = 18
性别 = “superman”
def():
    print(“唱的真好听”)
def():
    print("跳的真好看")
def rap():
    print("rap世界第一")

这样写有一个弊端,就是只有帅帅,淇淇不服啊。为什么她不出来一下?那么下面我们在实现淇淇的伪代码:

姓名 = “淇淇”
年龄 = 18
性别 = “girl”
def():
    print(“唱的真好听”)
def():
    print("跳的真好看")
def rap():
    print("rap世界第一")

这个时候淇淇的伪代码就完成了,但是淇淇在某些方面是不如帅帅的,这个时候我们要把函数给重新架构一下,成下面这样:

伪代码:

姓名 = “淇淇”
年龄 = 18
性别 = “girl”
def():
    print(“唱的没帅帅真好听”)
def():
    print("跳的没帅帅好看")
def rap():
    print("rap世界第二,搁帅帅后面排着")

到此,我们写完了两个人的,那么如果我们要写很多人呢?这个时候是不是可以把他们给抽象出来,变成一张图纸一样,用的时候直接拿过来用就可以?我们尝试着写成以下的伪代码:

伪代码:

人:
	属性:
        姓名
        年龄
        性别
    方法:
    	唱
        跳
        rap   

这个时候我们发现,只要是人,就可以拥有着写属性和方法。那么这个时候我们可以造出很多这样的人,比如薛之谦,王一博,周杰伦,他们有个共同的特征就是拥有人的属性和方法。

那么在python里面我们是怎样解决的呢?且看我把上面的伪代码实现

class Person:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    
    def sing(self):
        print(f"{self.name}唱的真好听")
    def dump(self):
        print(f"{self.name}跳的真不错")
    def rap(self):
        print(f"{self.name}世界第一")

这个时候我们肯定很奇怪,这不就是把之前的属性和方法前面套了一个class嘛,有什么神奇的?

下面我带你见证这一神奇的时刻:

shuaishuai = Person("帅帅",18,"superman")
shuaishuai.sing()
shuaishuai.dump()
shuaishuai.rap()
#结果:
帅帅唱的真好听
帅帅跳的真不错
帅帅世界第一

到这是不是还是有些不服,这上面的代码完全可以实现,我这写的都多了,是不是?

那么我们在把淇淇也写出来:

qiqi = Person("淇淇",18,"beautifulgirl")
qiqi.sing()
qiqi.dump()
qiqi.rap()
#结果
淇淇唱的真好听
淇淇跳的真不错
淇淇世界第一

这个时候是不是也没多少感觉,只是减少了一点点代码而已啦,那么接下来就是见证帅帅有多帅的时刻了:

zhoujielun = Person("周杰伦",28,"man")
wangyibo = Person("王一博",21,"man")
xuezhiqian = Person("薛之谦",32,"man")
linjunjie = Person("林俊杰",25,"man")

连续生成四个人,而且他们四个人都可以进行唱,跳,rap

你要用以前的方式写,现在都好几十行了,而我这,短短的四行。

这就是面向对象的魅力

对象

什么叫做对象呢?

​ 这道题就是送分题,你的女朋友或者你的男朋友。这里有个狗粮,帅帅&淇淇。单身狗看我博客是不是特别心酸?

​ 开个玩笑,对象就是实际生活中具体的事物。比如你的车就是一个对象,你的小狗就是一个对象,我的手机也是一个对象。我手机老破了,太难过。

python中一切皆对象,linux中一切皆文件

淇淇的进阶课堂:判断下面那个是对象

我的电脑

你的电脑

电脑

鼠标

键盘

手机

我的自行车

Alex的特斯拉

我假装有个特斯拉

什么叫做类?

类是封装对象的属性和行为的载体

类是对象的抽象。刚才我们说了对象就是具体的事物。那么比如我的车,你的车都是对象,这个时候他们有个统一的标准,比如四个轮胎,一个方向盘,四个座椅。而这个时候我们说车的时候,就会知道他会有我们所说的那些属性和方法。

我们住的房子,他就是对象,但是设计房子的图纸,他就是类。他可以设计成各种各样的,然后由我们勤劳的劳动人民去实现。比如天津的网红图书馆,天津的失恋博物馆(好像在这说这个有点尴尬,毕竟俺是有对象的人)。

那么我们刚才说的什么馆,抽象出他们的相同点,他们统一有一个专有名词,叫做馆。这个就是类。

总结:类是对象的抽象,对象是类的具体实例。

面向对象程序设计的特点

面向对象的三大特性:封装,继承,多态

封装:

我们使用电视遥控器,只需要知道按上下就可以进行换台操作,而不知道遥控器是具体怎么实现的,这就是封装。所谓封装,就是把具体实现隐藏起来,别人使用的时候只给固定的结构进行调用。

继承:

继承跟我们现实生活中的继承类似,你爸有好几套房,看你长大了,老在外面租房住,感觉不太好,送了你一套。这就是继承,而面向对象的继承比现实生活中强了不知道无数倍。继承来的东西你的父类还是存在。也就是说,你爸有十套房,你继承之后你也会有十套房,而且你爸还不会少。不像我们现实世界,你爸给你之后,你爸就没了这十套房。

多态:

一种事物的不同表现形态。python中是非常喜欢鸭子模型的,就是有个鸟,它会游泳,它会跑,它会叫,它就是鸭子。关于多态请看后面的多态详细描述。

类的定义和使用

类表示具有相同属性和方法的集合,使用类就是创建对象。

定义类

固定结构:

class 类名:  #类名一般使用大驼峰命名
    类属性
    类方法

写好了类之后并不能直接使用。就跟房子的图纸一样,你写好并不能进行住,需要盖好才可以。

创建类的实例(创建对象)

class Person:
    def __init__(self,name,age,sex): #self:代表当前对象 剩下的三个属性在类创建对象的时候必须实现
        #类属性,可以在__init__中添加,也可以在其他方法里面添加
        self.name = name
        self.age = age
        self.sex = sex
	
    #类方法
    def sing(self):
        print(f"{self.name}唱的真好听")
    def dump(self):
        print(f"{self.name}跳的真不错")
    def rap(self):
        print(f"{self.name}世界第一")
        
shuaishuai = Person("帅帅",18,"superman")

删除对象属性:del 对象名.属性名不建议删除

__init__魔术方法

当类中写了这个方法的时候,第一个参数必须是self,代表当前对象,然后第二个参数开始就随意,但是这些参数在创建对象的时候必须一一实现。

创建对象的时候自动调用__init__方法

命名空间

类空间和对象空间

类也遵从自上而上执行,所以如果类里面有同名的方法,前一个方法会被后一个方法覆盖。

然后对象初始化的时候,会调用类覆盖好的方法

# 创建一个类,能够自动统计当前类创建了多少个对象
class Person():
    num = 0
    def __init__(self):
        Person.num += 1
print(Person.num)
Person()
Person()
Person()
Person()
print(Person.num)

魔术方法

python中前后用两个下划线包起来的叫做魔术方法

python3中的魔术方法有:

常用的魔术方法:
__init__:
1.用来构造初始化函数,用来给对象进行初始化属性,所以不需要返回值
2.创建对象的时候自动调用
3.自定义类如果不定义的话,默认调用父类object的,同理继承也是,子类若无,调用父类的,若有,调用自己的

class Animal:
    def __init__(self):
        print("init初始化方法,没有调用父类object")

Animal()
#结果:
init初始化方法,没有调用父类object

__new__:
1.用所给类创建一个对象,并且返回这个对象
2.因为是给类创建实例,所以至少传一个参数cls,参数cls代表代表要实例化的类,此参数在实例化时用python解释器自动提供
3.在类实例化时内部创建类实例的函数,并且返回这个实例,所以它是实例时最先被调用的方法,一般不要人为定义该方法
4.因为要创建实例返回实例,所以要有返回值。return父类__new__出来的实例,或者直接是object的__new__出来的实例

__class__

__delattr__
__dict__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__gt__
__hash__
__init__
__init_subclass__
__le__
__lt__
__module__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
__weakref__

创建类的成员并访问

类的成员主要有属性和方法,属性可以随意,方法的第一个参数必须是self

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

    def sing(self):
        print(f"{self.name}唱的真好听")
    def dump(self):
        print(f"{self.name}跳的真不错")
    def rap(self):
        print(f"{self.name}世界第一")
        
shuaishuai = Person("帅帅",18,"superman") #初始化
#访问成员方法
shuaishuai.sing()
shuaishuai.dump()
shuaishuai.rap()

访问类中的属性和方法只需要对象使用成员运算符来进行调用

类属性通过类来访问,对象属性通过对象来访问

访问限制

如果想把某些属性给隐藏起来,不想让外界直接访问,可以在属性名前面加两个下划线。比如帅帅不想直接让别人知道他的年龄或者修改他的年龄:

可以使用单下划线,双下划线,首尾双下划线来限制访问权限

单下划线开头的是protected类型的成员,只允许类本身和子类进行访问,不能使用from xxx import ccc进行导入

双下划线只能由定义了该属性或方法的类调用,而不能由类的对象调用,类的对象如果想调用,必须使用set/get方法

首尾双下划线是系统定义的特殊方法,一般是系统定义的名字

class Person:
    def __init__(self, name,sex):
        self.name = name
        self.__age = None
        self.sex = sex

    def set(self,age):
        self.__age = age

    def get(self):
        return self.__age

    def sing(self):
        print(f"{self.name}唱的真好听")

    def dump(self):
        print(f"{self.name}跳的真不错")

    def rap(self):
        print(f"{self.name}世界第一")


shuaishuai = Person("帅帅","superman")  # 初始化
#这个时候初始化帅帅的年龄就会报错,可以使用set方法来赋值,get方法取值
shuaishuai.set(18)
print(shuaishuai.get())

属性

创建用于计算的属性

有的时候我们需要创建对象的时候就进行给属性赋值,但是这样的方法往往是不安全的,会直接修改我们类中的属性,这样的话安全性不高。比如你的年龄是18岁,但是这个时候有用户进行恶作剧,直接将其修改为600岁,你是不是乐坏了?但是你感觉现实吗?

python中使用@property将一个方法转换为属性,从而实现用于计算的属性,将方法转换为属性后,可以直接通过方法名来访问,而不需要加括号。

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    @property
    def func(self):
        if self.age < 150 and self.age > 0:
            print(self.age)
        else:
            print("想啥呢?")

shuaishuai = Person("帅帅",18)
shuaishuai.func  #func这个属性不能对其赋值,因为他本质也是一个函数

@property可以将属性设置为只读属性

为属性添加安全保护机制

为此,python中有了私有属性和私有方法供大家使用。私有属性就是在属性前面加两个下划线,然后给这个私有属性给予set和get方法,这样对象调用的时候就只能通过set方法来进行赋值,get方法来获取值

class Person:
    def __init__(self, name,sex):
        self.name = name
        self.__age = None
        self.sex = sex

    def set(self,age):
        self.__age = age

    def get(self):
        return self.__age

类与类之间的关系

一个一个的类就跟我们现实世界中的模板一样,只有把他们全部关联起来,才会使我们做事更加高效。比如轮胎跟发动机组合成了车,零食跟零食袋组合成了零食一样。类和类的组合,一般分为以下三种关系:

依赖(关联)

一个动作执行的时候,需要另一个动作来帮忙。比如打开电脑,帅帅去玩电脑或者帅帅去打王者

依赖就是我用你,你需要我。

比如帅帅玩电脑,电脑需要帅帅玩

#依赖
class Person:
    def run(self):
        print("我没事干")

class Computer:
    def play(self,tool):
        tool.run()
        print("我是电脑,玩我")


class Phone:
    def play(self,tool):
        tool.run()
        print("我有王者荣耀,来玩啊")

shuaishuai = Person()
dnf = Computer()
dnf.play(shuaishuai)   #依赖是给一个类的对象的方法给另一个对象

wangzhe = Phone()
wangzhe.play(shuaishuai)

一个类使用某个类中的某个方法

这种情况下,类与类之间的关系是最轻的,可以随时替换。

依赖就是一个类的对象的方法自己完不成一些功能,需要另一个对象来完成,把他加载到此方法中。就是依赖

组合(聚合)

一个类需要某个类的具体对象去做一些事情,这就是组合。轮胎和发动机组合成了车一样。

组合可以一对一,也可以一对多

一个类与另一个类没有共同点,但是他们之间有关系,这就是组合

组合就是我需要什么,但是我没有这些东西,我就拿你。

class Car:
    def __init__(self,name,power = None):
        self.__name = name
        self.__power = power

    def set_power(self,power):
        self.__power = power

    def zhuang_Tread(self):
        print(f"{self.__name}装好了{self.__power.get_name()}的轮胎")

    def saiche_power(self):
        print(f"{self.__name}装好了{self.__power.get_name()}的发动机")

class Tread:
    def __init__(self,name):
        self.__name = name

    def set_name(self,name):
        self.__name = name

    def get_name(self):
        return self.__name

class Engine:
    def __init__(self,name):
        self.__name = name

    def set_name(self,name):
        self.__name = name

    def get_name(self):
        return self.__name


tread = Tread("牛逼牌")
engine = Engine("赛车")
car = Car("奔驰",tread)
car.zhuang_Tread()
car.set_power(engine)
car.saiche_power()

#分析:
是不是发现了有很多的重复代码,而且极其麻烦,不如用继承好。刚开始用,还是没有那么的得心应手,我还的努力。

将一个类的对象封装到另一个类的属性中进行一些操作

淇淇的进阶课堂

老师和学生模型(老师对学生是一对多,学生对老师是一对一)
创建教师类和学生类
教师类有姓名和学生列表两个属性
教师类有添加学生的方法(添加的学生是具体对象)
教师类有显示对应学生姓名和学号的方法
学生类有学号/姓名/年龄属性
创建多个学生,并添加到某位教师的学生列表中
打印该教师的所有学生学号

继承(实现)

一般我们看到继承这两个字都有一种一夜暴富的心态。比如我们看的西虹市首富,猛虫过江,都是有了巨额的财富,令继承人过上了幸福的生活。那么在我们的Python中,继承究竟是怎么样的呢?在python中子类会拥有父类的属性和方法,但是不会继承私有属性和私有方法。被继承的类叫父类或基类,继承的类叫做子类或派生类。

继承的基本语法
#固定结构:
#父类
class Person(object): #括号里面写要继承的类
    def __init__(self, name,sex):
        self.name = name
        self.__age = None
        self.sex = sex

    def set(self,age):
        self.__age = age

    def get(self):
        return self.__age

    def sing(self):
        print(f"{self.name}唱的真好听")

    def dump(self):
        print(f"{self.name}跳的真不错")

    def rap(self):
        print(f"{self.name}世界第一")

#子类
class Boy(Person):
    pass

shuaishuai = Boy("帅帅","superman")
shuaishuai.sing()

#结论:
可以看出,子类Boy什么都没有写,只是继承了一下父类Person,他就拥有了父类的属性和方法,但是私有属性没有被继承。
淇淇的进阶课堂
老师和学生模型(老师对学生是一对多,学生对老师是一对一)
创建教师类和学生类
教师类有姓名和学生列表两个属性
教师类有添加学生的方法(添加的学生是具体对象)
教师类有显示对应学生姓名和学号的方法
学生类有学号/姓名/年龄属性
创建多个学生,并添加到某位教师的学生列表中
打印该教师的所有学生学号

组合是指在新类里面创建原有类的对象,重复利用已有类的功能“has-a”关系

而继承允许设计人员根据其它类的实现来定义一个类的实现“is-a”关系

方法重写

方法重写就是子类继承父类的方法,然后把方法体进行重写。

比如父类是房子的图纸,因为每个人对美的定义不一样,你感觉你爸弄得这个图纸不适合你,你又不想重新弄。就把比如卧室进行重新设计一下,床啥的就不换了。这就是方法重写

class Person(object):
    def __init__(self, name):
        self.name = name
    def sing(self):
        print(f"{self.name}唱的真好听")
   
#子类
class Boy(Person):
    def sing(self):
        print(f"{self.name}最帅")

shuaishuai = Boy("帅帅")
shuaishuai.sing()    
#结果:帅帅最帅
这个时候我们可以发现,原来我们不重写sing方法的时候,他调用的是父类的sing方法,当我们在子类中进行重写之后,他显示的就是子类的方法。
由此可以得出查找顺序:对象调用某个属性或者方法的时候,会先在当前类中进行查找,如果找不到,就去他的父类对象今进行查找。

深度优先和广度优先

派生类中调用基类的方法

子类如果有自己的初始化属性,也就是有魔术方法__init__的时候,子类不会自动执行父类的方法,必须使用super来进行调用,获取父类的初始化属性

使用super关键字调用父类的方法

子类如果编写了自己的构造方法,但没有显式调用父类的构造方法,而父类构造函数初始化了一些属性,就会出现问题

解决方式:调用超类构造方法,或者使用super函数 super(当前类名, self).init()

super在Python3和python2中的区别:

Python3可以使用直接使用 super().xxx 代替 super(Class, self).xxx

super钻石继承

super的内核 mro方法:返回的是一个类的方法解析顺序表(顺序结构)
我们定义的每一个类,Python 会计算出一个方法解析顺序(Method Resolution Order, MRO)列表,这也是super在父类中查找成员的顺序,它是通过一个C3线性化算法来实现的

我们可以使用下面的方式获得某个类的 MRO 列表
类名.mro() 或者 对象名.__class__.mro()
淇淇的进阶课堂
练习:
有一个动物类,猫狗两个类都继承了动物,动物类拥有动物的属性,定制猫狗类的个性属性

动物都有吃的方法,动物在吃东西之后会打印自己的血量值和智力值,但是猫和狗吃的东西不一样

猫狗在吃了食物之后,猫会增加血量值,狗会增加智力

多继承和多重继承的区别

多继承是子类不断的继承父类,依次类推,最后的孙子类拥有父类跟子类的方法

#多继承
class Person(object):
    def run(self):
        print("人会跑")

class Boy(Person):
    def like_girl(self):
        print("男孩喜欢女孩子")

class Girl(Boy):
    def like_boy(self):
        print("女孩子也喜欢男孩子")

class Kid(Girl):
    def kids(self):
        print("孩子是男孩跟女孩的爱情结晶")

kid = Kid()
kid.run()
kid.like_girl()
kid.like_boy()
kid.kids()

#结果
人会跑
男孩喜欢女孩子
女孩子也喜欢男孩子
孩子是男孩跟女孩的爱情结晶

可以看出,多继承就是最后的一个类拥有上面的类的属性和方法(除了私有属性和私有方法)

多重继承逻辑不行的人就别看了。

补充内容isinstance()及issubclass()

isinstance()issubclass() 
isinstance() 用于检查实例类型:isinstance(对象,类型)
issubclass() 用于检查类继承:issubclass(子类,父类)

super

使用多继承,会涉及到查找顺序(MRO)、钻石继承等问题
钻石继承
单继承时 类名.init()的方式和super().init()方式没啥区别

但是使用 类名.init()的方式在钻石继承时会出现问题

super的内核 mro方法:返回的是一个类的方法解析顺序表(顺序结构)
我们定义的每一个类,Python 会计算出一个方法解析顺序(Method Resolution Order, MRO)列表,这也是super在父类中查找成员的顺序,它是通过一个C3线性化算法来实现的

我们可以使用下面的方式获得某个类的 MRO 列表
类名.mro() 或者 对象名.class.mro()

封装性

栈:先进后出

栈在内存中保存对象的引用

堆在内存中保存对象

总结:对象在栈中保存,在堆中被引用

队列:先进先出

淇淇的进阶课堂

实现栈和队列

封装的定义

封装就是把一个个的属性和方法隐藏起来,只留下具体的接口供下一个人使用

我们最常见的封装就是遥控器,你不需要知道遥控器是怎么装的,直接拿来开始换台就行。比如动物世界。

私有属性

私有属性就是在属性前面加get/set方法,然后再当前类中进行调用和实现,外界只能使用这个属性的set/get方法来操作他。而且不能被子类继承

私有方法

私有方法跟私有属性一样,在方法前面加两个下划线

破解私有属性和私有方法
_类名__属性

破解私有属性

破解私有属性

推荐阅读