一、类(class)与实例
首先了解一下封装的概念:在函数的学习中我们已经接触了封装,把常用的代码块打包成一个函数,这是一种封装,是语句层面的封装,我们定义的列表、字典等数据,也属于一种封装,即把分散的数据打包到列表或字典中,这是数据层面的封装;
在Python中一切皆对象,所有数据类型都可视为对象,面向对象编程,也是一种封装的思想,不过要比以上两种封装更先进,它可以更好地模拟真实世界里的事物(将其视为对象),并把描述特征的数据和代码块(函数)封装到一起。
int # 为int对象
float # 为float对象
list # 为list对象
dict # 为dict对象
a = 1 # a为一个int对象
类是自定义对象,抽象的模板,创建实例的模板,比如:人类,动物类,汽车类,飞机类,当然还可以细分为对应子类如动物下面的猫类,狗类
定义一个类
class Cat:
def __init__(self,name,weight,color,ele_color):
self.name = name
self.weight = weight
self.color = color
self.ele_color= ele_color
def cat_run(self):
print(f"{self.name}正在奔跑")
def cat_eat(self):
print(f"{self.name}正在吃小鱼干")
def cat_sleep(self):
print(f"{self.name}正在睡觉")
def cat_clear_hair(self):
print(f"{self.name}正在清理它{self.color}的毛发")
上述代码中就通过class关键字定义了一个Cat类,该类中包含属性:name, weight, color, ele_color;包含的方法(对象行为):cat_run( ), cat_eat( ), cat_sleep( ),cat_clear_hair( )。该Cat类就像一个模板,通过调用这个模板可以源源不断创造出不同的实例来
面向对象中,常用术语包括:
类的定义
定义一个类使用 class 关键字实现,其基本语法格式如下
class 类名: # 和变量名一样,类名本质上就是一个标识符,起类名时同样见名知意
属性(数量>=0)
方法(数量>=0)
类名注意:多个单词构成类名,建议每个单词的首字母大写,其它字母小写
Class LoginPage:
pass
Class MainPage:
pass
类名后面要加 “ :”,即告诉 Python 解释器,下面要开始设计类的内部功能了,也就是编写类属性和方法(类属性指的就是包含在类中的变量;而类中的方法指的是包含在类中的函数);
注意:一个类中的属性与方法要保持统一的缩进(统一缩进4空格)
尝试创建一个Dog类
class Dog:
#类属性:简单地理解,定义在各个类方法之外(包含在类中)的变量为类变量(或者类属性)
eles_num = 2 # 类属性,眼睛数量
legs_num = 4 # 类属性,腿数量
def __init__(self, name, age, hair_color, ele_color): # 构造方法,用于类实例化时传入实例参数
self.name = name # 实例属性
self.age = age
self.hair_color = hair_color
self.ele_color = ele_color
def dog_run(self): # 实例方法
print(f"小狗{self.name},正挥动它{self.legs_num}条小腿快速奔跑;")
def dog_blink_eles(self): # 实例方法
print(f"小狗{self.name},正眨动它{self.eles_num}只{self.ele_color}的眼睛;")
上述 Dog 类中,eles_num ,legs_num是 Dog类的类属性(即所有Dog所共有的,相同的特征) 同时在创建类时,我们可以手动添加一个__ init __() 方法,该方法是一个特殊的类实例方法,称为构造方法(注意:注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格),另外,__init __() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数,即类的构造方法最少也要有一个 self 参数。
每当创建一个类的实例对象时,Python解释器都会自动调用构造方法;
类的实例化(调用类)
对定义好的类进行实例化,语法如下:
[变量名] = 类名(参数) # 赋值变量为非必须
注:实例化时,需要输入的实参,由__ init __(self, 参数1,参数2)构造方法中的参数决定,在实例化时,self参数忽略,不需要实参(self可理解为一个固定的标签,不需要我们关注)
对上述Cat类进行实例化
cat_niki = Cat('niki', 3, '白色' , '蓝色') #通过调用Cat类,传入对应参数name, age, color, ele_color的实参
#并赋值给变量cat_niki,实现实例化,即创建一个cat_niki实例对象
实例化时参数传递关系如下(注意:self 参数是特殊参数,不需要手动传值,Python 会自动传给它值)
定义的类只有进行实例化,也就是使用该类创建对象之后,才能使用,跟我们前面学的函数是一个道理
实例对象的使用(即类实例化后的使用)
访问属性或方法语法如下
实例对象.属性名
实例对象.方法名(参数)
例:访问Cat的color属性
cat_niki = Cat('niki', 3, '白色' , '蓝色') #调用Cat类传入实参进行实例化
niki_color = cat_niki.color # 通过 实例对象.属性名 访问获取这个cat_niki的color属性
print(niki_color) # 输出结果为白色
例:访问Cat的 cat_clear_hair()方法
cat_niki = Cat('niki', 3, '白色' , '蓝色') #调用Cat类传入实参进行实例化
cat_niki.cat_clear_hair() # 通过 实例对象.方法名(参数) 访问cat_clear_hair这个实例方法
# 结果:niki正在清理它白色的毛发
给实例对象动态添加属性
cat_niki = Cat('niki', 3, '白色' , '蓝色') #调用Cat类传入实参进行实例化
cat_niki.body_length = 0.3 # 给实例cat_niki添加实例属性body_length为0.3米
print(cat_niki.body_length) # 输出 0.3
修改实例对象的属性值
cat_niki = Cat('niki', 3, '白色' , '蓝色')
cat_niki.weight = 2 # 对属性weight重新赋值2
print(cat_niki.weight) # 输出 2
self参数的作用
在我们定义类的过程中,无论构造方法,还是实例方法,都以self为第一个参数,实际上Python并未规定类中的构造方法或实例方法第一个参数必须叫self,我们约定第一个参数self是一种俗成习惯,把self这个参数改成this或别的参数程序也不影响运行,遵守这个 约定,可以代码具有更高的可读性(即大家都认识这个self,不认识你这个自定义的this);
self的作用即:指向实例本身
例如上面的Dog类,我们可通过Dog类创建无数个不同的实例,例如
class Dog:
eles_num = 2 # 类属性,眼睛数量
legs_num = 4 # 类属性,腿数量
def __init__(self, name, age, hair_color, ele_color):
self.name = name
self.age = age
self.hair_color = hair_color
self.ele_color = ele_color
def dog_run(self):
print(f"小狗{self.name},正挥动它{self.legs_num}条小腿快速奔跑;")
def dog_blink_eles(self):
print(f"小狗{self.name},正眨动它{self.eles_num}只{self.ele_color}的眼睛;")
Jamis = Dog('Jamis', 3, '黄色', '黑色') #创建一个名叫Jamies小狗的实例
Tom = Dog('Tom', 5, '黑色', '褐色') #创建一个名叫Tom小狗的实例
上述代码,创建两个不同的小狗对象,它们都有各自的name, age, hair_color属性,以及各自的dog_run(), dog_blink_eles()方法,那把Jamies的name属性以及dog_run方法与Tom的区分开来,就需要依靠self这个参数,当实例化Dog创建一个对象时,Python 会自动绑定类方法的第一个参数self指向调用该方法的对象(实例对象),实例化时会开辟一块新的内存空间,每个实例都是开辟一个新的空间来存放该对象,每个实例的self都会指向对应的内存地址,如下面代码
class Dog:
eles_num = 2 # 类属性,眼睛数量
legs_num = 4 # 类属性,腿数量
def __init__(self, name, age, hair_color, ele_color):
self.name = name
self.age = age
self.hair_color = hair_color
self.ele_color = ele_color
def dog_run(self):
print(f"小狗{self.name},self是{self},正挥动它{self.legs_num}条小腿快速奔跑;")
def dog_blink_eles(self):
print(f"小狗{self.name},self是{self},正眨动它{self.eles_num}只{self.ele_color}的眼睛;")
Jamis = Dog('Jamis', 3, '黄色', '黑色')
Tom = Dog('Tom', 5, '黑色', '褐色')
Jamis.dog_run() # self -> Jamis
Tom.dog_run() # self -> Tom
运行结果:
小狗Jamis,self是<__main__.Dog object at 0x00000151FF051F98>,正挥动它4条小腿快速奔跑;
小狗Tom,self是<__main__.Dog object at 0x00000151FF051FD0>,正挥动它4条小腿快速奔跑;
上面代码中Jamis对象的self指向的是Jamis对象,Tom对象的self指向的是Tom对象
(即:不同对象调用同一属性或方法,调用的是对象自己的属性和方法)
类属性与实例属性
类属性:类体中、所有方法之外:此范围定义的变量,称为类属性或类变量,例下面代码的eles_num与legs_num属性
class Dog:
eles_num = 2 # 类属性,眼睛数量
legs_num = 4 # 类属性,腿数量
def __init__(self, name, age, hair_color, ele_color):
self.name = name
self.age = age
self.hair_color = hair_color
self.ele_color = ele_color
def dog_run(self):
print(f"小狗{self.name},self是{self},正挥动它{self.legs_num}条小腿快速奔跑;")
def dog_blink_eles(self):
print(f"小狗{self.name},self是{self},正眨动它{self.eles_num}只{self.ele_color}的眼睛;")
Jamis = Dog('Jamis', 3, '黄色', '黑色')
Tom = Dog('Tom', 5, '黑色', '褐色')
类属性的特点是,所有类的实例化对象都同时共享该变量,也就是说,类属性在所有实例化对象中是作为公用资源存在的,即以上述代码为例,eles_num与legs_num是实例对象Jamies与Tom所共有的,改变类中的变量值,会同时影响两个实例对象
类属性的访问
通过类名直接访问
Dog_eles_num = Dog.eles_num # 通过类名Dog直接访问,类名.属性名
print(Dog_eles_num)
通过实例对象访问
Jamis = Dog('Jamis', 3, '黄色', '黑色')
Dog_eles_num = Jamies.eles_num # 通过实例对象名直接访问,实例对象.属性名
print(Dog_eles_num)
注意:通过”类名.属性名”的方式修改类属性,会影响所有对应的实例,反之”实例对象.属性名”方式修改类属性,只影响该实例对象,并不影响其他相同类创建的实例对象
实例属性:指的是在类的方法内部,以“self.变量名”的方式定义的变量,其特点是只作用于实例对象。另外,实例变量只能通过实例对象名访问,无法通过类名访问。
class Dog:
eles_num = 2 # 类属性,眼睛数量
legs_num = 4 # 类属性,腿数量
def __init__(self, name, age, hair_color, ele_color):
self.name = name # 实例属性
self.age = age
self.hair_color = hair_color
self.ele_color = ele_color
def dog_run(self):
print(f"小狗{self.name},self是{self},正挥动它{self.legs_num}条小腿快速奔跑;")
def dog_blink_eles(self):
print(f"小狗{self.name},self是{self},正眨动它{self.eles_num}只{self.ele_color}的眼睛;")
Jamis = Dog('Jamis', 3, '黄色', '黑色')
Tom = Dog('Tom', 5, '黑色', '褐色')
上述代码中self.name, self.age, self.hair_color, self.ele_color都是实例属性
实例属性的访问(只能通过实例对象访问)
Jamis = Dog('Jamis', 3, '黄色', '黑色')
Tom = Dog('Tom', 5, '黑色', '褐色')
Jamis_age = Jamis.age # 调用Dog类实例化后,通过 实例对象.属性名 访问
Tom_color = Tom.hair_color
任务:创建一个Student类,包含name, age, country, gender, height实例属性,以及run( ), sleep( ), eat( ), study( )实例方法,并进行实例化后调用里面的属性,和方法;
任务:基于上一任务的实例对象,尝试修改这个student实例的age,并输出;
类中的方法
和类中的属性一样,类中的方法也可以进行更细致的划分,具体可分为类方法、实例方法和静态方法
类最基本的作用是通过实例化创建一个实例对象,通过实例对象才能访问实例方法,但是有的时候我们想不需要实例化直接访问类中的方法,而一个类中,某个方法前面加上了staticmethod或者classmethod的话,那么这个方法就可以不通过实例化直接调用,可以通过类名进行调用
类方法@classmethod
定义类方法基本语法如下
@classmethod
def 方法名(cls, [参数]): # 类方法第一个参数必须是cls,这个跟self是一个作用,参数是自定义的形参
方法内部代码块
class Car:
brand_name = "大众" # brand_name 就是类属性,类对象可直接调用
def __init__(self, model, color):
self.color = color
def get_color(self):
print(f"颜色是:{self.color}")
def get_name(self):
print(f"车的品牌名是:{Car.brand_name}")
@classmethod
def make(cls):
print(f"{cls.brand_name}生产汽车")
if __name__ == '__main__':
Car.make()
cls通常用作类方法的第一参数 类似实例方法中的self,cls传递当前类对象。
self 和cls 没有特别的含义,作用只是把实例对象或类对象绑定到方法上
类方法的调用
1、通过类名直接调用
Car.make()
2、通过实例对象调用
vw_car = Car('甲壳虫', '蓝色')
vw_car.make()
静态方法@staticmethod
静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在类这个空间,而函数则定义在类外层的程序所在的空间中。
定义静态方法基本语法如下
@staticmethod
def 方法名([参数]): # 静态方法不需要self, cls作为第一个参数
方法内部代码块
class Car:
brand_name = "大众" # brand_name 就是类属性,类对象可直接调用
def __init__(self, model, color):
self.color = color
def get_color(self):
print(f"颜色是:{self.color}")
def get_name(self):
print(f"车的品牌名是:{Car.brand_name}")
@classmethod
def make(cls):
print(f"{cls.brand_name}生产汽车",cls)
@staticmethod
def service():
print("售前售后服务")
静态方法的调用与类方法调用方法基本一致
Car.service() # 通过类名调用
vw_car = Car('甲壳虫', '蓝色')
vw_car.service() # 通过实例对象调用
在实际编程中,几乎很少会用到类方法和静态方法,因为我们完全可以使用实例方法代替它们实现想要的功能,但某些功能不需要实例化,使用类方法和静态方法也是很不错的选择
私有变量
实例化一个类以后得到一个实例对象,可以通过“实例对象名.属性名”来访问这个实例的属性,同样可以通过“实例对象名.属性名 = xxx”来对这个属性进行修改,即外部代码可以通过这种方式修改实例属性。为了让程序健壮以及安全考虑,需要让内部属性不被外部所访问,因此可以在属性名称前加两个“”下划线,在定义类时,以“”开头的变量名称为私有变量,只能在类内部访问,外部无法访问
class Car:
brand_name = "大众" # brand_name 就是类属性,类对象可直接调用
def __init__(self, model, color):
self.__color = color # 通过"__"来定义私有变量
def get_color(self):
print(f"颜色是:{self.__color}")
def get_name(self):
print(f"车的品牌名是:{Car.brand_name}")
@classmethod
def make(cls):
print(f"{cls.brand_name}生产汽车",cls)
@staticmethod
def service():
print("售前售后服务")
Car.service()
vw_car = Car('甲壳虫', '蓝色')
print(vw_car.__color)
上述代码,由于__color是私有变量,所以通过 vw_car.__color 外部访问,会提示没有这个对象,而通过 get_color 方法可访问 __color 完成输出;
注意,Python 类中还有以双下划线开头和结尾的类方法(例如类的构造函数__init__(self)),这些都是 Python 内部定义的,用于 Python 内部调用。我们自己定义类属性或者类方法时,不要使用这种格式。
任务:定义一个cat类,包含私有变量__name, __color,定义两个方法,get_color用于获取__color并通过return返回,set_name用于设置__name;
二、继承
继承机制经常用于创建和现有类功能类似的新类,又或是新类只需要在现有类基础上添加一些成员(属性和方法),但又不想直接将现有类代码复制给新类。也就是说,通过使用继承这种机制,可以轻松实现类的重复使用
继承语法如下
class 类名(父类1, 父类2, ...):
#类定义部分
定义一个类时,可以通过继承一个现有的类,来拥有这个类的所有功能,而不需要重新额外定义,被继承的类称为父类
class Car:
brand_name = "大众" # brand_name 就是类属性,类对象可直接调用
def __init__(self, model, color):
self.model = model
self.color = color # 通过"__"来定义私有变量
def get_color(self):
print(f"颜色是:{self.color}")
def get_name(self):
print(f"车的品牌名是:{Car.brand_name}")
@classmethod
def make(cls):
print(f"{cls.brand_name}生产汽车",cls)
@staticmethod
def service():
print("售前售后服务")
class Limousine(Car): # 定义一个轿车类,继承Car父类
pass
class Truck(Car): # 定义一个卡车类,继承Car父类
pass
limousine_car = Limousine('轿车', '蓝色')
limousine_car.get_color() # limousine_car调用的是Car类中的get_color实例方法
truck_car = Truck('卡车', '白色')
truck_car.get_name() # truck_car调用的是Car类中的get_name实例方法
对于 limousine 来说,Car 就是父类,基类或超类,而 limousine 就是 Car 的子类
作为子类,可以增加自定义方法
class Limousine(Car):
def auto_driver(self):
print("开始进行自动辅助驾驶")
limousine_car = Limousine('轿车', '蓝色')
limousine_car.auto_driver() # 开始进行自动辅助驾驶
上面代码中增加的 auto_driver 方法只存在于 limousine 中,对父类以及继承该父类的其他子类无影响 ;
对于子类继承的父类的方法,子类可以对继承的方法进行重写修改,这个子类运行调用该方法时,会运行修改后的方法
class Limousine(Car):
def get_color(self): # 此处是子类limousine对父类get_color重写
print(f"型号是:{self.model}")
print(f"颜色是:{self.color}")
class Truck(Car):
def get_color(self): # 此处是子类truck对父类get_color重写
print(f"型号是:{self.model},颜色是:{self.color}")
limousine_car = Limousine('轿车', '蓝色')
limousine_car.get_color() #型号是:轿车
#颜色是:蓝色
truck_car = Truck('卡车', '白色')
truck_car.get_color() #型号是:卡车,颜色是:白色
当子类和父类都存在相同的get_color( )方法时,我们说,子类的get_color( )覆盖了父类的get_color( ),在代码运行的时候,总是会调用子类的get_color( )。
三、继承树
继承可以一层一层的进行继承,父类-子类-孙类,就好比人类的继承关系,爷-父-子-孙继承,但所有继承都可以向上追溯到一个根类 object,Python所有类都默认继承这个 object
注意,如果类没有显式指定继承自哪个类,则默认继承 object 类(object 类是 Python 中所有类的父类,即要么是直接父类,要么是间接父类)
class Car:
pass
class Car(object):
pass
上述代码,继承关系是一样的,默认继承 object 这个根类可不显示的写出来
class Car:
pass
class Limousine(Car):
pass
class Truck(Car):
pass
再看一个继承实例
子类需要覆写构造方法
class Persion:
rest = '休息日'
work_study = '学习或工作日'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def eat(self):
print(self.rest)
print(f"{self.name} 正在吃饭")
def drink(self):
print(f"{self.name}正在喝水")
def walk(self):
print(f"{self.name} 正在走路")
def sleep(self):
print(f"{self.name} 正在睡觉")
@classmethod
def smile(cls):
print("微微一笑")
@staticmethod
def hungry():
print("肚子空空")
class Student(Persion):
def __init__(self,name,age,gender,school,classname,number):
super(Student).__init__(self,name,age,gender) # super(子类名)返回子类的父类
# Persion.__init__(self,name,age,gender)
self.school = school
self.classname = classname
self.number = number
def sport(self):
print(f"{self.name} 正在学校做运动")
def study(self):
print(f"{self.name}{self.age}岁,正在{self.school}{self.classname}上课")
if __name__ == "__main__":
xiaoMing = Student(name='小明',age=12, gender='boy', school='兴华实验学校', classname='一(2)班',number='10')
xiaoMing.study()
xiaoMing.drink()
如果在子类中定义构造方法,则必须在该方法中调用父类的构造方法。
上述Student类继承了Person类,并对Person类的构造方法进行覆写
注:一定要用super(Student).init(self, name,age,gender)去调用父类的构造方法,否则,Student子类将没有name,age,gender三个属性,super(Student)将返回当前子类Student继承的父类,即Person
继承关系树如下
小结:继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。