面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向对象编程 VS 面向过程编程:
- 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
- 面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
假设我们要表示学生的成绩(包含姓名+分数),然后处理学生的成绩,举例说明两种程序设计的不同:
面向过程的程序设计
为了表示学生的成绩,面向过程的程序可以用一个dict表示:
std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }
处理学生的成绩可以通过函数实现,比如打印学生的成绩:
def print_score(std):
print('%s: %s' % (std['name'], std['score']))
面向对象的程序设计
如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
面向对象的设计思想是从自然界中来的
在自然界中,类(Class)和实例(Instance)的概念是很自然的
Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念
实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法
面向对象的三大特点:数据封装、继承和多形态
11.1 类和实例
面向对象最重要的概念就是类(Class)和实例(Instance)
- 类是抽象的模板,比如Myclass类
- 实例是根据类创建出来的一个个具体的“对象”
- 每个对象都拥有相同的方法,但各自的数据可能不同。
class: 创建类Myclass,一般建议将类的首字母大写:
class Myclass:
pass
为类创建实例my_thing,类似调用函数。类的实例也可以称作为对象
class Myclass:
pass
my_thing = Myclass()
通过定义类及创建对象的编程模式被称为“面向对象编程”
类可以定义数据类型,决定如何使用该数据类型
type()
:得到一个数据的类型
print(type({"1":123}))
print(type([1,2,3]))
print(type(123))
# 输出:<class 'dict'>
# 输出:<class 'list'>
# 输出:<class 'int'>
用 type() 来得到一个对象所在的类:
class Myclass:
pass
my_thing = Myclass()
print(type(my_thing))
#输出: <class '__main__.Myclass'>
- my_thing:对象
- Myclass:类
- __main :正在运行的文件
通过 <class 'main.Myclass'> 知道, Myclass 是正在运行文件中所定义的类
11.2 方法
方法实质上是类中定义的函数,它至少有一个参数,其中第一个参数总是该调用方法的对象,习惯上我们将第一个参数命名为 self
class Myphone:
def ring(self):
print("叮铃铃铃……")
phone = Myphone()
phone.ring()
# 输出:叮铃铃铃……
示例中,类 Myphone 中定义了一个方法 ring()
phone 为 Myphone 的一个对象
当 phone 调用方法 ring() 时,程序输出 "叮铃铃铃……"
方法的参数
除了参数 self 之外,类的方法还可以加入更多的参数
class Myphone:
price = 4000
def cal(self,discount):
return self.price * discount
phone = Myphone()
print(phone.cal(0.8))
# 输出:3200.0
方法 cal() 中除了 self 之外,还有一个 discount 参数
cal() 方法返回 self.price 与 discount 的乘积
当调用cal() 时,self 参数不用赋值,此处为 discount 赋值为 0.8,运行结果输出 3200.0
11.3 变量和属性
类的变量
在类中创建类的变量,类的变量对类的对象都一致
通过 对象名.变量名
的方式来获取变量的值
class Myclass:
name = "智能手机"
my_thing = Myclass()
print(my_thing.name)
# 输出:智能手机
类 Myclass ,变量 name,赋值为 "智能手机"
my_thing 是类Myclass的一个对象,my_thing.name 的值为 "智能手机"。
11.4 属性
类的变量与对象的变量都是对象的属性。如果我们调用的对象属性不存在——既不是类变量,也不是对象变量,计算机会返回属性错误:AttributeError。
# .get() 是字典类数据的属性,字符串数据不具备该属性
print({"a":"apple"}.get("a"))
# 输出: apple
print("apple".get("apple"))
# 返回 AttributeError: 'str' object has no attribute 'get'
# 字符串没有 get 属性
对象的变量
我们之前学习了类的变量,以及如何为类创建属性。但有时,属于类的不同对象会有不同的属性值。比如同样是宠物的名字,有的宠物叫“阿黄”,有的宠物叫“小咪”。我们可以通过为对象的变量赋值来操纵对象的具体属性。请见示例:
class Phone:
pass
hua_wei = Phone()
xiao_mi = Phone()
hua_wei.name = "华为"
hua_wei.color = "黑色"
xiao_mi.name = "小米"
xiao_mi.color = "白色"
print("这部" +hua_wei.name + "是" + hua_wei.color) #输出:这部华为是黑色
print("这部" +xiao_mi.name + "是" + xiao_mi.color) #输出:这部小米是白色
示例中,我们通过 hua_wei.name,hua_wei.color分别为对象 hua_wei 的变量 name,color (属性)赋值;类似地,通过 xiao_mi.name,xiao_mi.color 分别为 xiao_mi 的变量 name,color (属性)赋值。
查看属性
我们可以用dir()
来看看某个对象具有哪些属性。
a = "apple"
print(dir(a))
# 输出:
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
上方示例中,有一些字符串属性我们在之前的课程学习过。除了字符串,我们还可以用dir()
得到诸如列表、字典等其他数据类型的属性:
print(dir({}))
# 输出:
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
上方示例中,我们用dir()
导出了字典数据的属性。
class Phone:
def __init__(self,name):
self.name =name
def number(self,num):
self.num = num
return "这部" + self.name + "手机的号码是:" + self.num
hua_wei = Phone("华为")
hua_wei.price = 5000
xiao_mi = Phone("小米")
print(dir(hua_wei))
# 输出:['__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__repr__', '__setattr__', '__str__', 'name', 'number', 'price']
print(dir(xiao_mi))
# 输出:['__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__repr__', '__setattr__', '__str__', 'name', 'number']
建立初始属性
在创建类的时候,可以用 __init__()
为类建立初始属性
每当建立类的对象时,就可以为对象的属性赋值
我们来看之前的方法,以做比较:
class Phone:
name = "手机"
color = "白色"
my_phone = Phone()
your_phone = Phone()
print(my_phone.name) # 输出:手机
print(my_phone.color) # 输出:白色
print(your_phone.name) # 输出:手机
print(your_phone.color) # 输出:白色
示例中,当我们创建对象 my_phone 和 your_phone时,都接收了类 Phone 的 name,color 属性:手机和白色。而下方代码:
class Phone:
def __init__(self,name,color):
self.name = name
self.color = color
hua_wei = Phone("华为","黑色")
xiao_mi = Phone("小米","白色")
print(hua_wei.name) # 华为
print(hua_wei.color) # 黑色
print(xiao_mi.name) # 小米
print(xiao_mi.color) # 白色
示例中,我们用了__init__()
方法在创建类 Phone 时,就设置了初始属性与对象 self 之间的关系。每当建立新的对象,比如 hua_wei,xiao_mi 时,可以为它们各自的 name,color 属性附上不同的值。
举例:
家里又新添了一个成员,猫小咪。我们来看看阿黄和小咪今年都多大了:
1.创建类 Pet;
2.创建init(),参数 self, name, age;
3.在init()内,令参数 name 为 self.name 赋值;令参数 age 为 self.age 赋值;
4.创建Pet的对象a_huang,name 为 "阿黄",age 为 3;
5.创建Pet的对象xiao_mi,name 为 "小咪",age 为 2;
class Pet:
def __init__(self,name,age):
self.name = name
self.age = age
a_huang = Pet("阿黄",3)
xiao_mi = Pet("小咪",2)
print("{}今年{}岁了".format(a_huang.name,a_huang.age))
print("{}今年{}岁了".format(xiao_mi.name,xiao_mi.age))
关键字self
之前我们学到的__init__()
方法中的关键字 self代指对象自身,我们还可以见到它更强大的用法:
class Phone:
def __init__(self,name):
self.name =name
def number(self,num):
self.num = num
return "这部" + self.name + "手机的号码是:" + self.num
hua_wei = Phone("华为")
xiao_mi = Phone("小米")
print(hua_wei.number("13813813818"))
print(xiao_mi.number("13913913919"))
#输出:这部华为手机的号码是:13813813818
#输出:这部小米手机的号码是:13913913919
上方示例中,在类 Phone 中,__init__()
方法中变量为 name;方法 number() 中变量为 num,令self.num 等于 num。
因此,属于类 Phone 的对象 hua_wei,xiao_mi 也具有方法 number(),输出 hua_wei.number("13813813818") 时,屏幕显示:"这部华为手机的号码是:13813813818"
11.5 继承和多形态
假设我们有一个建好的类 A,此时我们想新建一个类 B,类B 中有些属性和类A相同,为了降低代码的重复性,我们可以让类B继承类A的部分属性。
class Product:
name = "华为"
class Laptop(Product):
pass
print(Laptop.name)
#输出:华为
上方示例中,类Laptop后有一个() ,括号内填入的参数是类 Laptop 继承的类:Product。类Laptop 继承了类Product的name属性:"华为"。
我们将被继承的类称为父类,继承的类称为子类。
改写父类
当子类继承父类时,难免会出现部分属性或方法与父类相冲突的情况。这时,我们需要在子类对继承来的属性或方法做改写。
class Product:
def __init__(self,name):
self.name =name
def number(self,num):
self.num = num
return self.name + "的号码是:" + self.num
class Phone(Product):
pass
class Laptop(Product):
def number(self):
return "笔记本没有号码"
huawei_phone = Phone("华为手机")
huawei_laptop = Laptop("华为笔记本")
print(huawei_phone.number("13813813818"))
# 输出:华为手机的号码是:13813813818
print(huawei_laptop.number())
# 输出:笔记本没有号码
上方示例中,类Phone和Laptop都是类Product的子类,但是由于笔记本没有号码,因此在类Laptop中对number() 的内部代码进行了重新改写:去掉了参数 num,修改了返回的字符串。
class Pig:
def __init__(self):
self.eye = 2
self.foot = 4
self.tail = 1
def sing(self):
# 猪可不会唱歌哦
return False
def speak(self):
# 猪也不会说话!
return False
# Peppa 继承 Pig
class Peppa(Pig):
# 佩奇会唱歌
def sing(self):
return "啦啦啦"
# 佩奇会说话
def speak(self):
return "我是佩奇,这是我的弟弟乔治"
peppa_pig = Peppa()
print("佩奇有{}只眼睛".format(peppa_pig.eye))
print("佩奇有{}只蹄子".format(peppa_pig.foot))
print("佩奇有{}条尾巴".format(peppa_pig.tail))
print("佩奇唱:"+peppa_pig.sing())
print("佩奇说:"+peppa_pig.speak())
# 输出:佩奇有2只眼睛
# 输出:佩奇有4只蹄子
# 输出:佩奇有1条尾巴
# 输出:佩奇唱:啦啦啦
# 输出:佩奇说:我是佩奇,这是我的弟弟乔治
多形态
多形态:同一个运算符号在不同数据间体现不同运算方法的现象
比如运算符 + 在不同的数据类型之间有不同的运算功能:
1 + 2 == 3
"你" + "好" == "你好"
当数据类型都是 int 时,+ 的功能是做求和运算
当数据类型都是 str 时,+ 将前后两者联结
11.6 神奇方法
当我们新创建一个对象,直接输出该对象时,默认返回的是该对象的内存地址:
class Phone:
def __init__(self,name):
self.name =name
hua_wei = Phone("华为")
print(hua_wei.name)
# 输出:华为
print(hua_wei)
# 输出: <__main__.Phone object at 0x106ebdb70>
# 以上为hua_wei的内存地址
对象的内存地址有时对我们用处并不大,如果在输出对象时,我们想看到特定信息。可以用 __repr__()
方法实现:
class Phone:
def __init__(self,name):
self.name =name
def __repr__(self):
return "这是一部" + self.name +"手机"
hua_wei = Phone("华为")
print(hua_wei)
# 输出:这是一部华为手机
上方示例中,我们在类 Phone 中加了一个 __repr() __
方法
当输出对象时,返回 "这是一部" + self.name +"手机",其中 self.name是对象的 name 属性,该例子里是 "华为"。
__repr() __
只可以有一个参数self
,而且返回的必须是字符串。
class Famous:
def __init__(self,name):
self.name = name
def __repr__(self):
# 下方判断 self.name 是否为 "鲁迅"
if self.name == "鲁迅":
return "{}:在我的后园,可以看见墙外有两株树。".format(self.name)
# 下方判断 self.name 是否为 "海明威"
if self.name == "海明威":
return "{}:世界是个美好的地方,值得为它奋斗。".format(self.name)
if self.name == "维特根斯坦":
return "{}:语言的边界就是世界的边界。".format(self.name)
lu_xun = Famous("鲁迅")
# 下方创建对象,name 属性为 "海明威"
hemingway = Famous("海明威")
wittgenstein = Famous("维特根斯坦")
print(lu_xun)
print(hemingway)
print(wittgenstein)
# 输出: 鲁迅:在我的后园,可以看见墙外有两株树。
# 输出: 海明威:世界是个美好的地方,值得为它奋斗。
# 输出: 维特根斯坦:语言的边界就是世界的边界。
我们之前学过__init()__
和__repr()__
两种方法,像类似这种名字前后带有两个英文下划线的方法在 Python 中被称为神奇方法。之前我们学到的__init()__
和__repr()__
只是众多神奇方法中的冰山一角
class Clock:
def __init__(self, time):
self.time = time
def __repr__(self):
return "{time}点".format(time = str(self.time))
def __add__(self, hour):
self.hour = hour
new_time = (self.time + hour) % 24
return Clock(new_time)
# 假设现在是23点
now = Clock(23)
print(now + 20) # 输出:19点
print(now + 1) # 输出:0点
上方示例中,我们用__add__()
方法重新定义 + 的运算规则:self.time 与 hour 的和除以 24 的余数。所以当变量 now 定为 23点,now + 20 的运算结果为 19; now + 1 的运算结果为 0。
class StudentGroup:
def __init__(self, students, status):
self.student_list = students
self.status = status
def __iter__(self):
return iter(self.student_list)
def __len__(self):
return len(self.student_list)
def __contains__(self, student):
return student in self.student_list
上方示例中,我们分别采用了 __init()
、__iter()
、__len()
和__contains()
四个神奇方法:
(1)__init__()
之前学过,可以用来设置一个对象变量的初始值,在这里,我们设置了 self, students 和 status 三个参数;
(2)__iter__()
中的 iter 是英文单词 iterate(重复) 的含义,用了__iter() 方法可以将 for student in students 这样的语句用在 self.student_list 上;
(3)__len__()
可以在调用 len(student_group) 时,返回列表 student_group的长度;
(4)__contains__()
可以判断元素 student 是否在 self.student_list 之中。
在此基础之上,我们在下方代码继续创建类 Student,及其对象 a_qiang,lao_wang和 xiao_ming;接着创建类StudentGroup 的对象 completed 与 not_completed,并且用上述神奇方法操纵它们:
class Student:
def __init__(self, name):
self.name = name
a_qiang = Student('阿强')
lao_wang = Student('老王')
xiao_ming = Student('小明')
# 将 a_qiang、lao_wang 和 xiao_ming 分别放入 completed 与 not_completed 之中
completed = StudentGroup([a_qiang, lao_wang], {'任务完成': True})
not_completed = StudentGroup([xiao_ming], {'任务完成': False})
print(len(completed))
# 输出: 2
for student in completed:
print(student.name)
# 输出:
# "阿强"
# "老王"
if xiao_ming in not_completed:
print("{name} 没有完成任务".format(name = xiao_ming.name))
# 输出:小明 没有完成任务
上方示例中,我们创建了类 Student,并以此创建了 三个对象 a_qiang,lao_wang 和 xiao_ming。我们将它们分别放进不同的StudentGroup中:completed 和 not_completed。
1.小明完成了学习任务,请将 xiao_ming 从 not_completed 中移出至 completed 中;
2.妞妞刚报名了课程,还未完成任务,请创建类Student的对象 niu_niu,name 为 "妞妞";
3.将niu_niu 添加至 not_completed。
class StudentGroup:
def __init__(self, students, status):
self.student_list = students
self.status = status
def __iter__(self):
return iter(self.student_list)
def __len__(self):
return len(self.student_list)
def __contains__(self, student):
return student in self.student_list
class Student:
def __init__(self, name):
self.name = name
a_qiang = Student('阿强')
lao_wang = Student('老王')
xiao_ming = Student('小明')
niu_niu = Student("妞妞")
completed = StudentGroup([a_qiang, lao_wang, xiao_ming], {'任务完成': True})
not_completed = StudentGroup([niu_niu], {'任务完成': False})
print("完成的同学有:")
for student in completed:
print(student.name)
print("未完成的同学有:")
for student in not_completed:
print(student.name)
# 输出:完成的同学有:
# 输出:阿强
# 输出:老王
# 输出:小明
# 输出:未完成的同学有:
# 输出:妞妞
分别输出完成和未完成任务的学生人数
class StudentGroup:
def __init__(self, students, status):
self.student_list = students
self.status = status
def __iter__(self):
return iter(self.student_list)
def __len__(self):
return len(self.student_list)
def __contains__(self, student):
return student in self.student_list
class Student:
def __init__(self, name):
self.name = name
a_qiang = Student('阿强')
lao_wang = Student('老王')
xiao_ming = Student('小明')
niu_niu = Student("妞妞")
completed = StudentGroup([a_qiang, lao_wang, xiao_ming], {'任务完成': True})
not_completed = StudentGroup([niu_niu], {'任务完成': False})
print("完成的同学有{}个".format(len(completed)))
print("未完成的同学有{}个".format(len(not_completed)))
# 输出:完成的同学有3个
# 输出:未完成的同学有1个