Grundlagen-13, Objektorientiert Ⅱ
一、封装的引入
- 封装的基本概念
- 封装是面向对象的三大特征之一。
- 封装出现的原因:
- 1.属性不能随意修改
- 2.属性不能改为任意的值
- 封装是指隐藏对象中一些不希望被外部所访问的属性和方法。
- 我们可以提供一个getter()方法和setter()方法是外部可以访问已经封装的属性。
- getter()方法用来获取对象中指定的属性
- setter()方法用来设置对象指定的属性
- 封装的思想:
- 1.我们需要一种方式来增强数据的安全性。
- 2.属性不能随意更改。(允许修改时,才能修改。)
- 3.属性不能为任意值。
- 可以为对象的属性使用双下划线开头
__xxx
。双下划线开头的属性,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问。 - 其实隐藏属性只不过是python自动为属性改了一个名字——>
_累名__属性名
,例如__name ——>_Person__name
(这种方式实际上依然可以在外部访问,但是这种方式我们一般不用。一般我们会将一些私有属性以单下划线开头) - 一般来说,单下划线开头的属性都是私有实行,没有特殊情况下不要修改私有属性。
封装思想的简单演示:
class Car:
def __init__(self, name, color):
self.name = name
self.color = color
def run(self):
print('汽车开始跑了')
def horn(self):
print('BIBIBIBIKBI')
car = Car('法拉利','红色')
# print(car.name, car.color)
car.name = '小猫咪'
print(car.name,car.color)
car.run()
car.horn()
打印输出结果:
汽车开始跑了
BIBIBIBIKBI
分析:上述代码应该输出
小猫咪 红色
汽车开始跑了
BIBIBIBIKBI
但是为car实例添加的属性为小猫咪时,小猫咪的这恶搞属性就覆盖了法拉利。
这样就打乱了这一类的代码。导致错误。
二、封装
- 封装其实是做出一个相对封装的效果。(所谓古语曰:防君子不防小人)
- 封装演示实例:
1、比较low的封装
class Dog:
def __init__(self, name):
self.hidden_name = name
dog = Dog('哈士奇')
# print(dog.hidden_name)
dog.name = '田园犬'
print(dog.hidden_name)
打印输出结果:
哈士奇
分析:使用这种方式过于简单,只是简单的对哈士奇进行了封装,田园犬这个属性没有对哈士奇这个属性产生影响。
2、绝对封装演示:
class Car():
def __init__(self, name, color):
# 属性名称 值
# 这种方式告诉别人这个是私有属性,不要轻易修改
# __name 叫做完全封装 私有属性
self.__name = name
self.__color = color
def run(self):
print('汽车开始跑了')
def laba(self):
print('滴滴滴')
# getter方法 setter方法
# getter方法用查看私有属性 如果只有一个getter方法,没有setter方法:这个属性是一个只读属性
def get_name(self):
return self.__name
# setter方法是用来修改私有属性
def set_name(self, name):
self.__name = name
car = Car('奔驰', '黑色')
# _类名__属性名
# print(car._Car__name)
# car.__name = '雪佛兰' 这种方式是外行 没有工作经验的程序员
print(car.get_name())
# print(car.set_name('宝马'))
print(car.get_name())
打印输出结果:
E:\python\python.exe D:/PycharmProjects/基础班第11期/day-13/上课代码/01-封装.py
奔驰
奔驰
Process finished with exit code 0
分析:首先,在代码内部定义出了读取方法和修改方法,就证明,该代码内部的封装属性是可以修改的。
总结: 封装确实增加了类的定义的负责程度,但是也保证了数据的安全。
- 1.隐藏属性名,使调用者无法随意的修改对象中的属性。
- 2.增加了getter()方法和setter()方法,很好控制属性是否是只读的。
- 3.使用setter()方法设置属性,可以在再做一个数据的验证。
- 4.使用getter()方法获取属性,使用setter()方法设置属性可以读取属性和修改属性的同时做一些其他的处理。
总结:__name的意思是禁止修改此属性(相当于加了一层).
3、真正意义上的封装
class Person:
def __init__(self, name):
self._name = name
# getter()--查看属性方法:(可读)
def getter_name(self):
print('用户调用了此读取方法')
return self._name
# setter()--修改属性方法:(可写)
def setter_name(self, name):
print('用户调用了此修改方法')
self._name = name
p1 = Person('陈慧翎')
p1.setter_name('周慧敏')
# print(p1._name) # 未调用get方法禁止查看属性
# p1.name = '黄一琳'
# print(p1.name)
print(p1.getter_name())
打印输出结果:
用户调用了此修改方法
用户调用了此读取方法
周慧敏
总结: 封装确实增加了类的定义的负责程度,但是也保证了数据的安全。
- 1.隐藏属性名,使调用者无法随意的修改对象中的属性。
- 2.增加了getter()方法和setter()方法,很好控制属性是否是只读的。
- 3.使用setter()方法设置属性,可以在再做一个数据的验证。
- 4.使用getter()方法获取属性,使用setter()方法设置属性可以读取属性和修改属性的同时做一些其他的处理。
三、property装饰器
- property装饰器
- 我们可以适应@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的制只读属性,可以与虽定义的属性配合使用,这样可以防止属性被修改。
演示示例:
class Person:
def __init__(self, name):
self._name = name
@property # 设置只读属性(与getter方法类似)
def name(self):
print('getter方法执行了')
return self._name
@name.setter # 代表setter方法
def name(self, name):
print('setter方法执行了')
self._name = name
p1 = Person('周慧敏')
p1.name = '黄一琳'
print(p1.name)
打印输出结果:
setter方法执行了
getter方法执行了
黄一琳
总结:首先通过装饰器设置只读属性,再通过装饰器设置setter可修改属性,就可以告诉其他的同事,这是一个封装的参数,如果需要修改请通过正当方式修改。
四、继承
1、继承的简介
- 继承是面向对象的三大特性之一。
- 通过继承我们可以是一个类取到其他类中的属性和方法。
- 在定义类时,可以在类名后面的括号中指定当前类的父类(超类、基类)
- 继承提高了类的复用性。让类与类之间产生了关系。有了这个关系,才有了多态的特性。
class Animal():
def sleep(self):
print('动物会睡觉')
def run(self):
print('动物会跑')
# 定义一个狗类
# 1. 直接在动物类上面修改 违反ocp
# 2. 创建一个新的类(狗类) 会出现大量重复的代码
# 3. 直接从动物类中间拿到属性和方法 (推荐)=> 继承:就是在定义类名之后的括号里写上继承的父类(超类)
class Dog(Animal):
pass
dog = Dog()
dog.run()
dog.sleep()
# 检测是否是类创建的实例对象
res = isinstance(dog, Dog)
print(res)
# 检测是否是当前类的父类 issubclass
res = issubclass(Dog, Animal)
print(res)
# object 是所有类的父类
res = issubclass(Animal, object)
print(res)
打印输出结果:
E:\python\python.exe D:/PycharmProjects/基础班第11期/day-13/上课代码/04-继承的使用.py
动物会跑
动物会睡觉
True
True
True
Process finished with exit code 0
- isinstance检测是否是类创建的实例对象
- issubclass检测是否当前的父类
2、方法的重写
- 如果在子类中有和父类同名的方法,则通过子类实例去调用方法时,会调用子类的方法而不是父类的方法,这个特点我们成为方法的重写(覆盖)
- 当我们调用一个对象的方法时:
- 会优先调用当前对象中寻找是否具有该方法,如果有则纸直接调用。
- 如果没有,则去当前父类中寻找,如果父类中有则直接调用父类的方法。
- 如果没有,则去父类中的父类寻找,以此类推,知道找到object,如果依然没有找到就报错。
class A(object):
def test(self):
print('A.....')
class B(A):
def test(self):
print('B......')
class C(B):
def test(self):
print('C......')
c = C()
c.test()
# 方法的重写: 子类重写父类的方法,会将父类中的方法所覆盖掉 发扬光大的作用才会重写方法
打印输出结果:
E:\python\python.exe D:/PycharmProjects/基础班第11期/day-13/上课代码/06-关于方法的重写.py
C......
Process finished with exit code 0
总结:方法的重写就是子类覆盖父类的方法。
3、supper方法及其使用
- super()可以获取当前类的父类
- 并且通过super()返回对象调用父类方法时,不需要传递self
class Animal(object):
def __init__(self, name):
self._name = name
def sleep(self):
print('动物会睡觉')
def run(self):
print('动物会跑')
def get_name(self):
print('get 方法调用了')
return self._name
class Dog(object):
def __init__(self, gender):
self.gender = gender
class Zhonghua(Animal, Dog):
def __init__(self, age, name, gender):
super().__init__(gender)
super().__init__(name)
self.gender = gender
self._name = name
self.age = age
def sleep(self):
super().sleep()
print('狗会睡觉')
def run(self):
print('狗会跑')
dog = Zhonghua('中华田园犬', 18, '男')
dog.sleep()
# print(dog.get_name())
print(type(Zhonghua))
print(issubclass(Zhonghua, type))
print(issubclass(type, object))
# 解耦合 1. 提高问题的解决概率 2. 提高问题的解决效率 3. 提高解决问题的速度 4. 降低爆发隐患的可能性
# 多重继承: 兄弟类中都有相同的方法,那么先调用写到前面的那个类的方法
打印输出结果:
E:\python\python.exe D:/PycharmProjects/基础班第11期/day-13/上课代码/05-super方法及其使用.py
动物会睡觉
狗会睡觉
<class 'type'>
False
True
Process finished with exit code 0
总结:通过supper方法可以是在调用子类的方法的同时调用出父类中的相同方法。
注:在输出的时候,会先输出父类中的方法。
、PEP8代码书写规范
- 缩进使用4个空格,空格是首选的缩进方式,python3不允许混合使用制表符和空格来缩进。
- 每一行最大长度限制在79个字符以内。
- 顶层函数、类的定义。前后使用两个空行隔开。
- import导入
- 建议在不同的行导入,例如
import os
omport sys
也可以这样:
from subprocess import Popen, PIPE
- 导包位于文件顶部,在模块注释、文档字符串之后,全局变量、常量之前,导入按照以下顺序分组:
标准库导入
相关第三方库导入
本地应用/库导入
在每一组导入之间加入空行 - python中定义字符串使用双引号、单引号是相同的,尽量保持使用同一方式定义字符串,当一个字符串包包含单引号或者双引号时,在做外层使用不同的符号来避免使用反斜杠转译,从而提高可读性。
- 百搭是个语句中的空格:
1.避免在小括号、方括号、花括号后跟空格。
2.避免在都好、分号、冒号之前添加空格。
3.冒号在切片中就像二元运算符,两边要有相同数量的空格,如果某个切片参数省略,空格也省略。
4.避免为了和另外一个赋值语句对齐,在赋值运算符附加多个空格。
5.避免在表达式尾部添加空格,因为表达式尾部空格通常看不见,会产生混乱。
6.总是在二元运算符两边加一个空格,赋值(=),增量赋值(+=,-=),比较(==,<,>,!=,<>,<=,>=,in,not,in,is.is not),布尔(and,偶然,not)
7.避免将小的代码和if/for/while放在同一行,要避免代码太长。
1 if foo == 'blah': do_blah_thing()
2 for x in lst: total += x
3 while t < 10: t = delay()
- 永远不要使用字母’l’(L),o(O)或者大写的I作为字符串的变量,在有些字体里这些字符无法和数字0和1区分。
- 累名一般使用首字母大写的约定。
- 函数名应该小写,如果想提高可读性可以用下划线分割
- 如果函数的参数名和已有的关键词冲突,在做后加单一下划线比缩写或者随意拼写更好。
- 方法名和实例变量使用下划线分隔的小写单词,以提高可读性。