10. 深入面向对象 - 2
10.1 隐藏数据
引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Cat(object): def __init__(self, new_name, new_age): self.name = new_name self.age = new_age
def print_info(self): print("我叫%s,今年%s了" % (self.name, self.age))
cat = Cat("波斯猫", 4)
cat.print_info()
cat.age = -10
cat.print_info()
|
如果运行上述代码,会发现,第二次输出的信息有误,其为”我叫波斯猫,今年-10岁了”
之所以出现这样的错误,究其原因是因为:我们通过对象直接给实例属性赋值的这种方式容易出错
如果在赋值的时候,是通过一个实例方法的调用,在方法中对数据进行严格的检查,合格的数据可以给属性设置,不合格的数据就提醒开发者,这样一来就能够保证数据的准确性了
那该怎样实现呢
答:
- 设置属性为私有属性
- 添加额外对属性操作的方法
实现对数据的隐藏
想要实现上述阐述的那个功能,我们需要做2件事情
- 设置属性为私有属性
- 添加额外对属性操作的方法
设置私有属性
在Python中,如果想要设置为私有的属性,那么仅仅需要在定义属性时在前面加两个下划线__即可
示例如下:
1 2 3 4
| class 类名(object): def __init__(self): self.name = "小明" self.__age = 18
|
既然有了私有属性,那对象能够直接操作它呢?
答:不能,否则就没有私有的作用了
示例如下:
1 2 3 4 5 6 7 8 9
| class Student(object): def __init__(self): self.name = "小明" self.__age = 18
s = Student() print(s.name) print(s.__age)
|
添加额外对属性操作的方法
想要实现对私有属性的操作,我们需要定义方法,在方法中操作
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Student(object): def __init__(self): self.name = "小明" self.__age = 18
def set_age(self, new_age): if 1 <= new_age <= 120: self.__age = new_age print("设置年龄成功") else: print("年龄数据有误...") def get_age(self): return self.__age
s = Student() s.set_age(20) print(s.get_age())
|
简单总结
1.操作属性有2种方法
2.通过使用方法来进行修改,就可以在方法中进行数据合法性的检查
3.通过__可以将属性变为私有属性,这样就防止了通过对象直接操作属性时可能带来的隐患
10.2 隐藏功能
引入
生活中我们肯定去过银行办理过业务,我们可以从银行的大门进入大厅,取号等待办理业务,可以在大厅里来回走动,这个区域是所有人都可以随意进出的;而银行办公人员工作的地方,只能有相应的权限的办公人员才能进出,这个区域对于外来办理业务的人员来说是禁止的。
通过上述的描述,大家能够理解了一件事情,即访问的地方不同需要的权限不同
那么试想,一个较大软件系统肯定有很多个可以让用户直接调用的接口(API可以简单理解为方法)这些接口可以任意调用,而有些接口就不能使用
在Python中,我们把可以通过对象直接调用的方法叫做公有方法,不能通过对象直接调用的方法叫做私有方法
实现对方法的隐藏
对于定义私有方法的方式与定义私有属性基本相同,就是在方法的前面添加__(即2个下划线_)
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class BankService(object): def __bank_2_bank(self, money): print("这里是银行之间的转账代码....") return True
def transfer(self): money = int(input("请输入转账金额:")) if money > 100000: if self.__bank_2_bank(money): print("转账成功") else: print("转账失败...") else: print("都没钱,还转什么啊!自己留着花吧!")
bank_service = BankService() bank_service.transfer()
|
运行测试(转账成功)
1 2 3
| 请输入转账金额:20000000 这里是银行之间的转账代码.... 转账成功
|
运行测试(转账失败)
1 2
| 请输入转账金额:100 都没钱,还转什么啊!自己留着花吧!
|
注意点
Python中没有像C++中public和private这些关键字来区别公有和私有,它是以命名方式来区分,如果在名字前面加了2个下划线__,则表明该属性是私有,否则为公有
直接调用私有方法所出现的问题
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class BankService(object): def __bank_2_bank(self, money): print("这里是银行之间的转账代码....") return True
def transfer(self): money = int(input("请输入转账金额:")) if money > 100000: if self.__bank_2_bank(money): print("转账成功") else: print("转账失败...") else: print("都没钱,还转什么啊!自己留着花吧!")
bank_service = BankService()
bank_service.__bank_2_bank()
|
运行结果:
1 2 3 4 5 6 7
| AttributeError Traceback (most recent call last) Cell In [1], line 19 17 bank_service = BankService() 18 ---> 19 bank_service.__bank_2_bank()
AttributeError: 'BankService' object has no attribute '__bank_2_bank'
|
10.3 对象关联
引入
大家知道,我们在上学的时候,每个同学是一个对象,那么教室也是一个对象对吗?每个同学肯定是属于某一个教室的对吧,例如张三是205班的;那么怎样才能用代码来实现他们之间的关系呢
如下代码,创建了1个教室对象,1个学生对象,该怎样将它们2个联系起来呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Classroom(object): def __init__(self, name): self.classroom_name = name
class Student(object): def __init__(self, name): self.student_name = name
class205 = Classroom("205班")
stu01 = Student("学生1")
|
将两个对象进行关联
在上述的代码中,我们发现如果当前的教室对象与学生对象是没有任何关系关联的,如果想要实现学生属于教室,那么只需要2步就能实现
- 搞清楚
谁属于谁,例如上述示例中,学生属于教室
- 在范围大的那个对象中,定义一个属性存储范围小的对象引用即可
示例一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Classroom(object): def __init__(self, name): self.classroom_name = name
class Student(object): def __init__(self, name): self.student_name = name
class205 = Classroom("205班")
stu01 = Student("学生1")
class205.stu = stu01
|
调用关联的对象
上述代码已经完成了对象学生与教室的关联,那么怎样调用呢?格式如下:
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Classroom(object): def __init__(self, name): self.classroom_name = name self.stu = None
def add_new_stu(self, stu): """定义新方法用来完成关联""" self.stu = stu
class Student(object): def __init__(self, name): self.student_name = name
class205 = Classroom("205班")
stu01 = Student("学生1")
class205.add_new_stu(stu01)
print(class205.stu.student_name)
|
运行结果:
关联多个对象
既然关联1个对象搞懂了,那么关联多个也就手到擒来,方式如下:
- 在范围大的那个对象中再定义一个新的属性,通过设置属性指向新的对象
- 如果关联的新的对象与之前关联的对象类型相同,可以考虑用列表、字典、集合等方式将它们关联
实现将多个学生关联到一个教室:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Classroom(object): def __init__(self, name): self.classroom_name = name self.stus = []
def add_new_stu(self, stu): """定义新方法用来完成关联""" self.stus.append(stu)
class Student(object): def __init__(self, name): self.student_name = name
class205 = Classroom("205班")
stu01 = Student("学生1") stu02 = Student("学生2") stu03 = Student("学生3")
class205.add_new_stu(stu01) class205.add_new_stu(stu02) class205.add_new_stu(stu03)
print(class205.stus[0].student_name) print(class205.stus[1].student_name) print(class205.stus[2].student_name)
|
10.4 面向对象中的继承
引入
继承,通俗的说就是不劳而获,不需要付出一下子什么都有了
Python中也有继承这个功能,它能够实现一个类中可以使用另外一个类中的代码
在开发较大的项目时,往往需要多个类实现,当我们定义一个新类时如果这个新的类的功能与之前某个类功能很类似,此时通过使用继承可以让新类不用写代码或者写很少的代码,就实现了想要的所有功能,这样一来编写的代码少了也就提高了开发效率
格式定义
生活中,如果子女想要继承父辈的遗产,往往需要一定的证明素材,这样才有合法性;同理Python中想要表示出谁继承谁,也需要一定的格式来规定,这种方式就是在定义类的()中写上父类的名字。
如下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Animal: pass
class Dog(Animal): pass
class Cat(Animal): pass
class BoSiCat(Cat): pass
|
在继承中的专用术语
为了更加清楚的标记处,谁继承了谁,我们用父类(基类)、子类(派生类)来称呼它们
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class A(object): def __init__(self): self.num = 10
def print_num(self): print(self.num + 10)
class B(A): pass
b = B() print(b.num) b.print_num()
|
运行结果:
从上述的运行结果来看,虽然类B没有写代码,但是依然能够正确的执行,这足以说明类B继承了类A的功能
10.5 单继承
单继承的概念
单继承,就是一个子类只继承一个父类
下面定义了2个类,Animal类是父类它编写了吃、喝、睡3个功能,Dog类是子类它继承了Animal的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Animal: def eat(self): print("吃饭...")
def drink(self): print("喝水...")
def sleep(self): print("睡觉...")
class Dog(Animal): pass
dog = Dog() dog.eat() dog.drink() dog.sleep()
|
运行上述代码,会看到如下效果:
为类添加新功能
假如现在需要在上述的代码中,添加一个新的方法info,那么该写到父类中还是子类中呢?
写到父类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Animal: def eat(self): print("吃饭...")
def drink(self): print("喝水...")
def sleep(self): print("睡觉...")
def info(self): print("名字是:" + self.name + ", 年龄是:" + str(self.age))
class Dog(Animal): def __init__(self, name, age): self.name = name self.age = age
dog = Dog("小黑", 2) dog.eat() dog.drink() dog.sleep() dog.info()
|
运行效果如下:
1 2 3 4
| 吃饭... 喝水... 睡觉... 名字是:小黑, 年龄是:2
|
从上述的运行来看,虽然Animal类中没有name属性、age属性,但是可以在子类中添加这2个属性,只要方法被子类继承后,通过子类的对象调用时可以直接使用子类的属性
但是,要注意如果在父类Animal中如果没有name、age那么也就意味着不能直接创建Animal类的实例对象,否则当通过Animal的实例对象调用info方法时就会出现找不到name、age属性的问题
写到子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Animal: def eat(self): print("吃饭...")
def drink(self): print("喝水...")
def sleep(self): print("睡觉...")
class Dog(Animal): def __init__(self, name, age): self.name = name self.age = age
def info(self): print("名字是:" + self.name + ", 年龄是:" + str(self.age))
dog = Dog("小黑", 2) dog.eat() dog.drink() dog.sleep() dog.info()
|
运行效果如下:
1 2 3 4
| 吃饭... 喝水... 睡觉... 名字是:小黑, 年龄是:2
|
两种定义方式的区别
- 当前将新方法定义到子类中不会对父类造成影响
- 如果将新方法定义到父类中则继承这个父类的所有子类都会获得这个新方法,但是有些子类并不需要
- 如果将新方法定义到父类中可能导致父类无法实例化
总结:
如果一个方法在多个子类中用到,那么就定义在父类的方法中,否则就定义在子类中。
10.6 多继承
多继承的概念
如果一个类继承了多个父类,那么这就是多继承
定义格式
单继承时在类()中写一个父类的名字,那么多继承就需要在()中写多个父类的名字而且用英文逗号,进行分割
1 2 3 4 5 6 7 8 9 10
| class A: pass
class B: pass
class C(A, B): pass
|
多继承的应用
我们都知道今天的手机功能已经非常全面了,而且还在突风猛进的进化中,不仅有电话功能还有照相功能(参照照相机)、播放音乐(参照MP3)等,下面我们通过一步步的展示方式来揭晓多继承的真实作用价值所在。
首先我们定义一个普通的手机Telephone(即很久之前的功能机)
1 2 3 4 5 6 7 8
| class Telephone: def call(self): """打电话""" print("正在打电话...")
def answer(self): """接电话""" print("正在接电话...")
|
如果我们想要让手机拥有照相机的功能,此时就需要定义一个照相机类Camera
1 2 3 4
| class Camera: def take_photo(self): """拍照功能""" print("正在拍照...")
|
然后让Telephone继承Camera
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Camera: def take_photo(self): """拍照功能""" print("正在拍照...")
class Telephone(Camera): def call(self): """打电话""" print("正在打电话...")
def answer(self): """接电话""" print("正在接电话...")
|
创建一个手机对象,此时就手机就继承了拍照功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Camera: def take_photo(self): """拍照功能""" print("正在拍照...")
class Telephone(Camera): def call(self): """打电话""" print("正在打电话...")
def answer(self): """接电话""" print("正在接电话...")
phone = Telephone() phone.call() phone.answer() phone.take_photo()
|
运行效果:
1 2 3
| 正在打电话... 正在接电话... 正在拍照...
|
如果想要让手机拥有听音乐的功能,我们可以定于一个MP3类
1 2 3 4
| class MP3: def play_music(self): """播放音乐功能""" print("正在播放音乐...")
|
最后让手机Telephone类继承MP3类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Camera: def take_photo(self): """拍照功能""" print("正在拍照...")
class MP3: def play_music(self): """播放音乐功能""" print("正在播放音乐...")
class Telephone(Camera, MP3): def call(self): """打电话""" print("正在打电话...")
def answer(self): """接电话""" print("正在接电话...")
phone = Telephone() phone.call() phone.answer() phone.take_photo() phone.play_music()
|
带有音乐功能的手机就做好了,试试看:
1 2 3 4
| 正在打电话... 正在接电话... 正在拍照... 正在播放音乐...
|
10.7 重写
引入
我们知道一个子类如果继承了父类,那么当通过子类对象去调用一个方法时,如果子类对象中没有此方法,那么就会到继承的父类中查询,如果查询到有则进行调用
但是有时,我们发现子类继承的父类的方法不能100%满足子类对象的需求,则此时就需要在子类中定义一个与父类相同的名字的方法,此时子类对象调用这个方法时即使父类中有,但依然不会调用,而是调用子类中的方法。
什么是重写
我们把子类中定义了与父类中相同名字的方法,叫做重写(大白话就是:子类的方法覆盖了父类中的同名方法)
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Father(object): def play_game(self): print("父类中的play_game")
def drink(self): print("父类中的drink方法")
class Son(Father): def play_game(self): print("子类中的play_game")
father = Father() father.play_game()
son = Son() son.play_game() son.drink()
|
运行结果:
1 2 3
| 父类中的play_game 子类中的play_game 父类中的drink方法
|
注意事项
如果父类中的方法在子类继承时发现并不符合子类的需求,此时我们在子类中重写这个方法即可
切记:不要直接在父类中修改此方法,如果将父类中的方法改成你子类的功能,虽然子类创建的对象可以100%满足要求,但你并不能保证其它继承这个父类的子类也需要同样的功能,所以在以后的开发工作中,一个父类定义好了之后,就不要轻易的修改,否则继承它的子类都要进行修改,这个工作量是非常大的,请不要这么做。