面向对象简单理解:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程
类:对象是面向对象编程的核心,在使用对象的过程中,为了将具有共同特征和行为的一组对象抽象定义,提出了另外一个新的概念——类
类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象
如字典就是一类数据结构,字符串是一类数据结构,它们各自都具有相同的特征属性和方法
对象:某个具体事物的存在,在现实世界看得见摸得着,对象归属于一类;创建一个对象需要依赖于类
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。类根据继承可以分为父类和子类
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 实例变量:定义在方法中的变量,只作用于当前实例的类。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,所以Dog也是一个Animal。(派生类相当于子类,基类相当于父类,只是称呼不同)
- 实例化:创建一个类的实例,类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法
1、创建类和对象
1.1、类
经典类和新式类,从字面上可以看出一老一新,新的必然包含了更多的功能,是推荐的写法,从写法上区分:如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类
创建一个类
# 经典类1 class Hhh: pass # 经典类2 class Jor: pass # 新式类 class Fa(object): pass
1.2、创建一个对象
# 自定义一个狗类(自定义类名一般遵循大驼峰命名) class Dog(object): # 定义一个方法(实例方法 或 对象方法) # 实例方法第一个参数为self(python默认的) # 实例方法 下划线 或小驼峰(公司的要求) def move(self): print('狗会吃骨头') # 使用自定义的类创建一个对象 wangcai = Dog() # 调用狗类中的方法(想要使用其类中的方法,要使用这个类创建一个对象 使用对象调用其类里面的方法) wangcai.move()
2、添加对象属性和判别同一个类创建的多个对象是否为同一个对象
2.1、方法外添加对象属性
1 # 定义一个Dog类 2 class Dog(object): 3 def eat(self): 4 print('狗吃骨头') 5 def zuoyong(self): 6 print('狗会看家') 7 8 # 使用类创建一个对象 9 wang = Dog() 10 # 在方法外赋予对象属性 11 wang.name = '旺财' 12 wang.age = 4 13 wang.colour = '白色' 14 print(wang.name)
2.2、判别同一个类创建的多个对象是否为同一对象
python中每创建一个新的对象,需要开辟一个内存空间,占用内存地址,而内存地址是唯一的,以此判断是否为同一个对象,不同对象开辟的内存地址也不同
1 class Dog(object): 2 def eat(self): 3 print('狗吃骨头') 4 # 使用类创建多个对象 5 wang = Dog() 6 wang1 = Dog() 7 wang2 = Dog() 8 # 用16进制查看对象的内存地址 9 print(wang,wang1,wang2) 10 # 用10进制查看对象的内存地址 11 print(id(wang)) 12 print(id(wang1)) 13 print(id(wang2)) 14 15 ================================ 16 17 运行结果:<__main__.Dog object at 0x000002AFC4E21CC8> 18 <__main__.Dog object at 0x000002AFC4E21D48> 19 <__main__.Dog object at 0x000002AFC4E21D88> 20 2953945685192 21 2953945685320 22 2953945685384
运行结果是各不相同,证明同一个类创建的多个对象时唯一的
3、面向对象的三大特征:封装、继承、多态
3.1、封装
顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容;对于面向对象来说,其实就是使用构造方法(__init__)将内容封装到对象中,然后通过对象直接或self间接获取被封装的内容
封装的意义:
- 将属性和方法放到一起做为一个整体,然后通过实例化对象来处理;
- 隐藏内部实现细节,只需要和对象及其属性和方法交互就可以了;
- 对类的属性和方法增加 访问权限控制
3.1.1、通过 对象.属性 直接调用被封装的内容
class Hero(object): # 构造方法 def __init__(self, name, hp, atk): # 设置属性的值 self.name = name self.hp = hp self.atk = atk # 创建一个对象 laosun = Hero('俺老孙',100,10)# 实则就是把'俺老孙',100,10三个数据分别封装到name,hp,atk里 # 通过对象直接调用 print(laosun.name)
3.1.2、通过 对象.方法 间接调用
class Dog(object): # 构造方法 def __init__(self, new_name, new_age, new_colour="白色"): # 给对象的属性赋值 self.name = new_name self.age = new_age self.colour = new_colour def info(self): print("名字:%s" % self.name) print("年龄:%d" % self.age) print("毛色:%s" % self.colour) # 创建一个对象 wang = Dog('黑狗',4,'黑色') # 间接调用 wang.info()
3.2、继承
面向对象的继承和现实中的继承相似,面向对象的继承指的是类的继承,当一个类继承了另外一个类,被继承的类称之为父类,继承的类叫子类
子类会继承父类的所有方法,属性;对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法
在继承关系中,如果一个实例的数据类型是某个子类,那它也可以被看做是父类。但是,反过来就不行。
继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树
3.2.1、单继承
只是继承一个父类
# 创建一个类,继承于object class Dog(object): # 构造方法 def __init__(self, new_name,): # 给对象的属性赋值 self.name = new_name def info(self): print("名字:%s" % self.name)
# 创建一个单继承的子类Heigou,继承类Dog class Heigou(Dog): def eat(self): print('黑狗血很贵听说') # 创建一个对象 wang = Dog('黑狗') hei = Heigou('大黑') # 根据对象调用类的方法 wang.info() hei.eat() # 根据对象调用父类的方法 hei.info() ============================================= 结果是成功调用父类的方法
虽然子类没有定义__init__方法初始化属性,也没有定义实例方法,但是父类有。所以只要创建子类的对象,就默认执行了那个继承过来的__init__方法
3.2.2、多继承
子类继承多个父类(Python的类可以继承多个类,Java和C#中则只能继承一个类)
多继承可以继承多个父类,也继承了所有父类的属性和方法
注意:如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性mro的顺序来查找);多个父类中,不重名的属性和方法,不会有任何影响
# 创建两个类,继承于object class Hero(object): def __init__(self, name, hp, atk): self.name = name self.hp = hp self.atk = atk class Dog(object): def __init__(self, new_name,): self.name = new_name def info(self): print("名字:%s" % self.name) # 创建一个多继承的类 class Heigou(Dog,Hero): def eat(self): print('黑狗血很贵听说') # 创建一个对象 wang = Dog('黑狗') hei = Heigou('大黑') # 调用父类的方法 hei.info() # 利用子类的魔法属性__mro__查看属性和方法的查找顺序 print(Heigou.__mro__)
当多继承中多个父类都定义了相同的函数(包括函数名,函数内容),子类调用函数时调用的是哪个父类的函数?
当类是经典类时,会按照深度优先方式查找(子类a继承父类b、c,c又继承了d,当子类调用相同方法时,先查找a是否有此方法,若无,调用b的,若b也无,查看b是否有继承父类,有则 继续查找b的父类 ,没有则开始查找c,c若无,找c的父类,无则继续找父类的父类)
当类时新式类时,按照广度优先查找(子类a继承父类b、c,c又继承了d,当子类调用相同方法时,先查找a是否有此方法,若无,调用b的,若b也无,先查找a的第二个父类c,若无,继续查找a的父类,此级父类均没有才开始查找父类的父类)
如下图
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,转而执行方法,不会再继续找
3.3、多态(同一个对象,多种形态)
所谓鸭子类型:看着像鸭子,我们就说它是鸭子
3.3.1、变量的多态
多态在python中其实是很不容易说明⽩的. 因为我们⼀直在⽤. 只是没有具体的说.
比如. 我们创建⼀个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = "alex", 这时, a⼜变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同⼀个变量a可以是多种形态
3.3.2、类的多态
在说明多态是什么之前,在 Child 类中重写 print_title() 方法:若为male,print boy;若为female,print girl
class Person(object): def __init__(self,name,sex): self.name = name self.sex = sex def print_title(self): if self.sex == "male": print("man") elif self.sex == "female": print("woman") class Child(Person): # Child 继承 Person def print_title(self): if self.sex == "male": print("boy") elif self.sex == "female": print("girl") May = Child("May","female") Peter = Person("Peter","male") print(May.name,May.sex,Peter.name,Peter.sex) May.print_title() Peter.print_title()
当子类和父类都存在相同的 print_title()方法时,子类的 print_title() 覆盖了父类的 print_title(),在代码运行时,会调用子类的 print_title()
这样,我们就获得了继承的另一个好处:多态。
多态的好处就是,当需要传入更多的子类,例如新增 Teenagers、Grownups 等时,我们只需要继承 Person 类型就可以了,而print_title()方法既可以直接继承,不重写(即使用Person的),也可以重写一个特有的。这就是多态的意思。调用方只管调用,不管细节,而当我们新增一种Person的子类时,只要确保新方法编写正确,而不用管原来的代码。
用自己的话说多态:子类继承了父类,子类对父类的方法进行了重写,用子类进行创建对象,用对象可以调用子类的方法,也可以调用父类的方法,这就是多态