前言
前面说了面向对象编程的基本思路和方法,主要是“封装”的这么一个特点。在面向对象的过程中还有其他的一些特点,比如我们常说的“继承”和“多态”;在我们类的属性和方法中还有涉及到一些权限、对象实例的区别,这次的更新都会再进行补充。
1.封装的案例
1.1登录模块封装
既然我们前面已经详细的说了类的属性和方法,那么就来通过测试里最常见到的登录模块的类进行封装特点的举例。
首先我们先明确我们的需求:进入某web项目登录页面,输入用户名/密码/验证码之后登录我们的系统。类里需要包含的属性(变量名):用户名username,密码password,验证码verify,方法:login()
那么我们这个登录的类就可以设计成如下图所示的框架
ps:因为使用的是封装的特点,前面在魔法方法中有提到过if __name__ == '__main__':,如果我们在“主函数”中去调用 我们的类,这样之后别的模块里的语句想再倒入我们这个模块来用这里面的类的话就可以避免重复执行我们这“主函数”的语句。
2.继承
继承如其名,描述的是类与类之间的关系,为什么要有这个特点呢,我们书写代码的规范的根本就在于提高我们的效率,那么继承这个特点就是为了减少代码的冗余,根据需求将属性和方法封装到一个抽象的类中,实现重复代码不需要多次书写, 提高我们的编程能效。
继承的语法如下:
class 类A(object): # 默认继承 object类
class 类B(类A): # 类A 继承 类B
上面的object就是默认继承的类,它是python中最原始的类,可以说object是父类,类A是它的子类;同理,第二行的意思就是类A是类B的子类。
这里子类如果继承父类之后,子类的对象就可以直接使用父类中的属性和方法。
例如:我们定义一个动物类,动物有姓名和年龄属性,具有吃和睡的行为(方法);我们再定义一个狗类,狗类具有动物类的所有属性和方法,并且具有看门的特殊行为(方法)。
从上面的代码不难看出,我们写的动物类里声明(初始化)了两个变量属性name和age,但是我们并没有直接给他们赋值,这样的写法就相当于是在一个函数的形式里传递参数,在后面我们对象去调用这个类将它实例化后传入实参名,脚本里的xiaogou = dog(17,'zzx')就代表了xiaogou这个具体的对象的实际属性(参数)。最后使用我们的dir函数就能够打印出来我们xiaogou对象里的所有属性和方法,在执行结果的最后两行我们能够看到父类animal的age和name属性已经在xiaogou对象对应的抽象类dog中,说明dog类继承了animal类。
另外,继承也是有传递性的,如果说B类是A类的子类,那么B类也可以做另外一个类的父类,因此我们定义一个C类来作为B类的子类,看看我们C类能不能去使用A类中的方法和属性。
我们可以清晰的看到,C类将A类的属性name和方法aa给继承了。
3.重写
重写指的是当父类中的方法实现不能满足子类需求时,可以对父类中的方法进行重写。
3.1覆盖式重写
覆盖式的重写具体的应用场景就是在开发过程中,父类的方法实现 和 子类的方法实现,完全不同了我们就可以使用 “覆盖” 的方式,在 子类中 重新编写 父类的方法实现。
实现的方式 : 相对于在 子类中 定义了一个 和父类同名的方法并且实现
如上图所示,其实我们本应该是在B类里继承我们A类的way()方法的,但是我们在B类当中对way()方法进行了覆盖式的重写,那么我们就在B类里去调用way()方法就会完全按照我们重新写的方法调用,不过这个调用并不会影响A类(父类)。
3.2扩展式重写
和我们前面说的覆盖式不同的是,扩展式并不会让我们继承到的方法里的其他不需要的东西也被取代,而是在实现这个语句的基础上再进行方法的扩写。
扩展式与覆盖式直接的书写不同,它需要有一定的语法格式如下
在子类中重写父类方啊,在子类中需要的位置使用 super().父类方法名调用父类方法执行
ps:super是pthon当中的一个 特殊的类; super()就是使用super 类 创建出来的对象,最常用 在 重写 父类方法时,调用 在父类中封装的方法实现。
我们能看到在B类中我们使用了super() -- 它是由super类创建出来的对象,我们在这个对象后引用了drink()方法,然后我们对这个方法进行了我们的拓展代码书写,在保证drink()方法继承A类的前提下,我们还让它涵盖了我们新的要扩展的语句。
3.3多态-了解即可
其实多态在我们面向对象的三个特点当中是用的最少的,平常出现频率更高的是在封装和继承上。(偷偷说下自动化对于多态的特征几乎不怎么用到)。
多态的特点其实就是为了增加我们代码的灵活度,以继承和重写父类方法为前提,它是调用方法的技巧,不会影响到类的内部设计。
例如我们的需求是:在Dog类封装方法game,定义一个XiaoTianQuan类继承自Dog类,并且重写我们的game方法,最后定义一个Person类,并且封装一个和狗玩的方法
为什么我们在Person类里的play_with()方法中的“形参”有个dog,其实这个dog和前面的self一样我们可以把它看作是我们具体的对象让我们实例化抽象类引用的这么一个过程。再次强调我们只需去执行我们对象的调用并不需要管是什么对象,前提是要相似属性和方法的对象传入。
ps:如果同样结构的继承,那么父类的优先级会高的传入多态的对象。
4.面向对象其他语法
4.1私有和公有
在python当中,我们定义类的时候,可以给 属性和方法设置 访问的权限,即规定在什么地方可以使用,权限一般分为两种 : 公有权限和 私有权限
简单来说,直接定义的属性和方法就是公有的,这样的属性和方法可以在任何地方访问和使用,只要有对象就可以访问和使用。
但是私有权限的话,我们就只能在类内部定义(class 类的范围内去使用);那么私有权限的话我们怎么 把他们进行区分呢?
self.name = name ; # 公有 self.__age = age ; # 私有
只需要在属性名 或者 方法名 前边 加上两个 下划线,这个方法或者属性就变为私有。私有只能在当前类的内部使用,无法在其子类和外部使用。
4.2类属性和类方法
前面我们说过了,我们的类是一个特殊的对象,在程序运行时,类会被加载到内存中,在python里,类是个特殊的对象,可以叫类对象。既然它也能叫“对象”,那么它也有对应的类属性和类方法。我们也可以使用类名.类属性和类名.类方法()获取类属性和调用类方法。
类方法就是 针对 类对象 定义的方法,在类方法内部可以直接访问 类属性 或者调用其他的 类方法。这里的类方法,需要用装饰器 @clasmethod 来标识,类方法的第一个参数 应该是 cls
由哪一个类 调用的方法,方法内的 cls 就是 哪一个类的引用(有点类似参数 和实例方法 的第一个参数是self类似)。
在后期的自动化测试过程中,某些业务场景下,需要保证生成的对象始终只有一个时,就可以使用类对象及类属性和方法。(好处就是无需实例化对象,再从实例化的对象里去找实例属性和实例方法,简化很多流程),可以通过一个下面一个案例的演练来更进一步的了解。
我们能看到我们书写了一个 装饰器 @classmethod 来标识我们的类方法,我们在类方法里引用了我们的类属性count,这里我们其实已经在外面进行了类属性count的声明和赋值。
!!!我们一定要区分开,一个类里实例方法和实例属性是常用的,一般不增加我们的装饰器@classmethod的,并且在对应方法里的self.属性名=属性值的就是我们的实例属性和实例方法。实例属性除了在实例方法里传参,也可以用魔法方法__init__直接书写。
而我们的类属性通过访问就是使用cls.类属性名,不同于实例属性访问的self.实例属性名。
4.3静态方法(了解即可)
什么时候把方法定义成静态方法,就是在这个方法里不需要用到类属性也不用实例属性的时候,我们就可以用装饰器 @staticmethod 把它定义成 静态方法。
语法格式如下:
@staticmethod
def 方法名():
pass
我们调用静态方法的时候可以用类名.方法名(),也可以使用实例对象名.方法名()。
4.4综合案例-了解实例属性方法/类属性方法/静态方法
我们有这样的一个需求,设计一个Game游戏类。
属性:定义一个最高分top_score类属性,用来记录历史的最高分,定义一个player_name实例属性,记录当前游戏的玩家姓名。
方法:静态方法 show_help() 用来显示游戏帮助信息
类方法 show_top_score() 用来显示历史最高分
实例方法 start_time 用来开始当前玩家的游戏
具体思路:1.用随机函数 在10-100分之间生成一个分数作为本次得分,2.打印本次游戏得分,玩家xx 本次 游戏得分 xx, 3.和历史最高分进行比较,如果比历史最高分高,则修改历史最高分。
代码实现:
其实前面的思路已经说的很清楚,像不需要传参数的show_help()方法就可定义成我们的静态方法,以及声明我们的类属性top_score也是一样的,为此我们定义了对应的类方法,能够在我们这个类里去调用它。这里值得注意的点是前面强调过的,类属性用cls.类属性名调用,我们在外面的类方法调用则可以使用类名.方法名来进行调用,比如这里的Game.show_top_score()。
总结
这次主要复习了一些面向对象里的特点及一些细节的语法,我们对于面向对象继承有了更深的理解,通过不同的重写方法能够巧妙的调整类里的代码;对于细节的语法我们能够让面向对象里的类和属性以及其不同权限下的作用域都能有更清晰的认知,比起刚开始只知道主流的实例化类和实例化属性而言,通过对类方法类属性/静态方法对补充,同时也重新演示了__init__添加实例方法的魔法方法。最后一个有趣的案例也值得反复去琢磨和解析。