面向对象
现实生活 --- 抽象 --- 类 --- 实例化 --- 对象:用来表示现实生活中具体的对象
例如:打车软件,当打到一辆车时,订单界面会显示车牌号, 车品牌, 车颜色, 司机等信息, 这些信息组合成了一个"车"对象, 来表示现实生活中的具体的某一辆车。这里的车牌号, 品牌, 颜色, 司机, 这些信息都是从现实生活中的车抽象出来的, 用来表示车如何在虚拟世界中去显示。将这些数据组合起来就成了一个类, 通过这个类去生成一个对象。
总结上面的内容就是:
1. 面向对象主要就两块内容:对象和类
2. 如何去抽象并创建一个类
3. 如何通过类去实例化一个对象
类
抽象
抽象其实就一句话:相同类型的物品, 寻找相同的特性(静态---属性, 动态---方法)
品牌:宝马,颜色:白色,车牌:aaa
品牌:本田,颜色:蓝色,车牌:bbb
从上面两辆车进行对比,虽然车型, 颜色, 车牌等信息都不一样,但是它们都有一些共同的属性,也就是品牌,颜色,车牌;动态的方法就是它们都是交通工具,拥有跑的方法,抽取出动态的方法:run()
类的创建
格式:
class 类名:
类体
类体
先跳到实例属性那部分
构造函数__init__
def __init__(self, 参数):
self.属性名 = 参数
- init是构造函数负责对对象初始化的;而对象初始化是由__new__方法负责的,它会创建一个对象,并将对象的地址传递给init方法,这个对象就是self
- self表示对象本身,参数则是在实例化对象传递进来的
- 一个对象的init方法只执行一次,就是实例化对象时自动执行
- init方法中是定义并赋值一个对象的实例属性的
- __dict__该方法可以查看对象自身的实例属性
实例成员
需要创建对象才能使用的成员
实例属性
拿上面的案例为例:车这个类的实例属性就是"品牌, 颜色, 车牌", 它代表了这个类在计算机中的表示
特点:
- 实例属性是对象私有的,不是属性私有,而是实例属性的值私有;例如车都有属性color, 但是它们的颜色不一定是相同的,第一辆车为白色, 第二辆车为蓝色
- 创建和调用对象的属性都是 "对象.属性", 在赋值情况下对象没有这个属性就是创建,有这个属性就是修改;访问情况下,没有该属性就报错,有该属性就返回属性值
- 对象属性一般在init方法中创建,它是构造函数,负责对对象进行初始化的
实例方法
方法就是函数,只是叫法不同罢了,在类中定义实例方法格式如下:
def 方法名(self, 参数):
方法体
- 基本格式和函数是一致的,就多了个self参数,上面说过self参数表示的是对象本身,即谁调用的方法,self就是谁,并且可以使用self中的实例成员
- 实例方法和实例属性不同,实例方法是对象共享的,即只有一份
- 访问:对象.方法名(参数)
类成员
不需要创建对象,属于类本身的,所有子类共享,类名.类成员
类变量
定义在类中,方法外的变量
class 类:
变量 = 值
类.变量
- 随类的加载而加载,不需要创建对象即可访问,是类中的全局变量
- 只有一份数据,且数据对所有对象共享
类方法
class 类:
@classmethod
def 方法(cls, 参数):
方法体
- 随类的加载而加载,不需要创建对象即可访问,是类中的全局变量
- 只有一份数据,且数据对所有对象共享
- 与实例方法不同,cls表示的是类, self则为对象
- 使用@classmethod修饰
- 一般用于操作类变量
静态方法
静态方法在定义上是属于类的,但是其本身和类没有任何关系,所以静态方法没有默认的cls作为参数
@staticmethod
def 方法名(参数):
方法体
- 调用静态方法时,依然使用"类名.方法名(参数)"进行调用
- 和类方法一样,不需要创建对象即可访问
- 使用@staticmethod进行修饰
创建类
通过上面的实例方法和实例属性的学习,根据汽车为例子创建一个类
class Car:
def __init__(self, brand, color, license_plate):
self.brand = brand
self.color = color
self.license_plate = license_plate
def run(self):
print(f"{self.brand}在跑")
对象
类是对象的模板,现在就来学习如何通过类去创建对象
实例化对象
对象名 = 类(参数)
- 只需要传递除self参数以外的所有参数
- 对象名本质上就是变量名,命名方式和变量一致
实例化对象并使用
class Car:
def __init__(self, brand, color, license_plate):
self.brand = brand
self.color = color
self.license_plate = license_plate
def run(self):
print(f"{self.brand}在跑")
# 实例化对象
car = Car("BMW", "白色", "京A·88888")
# 访问实例变量
print(f"{car.brand}, {car.color}, {car.license_plate}")
# 对实例变量进行赋值
car.color = "黑色"
# 创建新的实例变量
car.price = 250000
# 通过dict来获取对象的实力属性
print(car.__dict__) # {'brand': 'BMW', 'color': '黑色', 'license_plate': '京A·88888', 'price': 250000}
# 访问实例方法
car.run()
跨类调用
其实就是在一个类中调用另外一个类
方式一:
在方法中创建类,并使用它
特点:每次调用方法,都会创建一个新类
class Car:
def run(self):
print("开车")
class Person:
def __init__(self, name):
self.name = name
def go_to(self, position):
car = Car()
print(f"{self.name}去{position}")
car.run()
zhangsan = Person("张三")
zhangsan.go_to("重庆")
方式二:
在init构造方法中创建,在实例方法中去使用
特点:对象的属性,是对象的一部分
class Car:
def run(self):
print("开车")
class Person:
def __init__(self, name):
self.name = name
self.car = Car()
def go_to(self, position):
print(f"{self.name}去{position}")
self.car.run()
zhangsan = Person("张三")
zhangsan.go_to("重庆")
方式三:
由外部传入,实例方法接收,调用
特点:灵活
class Car:
def run(self):
print("开车")
class Person:
def __init__(self, name):
self.name = name
def go_to(self, position, vehicle):
print(f"{self.name}去{position}")
vehicle.run()
zhangsan = Person("张三")
zhangsan.go_to("重庆", Car())
三大特征
封装
从数据角度
将一些基本数据类型合并成一个自定义类型,前面类的创建其实就是对数据的封装。封装能够将数据与对数据的操作相关联,并且能够提高代码的可读性
从行为角度
向类外提供必要的功能,隐藏功能实现的细节
私有成员
使用__开头的成员, 该成员无法从外部直接访问,但是可以通过"对象.下划线类名成员名"进行访问,本质上就是将私有成员的名称修改为:_类名__成员名,可以通过__dict__属性查看
class MyClass:
def __init__(self, data):
# 私有成员
self.__data = data
def __run(self):
print("私有方法")
obj = MyClass(123)
# print(obj.__data) # 'MyClass' object has no attribute '__data'
# obj.__run) # 'MyClass' object has no attribute '__run'
# 外部访问私有变量
print(obj._MyClass__data) # 123
# 外部执行私有方法
obj._MyClass__run()
print(obj.__dict__) # {'_MyClass__data': 123}
属性@property
属性是用于保护实例数据的,当对数据进行赋值前,先做一个判断,通过判断再进行赋值
class MyClass:
def __init__(self, data, age):
# 私有成员
self.__data = data
self.age = age
# 当访问age时,调用这个属性
@property
def age(self):
return self.__age
# 当给age赋值时,调用这个属性
@age.setter
def age(self, value):
if value > 100:
self.__age = 100
elif value < 20:
self.__age = 20
else:
self.__age = value
obj = MyClass(123, 85)
print(obj.age) # 85
obj.age = 150
print(obj.age) # 100
obj.age = -25
print(obj.age) # 20
属性的三种形式
- 读写
上面的代码就是读写模式 - 只读
class MyClass:
def __init__(self, data, age):
# 私有成员
self.__data = data
self.age = age
# 当访问age时,调用这个属性
@property
def age(self):
return self.__age
obj = MyClass(123, 85)
print(obj.age) # 85
obj.age = 150 # AttributeError: property 'age' of 'MyClass' object has no setter
- 只写
class MyClass:
def __init__(self, data, age):
# 私有成员
self.__data = data
self.age = age
def age(self, value):
if value > 100:
self.__age = 100
elif value < 20:
self.__age = 20
else:
self.__age = value
"""
当没有写这段代码时:NameError: name 'age' is not defined
因为写操作是通过@属性名.setter
所以要跳过读操作,就必须使用关键字参数 fset
"""
age = property(fset=age)
def __run(self):
print("私有方法")
obj = MyClass(123, 85)
# print(obj.age) # AttributeError: property 'age' of 'MyClass' object has no getter
obj.age = 150
继承
子类继承父类的数据,实现代码的复用,实现多态的思想
class 父类:
def 父类方法(self):
方法体
class 子类(父类):
def 子类方法(self):
方法体
儿子 = 子类()
儿子.子类方法()
儿子.父类方法()
# 父类
class Person:
def say(self):
print("说话")
class Student(Person):
def study(self):
print("学习")
class Teacher(Person):
def teach(self):
print("教学")
s = Student()
s.say()
s.study()
t = Teacher()
t.say()
t.teach()
继承方法
子类默认继承父类的非构造方法的方法,子类可以通过方法名去调用它们。
重写方法
重写方法指的是子类有和父类同名的方法,这就是重写方法。这个时候不管是外部还是内部,优先调用的就是子类重写的方法,而非父类的方法,这时候如果想要调用父类的方法就需要使用super().方法名。比如子类如果拥有自己的构造方法,这时再创建对象时,就会使用自己的方法。而如果要对继承过来的属性进行赋值,就可以显示的调用父类的构造方法。
# 父类
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print(f"{self.name}在说话")
class Student(Person):
def __init__(self, name, age):
# 调用父类的构造方法完成对继承过来的属性进行赋值
super().__init__(name, age)
def study(self):
print("学习")
class Teacher(Person):
def __init__(self, name, age):
super().__init__(name, age)
def teach(self):
print("教学")
s = Student("hhc", 25)
s.say()
s.study()
t = Teacher("lxq", 38)
t.say()
t.teach()
非重写方法
非重写方法,则子类会直接调用父类的属性和方法。例如如果子类没有构造方法,则会隐式调用父类的构造方法,这时如果父类的构造方法有参数,而你在创建子类对象时没有传递参数则会报错
# 父类
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print(f"{self.name}在说话")
class Student(Person):
def study(self):
print("学习")
class Teacher(Person):
def teach(self):
print("教学")
# TypeError: Person.__init__() missing 2 required positional arguments: 'name' and 'age'
# s = Student()
# s.say()
# s.study()
t = Teacher("lxq", 38)
t.say()
t.teach()
Object
objcet类是所有类的父类。如下图,当我创建一个Student类时,并没有写任何属性和方法,但是当我对象.的时候,弹出了很多方法,这就是继承object类得来的。
多继承
多继承就是一个类可以有很多父类,多继承只需要知道访问同名函数的解析顺序
class A:
def func01(self):
print("A")
super().func01()
class B:
def func01(self):
print("B")
class C(A, B):
def func01(self):
print("C")
super().func01()
class D(A, B):
def func01(self):
print("D")
super().func01()
class E(C, D):
def func01(self):
print("E")
super().func01()
e = E()
e.func01() # E C D A B
内置判断对象/类类型的方法
- isinstance(对象, 类型)返回指定对象是否是某个类的对象。
- issubclass(类型,类型)返回指定类型是否属于某个类型。
- type(对象)返回对象的类型
多态
多态指的是同一个方法,不同对象所执行的效果不同,前提是继承。例如+, +底层其实就是调用了内置函数__add__, 如果是两个数相加,则就是数学上的加法;如果是两个字符串相加,就是字符串拼接,这就是对象不同,执行效果不同,这也就实现了多态。
多态的思想能够约束子类,例如一个人去旅行所乘坐的交通工具,可以是坐飞机,开车,坐火车等方式,如果将这些交通工具封装成一个类,并且统一方法,这时候我们在其他类中不管传递什么交通工具都可以调用一样的方法,而不需要去根据传入的交通工具不同去调用不同的方法。今后如果有出现新的交通工具,只需要去继承Van重写run方法,就可以实现,而不需要去修改go_to中的代码。
class Van:
def run(self):
pass
class Car(Van):
# 重写方法
def run(self):
print("开车去")
class Airplane(Van):
def run(self):
print("坐飞机")
class Train(Van):
def run(self):
print("坐火车")
class Person:
def __init__(self, name):
self.name = name
def go_to(self, address, van):
print(f"{self.name}去{address}")
# 多态,不需要管传入的是什么交通工具
van.run()
p = Person("hhc")
p.go_to("北京", Car())
p.go_to("重庆", Train())
p.go_to("天津", Airplane())