前言
面向对象是一种编程思想,与面向过程相对应。
面向过程是分析解决问题所需的步骤,用函数实现每个步骤,最后从头到尾依次调用。
面向对象是把问题分派给多个人,让每个人完成自己应做的任务,最后选择合适的人和顺序来解决问题。
面向过程的优点是按步骤、逻辑清晰,但在复杂庞大的项目中效率低下。
面向对象增加了程序的可扩展性,但同时也使开发人员不易预知对象之间的交互结果。
面向对象包含继承、封装、多态三个特征。
1. 面向对象的基础——类
类是描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
定义一个类
class People(object):
People是自定义的类名,首字母大写
object是所有类默认继承的父类,可省略__new__(cls)
定义类时,默认加载new方法。
创建一个类对象的地址,并返回给实例对象__init__(self)
是初始化方法/构造函数,用于初始化实例对象的变量。
其中self指的是实例对象,设置self.name可在整个类中使用- 创建实例对象
a = People("张三", 20, "男")
a是自定义的People类的实例对象
class People:
# new方法,创建地址并返回给实例对象
def __new__(cls):
return super().__new__(cls)
# 初始化方法/构造函数,初始化数据属性
def __init__(self, name, age=None, gender=None):
self.name = name
self.age = age
self.gender = gender
def eat(self):
print(f"{self.name}吃饭")
# 创建实例对象
a = People("张三", 20, "男")
b = People("李四", 25, "女")
示例
要求:设计“老张开车去东北”
分析:面向对象是将一个问题分派给多个对象去完成。
-
首先确定对象:人、车、地点,创建三个类People、Car、Place
-
再确定每个对象的属性和任务:
人:姓名——开车
class People:
def __init__(self, name):
self.name = name
def drive(self, car, place):
print(f"{self.name}开车")
车:品牌/速度——行驶
class Car:
def __init__(self, brand, speed):
self.brand = brand
self.speed = speed
def move(self):
print(f"{self.name}行驶ing...")
地点:地名/距离——(无行为)
class Place:
def __init__(self, name, distance=0):
self.name = name
self.distance = distance
- 车在行驶过程中,离目的地距离随时间变化,因此在Car类的move方法中调用Place对象的distance属性
距离非0时,汽车行驶,距离递减,
直到距离为0,到达目的地。
def move(self):
while place.distance > 0:
print(f"{self.brand}行驶中,还有{place.distance}公里...")
place.distance -= self.speed
time.sleep(1)
print(f"到达{place.name}!")
- 人的开车行为会使汽车行驶,因此在People类的drive方法中调用Car对象的move方法
class People:
def __init__(self, name):
self.name = name
def drive(self, car, place):
print(f"~~~{self.name}驾驶{car.brand}去往{place.name}~~~")
car.move()
- 创建实例对象,调用类的方法
# 创建实例对象
zhang = People("老张")
car = Car("五菱宏光S", 50)
place = Place("东北", 500)
# 老张开车去东北
zhang.drive(car, place)
结果:
~~~老张驾驶五菱宏光S去往东北~~~
五菱宏光S行驶中,还有500公里...
五菱宏光S行驶中,还有450公里...
五菱宏光S行驶中,还有400公里...
五菱宏光S行驶中,还有350公里...
五菱宏光S行驶中,还有300公里...
五菱宏光S行驶中,还有250公里...
五菱宏光S行驶中,还有200公里...
五菱宏光S行驶中,还有150公里...
五菱宏光S行驶中,还有100公里...
五菱宏光S行驶中,还有50公里...
到达东北!
- 完整代码
import time
class People:
def __init__(self, name):
self.name = name
def drive(self, car, place):
print(f"~~~{self.name}驾驶{car.brand}去往{place.name}~~~")
car.move()
class Car:
def __init__(self, brand, speed):
self.brand = brand
self.speed = speed
def move(self):
distance = place.distance
while distance > 0:
print(f"{self.brand}行驶中,还有{distance}公里...")
distance -= self.speed
time.sleep(1)
print(f"到达{place.name}!")
class Place:
def __init__(self, name, distance=0):
self.name = name
self.distance = distance
# 创建实例对象
zhang = People("老张")
car = Car("五菱宏光S", 50)
place = Place("东北", 500)
# 老张开车去东北
zhang.drive(car, place)
# 创建实例对象
wang = People("小王")
car = Car("mini", 100)
place = Place("云南", 1210)
# 小王开车去云南
wang.drive(car, place)
2. 封装、继承、多态
2.1 封装
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
https://www.runoob.com/java/java-encapsulation.html
公有变量、受保护的变量、私有变量
-
公有变量:可以随意访问与修改
-
受保护的变量_,建议只在类内访问和修改
类外访问和修改不会出错,但波浪线提示:正在访问一个受保护的变量
python中的受保护变量只是一种自我约定(口头约定),可以访问但是不建议访问 -
私有变量__,只允许类内访问
类外访问时报错:AttributeError: 没有这个属性
类外修改时不会报错,但修改无效
class Person:
def __init__(self, name, age, number):
self.name = name # 公有变量
self._age = age # 受保护的变量_,建议只在类内访问
self.__number = number # 私有变量__,只允许类内访问
# 创建实例对象
p = Person("老王", 28, "001")
# 公有变量:
p.name = "小王"
print(p.name)
# 受保护的变量:
print(p._age) # 波浪线提示:正在访问一个受保护的变量
# # 注意:python中的受保护变量只是一种自我约定(口头约定),可以访问但是不建议访问
# 私有变量:
print(p.__number) # 访问时报错:AttributeError: 没有这个属性
p.__number = "100"
p.info() # 修改不报错,但修改无效
- 若想访问和修改私有变量:设置set和get方法,即为外界提供一个接口
class Person:
def __init__(self, name, age, number):
self.name = name # 公有变量
self._age = age # 受保护的变量_,建议只在类内访问
self.__number = number # 私有变量__,只允许类内访问
# 更改私有变量
def set_number(self, value):
self.__number = value
# 访问私有变量
def get_number(self):
return self.__number
## 若要修改或访问必须在类内部,设置set/get方法
p = Person("老王", 28, "001")
p.set_age(100)
print(p.get_age())
- 快速设置set和get方法:
props+enter
@property
def number(self):
return self.__number
@number.setter
def number(self, value):
if value < 10000:
self.__number = value
【注意!】:
在类外修改私有属性:
不是调用方法传参 p.number(456)
是直接对示例对象的私有变量进行赋值!p.number = 456
p = Person("tom", 20, 123)
print(p)
# 改变tom的number
# p.number(456) # TypeError: 'int' object is not callable
p.number = 456 # 更改私有变量,直接赋值
print(p)
运行结果:
tom 20 123
tom 20 456
2.2 继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
https://www.runoob.com/java/java-inheritance.html
- 单继承
class Person:
pass
class Man(Person):
pass
Man类继承一个Person类
具有Person类的属性和方法
可直接使用
- 多继承
class Man:
pass
class Woman:
pass
class Someone(Man, Woman):
pass
Someone类继承一个Man类,一个Woman类
具有两个类的属性和方法
多继承中的继承优先级问题:
如果Someone继承的两个类中含有同名属性和方法,就会出现优先级的问题。
- 继承的父函数的父函数只有默认的object时,优先使用第一个类(即Man类)的属性/方法
class Man:
pass
class Woman:
pass
class Someone(Man, Woman):
pass
- 继承的父函数都继承同一个类
class Person:
pass
class Man(Person):
pass
class Woman(Person):
pass
class Someone(Man, Woman):
pass
Man类和Woman类都继承Person类,
Someone调用属性/方法的顺序:
self->Man->Woman->Person。
- 继承的父函数继承不同的类
class Person1:
pass
class Person2:
pass
class Man(Person1):
pass
class Woman(Person2):
pass
class Someone(Man, Woman):
pass
Man类继承Person1类
Woman类继承Person2类
Someone调用属性/方法的顺序:
self->Man->Person1->Woman->Person2。
4. 类属性和实例对象属性、类方法和实例对象方法
定义
类方法
@classmethod
def play(cls):
print("类方法")
class Person:
# 类属性:
count = 0
# 实例对象属性
def __init__(self, name):
self.name = name
# 类的方法:
@classmethod
def play(cls):
print("类方法")
# 类的静态方法:不用传参
@staticmethod
def work():
print("静态方法")
# 实例对象的方法
def eat(self):
print(f"实例方法访问类属性{Person.count}")
两者区别在于:
- 实例对象的属性和方法必须先创建一个实例对象,才能调用
print("实例对象的属性和方法必须先创建实例对象")
p = Person("小王")
p.eat()
- 类的属性和方法可以直接调用
print("访问类属性、类方法、静态方法,无须创建实例对象")
print(Person.count)
Person.play()
Person.work()
- 实例对象也可以调用类的属性和方法
print("实例对象可以访问类属性、类方法、静态方法")
p = Person("小王")
print(p.count)
p.play()
p.work()
动态修改
动态语言便于更改数据结构:
写好的代码不建议随意更改,因此需要在使用时动态地添加。
- 定义一个类,如下。
此时Cat类中含有一个类属性name、一个实例对象方法eat。
class Cat:
# 类属性
name = "tom"
def __init__(self) -> None:
super().__init__()
def eat(self):
print("eat fish")
- 添加类属性:新建属性–>直接赋值。
类名.属性=值
Cat.age = 12
- 添加实例属性:创建实例对象–>新建属性–>赋值
c = Cat()
c.age = 12
print(c.age)
- 添加类方法和静态方法:定义类方法/静态方法,赋值给类的新建方法名。
# 在类外定义classmethod和staticmethod,并使用
@classmethod
def play(cls):
print("playing...")
Cat.play = play
Cat.play()
@staticmethod
def sleep():
print("sleeping..")
Cat.sleep = sleep
Cat.sleep()
- 添加实例对象方法:创建实例对象,定义带self参数的函数,用types.MethodType将新建函数传给实例对象
import types
def play(self):
print("playing...")
c = Cat()
c.play=types.MethodType(play,c)
c.play()
- 在类外定义方法时,要注意方法所带的参数:
类方法,cls
静态方法,没有参数
实例对象方法,self