概述面向对象类继承多态静态单元测试实践面向对象面向对象编程是一种编程思想。到目前为止,我们编写的程序都是面向过程的。面向过程就是在写程序的过程中,我们把需求拆分成多个过程,每个过程封装一个方法,一个一个调用。比如买菜、洗菜、煮菜、上菜这四个步骤,我们对它们的方法是买、洗、煮、上菜。如果需求发生变化,比如我还需要买米,做饭,铁锅煮饭,微波炉煮饭等等。这时候使用面向过程就很麻烦,而且会越来越乱。面向对象是从另一个角度思考,它使用对象而不是方法作为程序的基础。对象是对事物的抽象,比如人可以抽象为名字、年龄、可以做的事情。这是一个类。我们在编程的时候,只需要创建这个对象,让它做它能做的事情就可以了。依旧是买菜、洗菜、炒菜、上菜四个步骤。现在我们可以有两个对象:超市和厨师。超市负责买菜,厨师负责洗菜、炒菜、上菜。更进一步,厨师用什么洗碗,怎么煮菜,怎么上菜,是一系列要处理的对象。超市的对象提供购买米饭、蔬菜、肉类等功能,有需要可以继续添加。除了基本功能,沃尔玛和永辉卖的东西也有区别,即使是卖大米。这就是继承。沃尔玛和永辉都是超市的传承,但又不尽相同。类[class]是对对象的描述。我们需要在python中写下这个人的名字和年龄以及他们做什么。创建类:类。我们创建一个用户包,并在包中创建一个Person模块。在模块中编写Person类。classPerson:def__init__(self):passclass后面是类名Person,下面的代码块就是类的定义。类名使用驼峰式大小写。每个文件一个类,在大多数情况下我们会这样做。Python中不反对一个文件中有多个类。在类中定义的第一个方法是构造函数,名为__init__。这是类默认存在的方法。构造函数中的第一个参数是self,代表对象本身。默认类的方法的第一个参数是self。实例化:创建一个类来使用。我们创建一个main.py来实例化这个类。fromuserimportPersonp1=Person.Person()fromuser.PersonimportPersonp1=Person()以上两个方法是封装下的类实例化方法,与方法调用相同。对于类,由于一文件一类的原则,我们更希望能够更方便地调用包中的类。如下:fromuserimportPersonp1=Person()p2=Person()那么我们就在包的__init__.py文件中做文章,写from.PersonimportPerson然后就可以直接从包中导入Person类了.构造方法:用于初始化,每个类实例化时默认会调用的方法。def__init__(self,name,age):self.name=name#nameself.age=age#age我们将数据、姓名和年龄添加到Person类。self代表它自己,它指向的变量称为成员变量。self.name和self.age分别赋值给构造函数传入的参数name和age进行初始化。实例化并获取成员变量:p1=Person('Green',12)print(p1.name,p1.age)使用构造方法创建,然后直接使用创建的变量点击成员变量。原则上,成员变量只能在构造函数中声明。成员方法:构造函数实际上是一种特殊的成员方法。defsleep(self,t):print('{}sleepsfor{}seconds'.format(self.name,t))定义了一个sleep方法,同样第一个参数是self,表示该方法是一个成员方法。在成员方法内部,我们可以使用self来获取成员变量。调用:也可以使用点来调用该方法。p2=Person('Lucy',14)p2.sleep(10)这时候如果我们需要在sleep之前给bed增加一个台阶,可以通过增加一个go2bed成员方法,在睡眠方法这个成员方法。classPerson:def__init__(self,name,age):self.name=name#nameself.age=age#agedefsleep(self,t):self.go2bed()print('{}睡{}秒.'.format(self.name,t))defgo2bed(self):print(self.name+'gotobed.')访问限制:限制成员变量和成员方法,防止用户直接使用。作为类的数据,成员变量不应该被用户直接修改,以保证类的正确使用。两个下划线[__]开头的变量名是访问受限的私有变量,只能在内部访问。def__init__(self,name,age):self.__name=name#nameself.__age=age#age调用出错如果需要获取这两个变量的值,或者修改它们,我们定义get和设置方法。defget_age(self):returnself.__agedefset_age(self,age):self.__age=agedefget_name(self):returnself.__name我们希望age可以设置,name没有设置方法。对此类成员变量的任何修改都在我们的控制范围内。成员方法也受到两个下划线的限制。__go2bed是一种只能在内部访问的方法。classPerson:def__init__(self,name,age):self.__name=name#nameself.__age=age#agedefsleep(self,t):self.__go2bed()print('{}睡{}秒.'.format(self.__name,t))def__go2bed(self):print(self.__name+'gotobed.')defget_name(self):returnself.__namedefget_age(self):returnself.__age的主要目的是:1.不想让用户调用它。2、提供给用户的方法也可以称为接口。接口是用户可以调用的所有方法。这对用户来说也是最好的。继承继承就是在已有的类【父类】的基础上写一个子类,它拥有父类的所有内容。fromuserimportPersonclassStudent(Person):通过类名Student后面括号中的Person为父类。此时这个子类没有任何代码,但是继承了父类的所有内容,使用方法也是一样的。fromuserimportStudentsstudent1=Student('Green',12)print(student1.get_name())自动继承所有方法变量。对象类:默认的Python继承类。不继承的类,其实默认继承python的对象类[objectclass]。classPerson(object):对象不可写。添加成员变量和方法:子类一般比父类有更强大的功能。在Students类中,我们添加了一个成绩变量来标识成绩和一个新方法。classStudent(Person):def__init__(self,name,age,grade=1):Person.__init__(self,name,age)#父类构造方法self.__grade=grade#gradedefget_grade(self):returnself.__grade代码中的Person.__init__表示我们先在new构造函数中调用Person父类的构造函数。这样我们就减少了代码的重复。重写成员方法:修改父类的方法内容。方法的重写必须保证方法名和参数与父类一致。defsleep(self,t):print('student'+self.get_name())Person.sleep(self,t)#父类方法通过调用父类的方法来扩展这个方法。或者也可以不直接调用父类,重写所有的方法。defsleep(self,t):print('student{}sleepfor{}seconds.'.format(self.__name,t))子类的方法覆盖父类的方法,当子类运行时它被称为类方法。复制实现了面向对象中多态的概念。这时我们发现代码报错是因为__name变量是一个成员变量,只能在Person类内部访问。您只能使用self.get_name()来获取名称。子类怎么调用父类的私有变量呢?继承中的访问限制:两条下划线使得成员变量和方法只能在本类内部使用。但是在继承中,除了成员变量和方法不能被外部调用外,它们的子类应该是可以正常使用的。可以使用定义在下划线[_]开头的成员变量和方法来实现。def__init__(self,name,age):self._name=name#nameself._age=age#agedef_go2bed(self):print(self._name+'gotobed.')这样,在副本中方法刚刚准备好使用。defsleep(self,t):print('student{}sleepfor{}seconds.'.format(self._name,t))accessrestrictioncategory:public:公共权限,无下划线[self.name],任何地方都可以被访问以进行修改。protected:受保护的权限,单下划线[self._name],只允许自己及其子类访问和修改。private:私有权限,双下划线[self.__name],只允许自己访问和修改。根据我们的需求,选择相应的权限进行开发。多态判断变量类型:type,isinstance。p1=Person('Green',12)s1=Student('Green',12)print(type(s1))print(type(p1))输出:type可以得到变量的类型。print(isinstance(p1,Person))print(isinstance(p1,Student))输出:isinstance可以直接判断变量是否是某种类型。对于父类变量,结果与以下代码相同:print(type(p1)==Person)print(type(p1)==Student)但对于子类,情况就不一样了。子类类型判断:type(s1)==Person#Falsetype(s1)==Student#Trueisinstance(s1,Person)#Trueisinstance(s1,Student)#True我们发现子类变量在类型的相等判断中,和不等于父类。但是在isinstance判断中判断子类和父类是一样的,这就是多态。一个类可以是它自己,也可以是它的父类,逻辑上是相连的,学生也是一个人。反之则不然,一个人不一定是学生。多态的用处:defpoly_test(person):ifnotisinstance(person,Person):raiseException('datatypeinputerror')returnperson.sleep(10)在这个方法中,我们执行的是有限的。这样我们就可以直接传入Student、Person或者其他子类了。sleep也是父类中的一个方法,直接调用是不会有问题的。当我们添加一个新的子类时,比如Programmer类,我们不需要修改这个方法,直接使用即可。按照对象的真实类型运行,这就是多态的用处。Static除了成员变量和成员方法,类还可以定义静态属性:类变量和类方法。静态的意思是不用实例化就可以使用。类变量数据属于所有实例共享一个。直接在与类定义中的方法相同的级别创建。使用类名。调用时的变量名。classProgrammer(Person):programmers={}#静态变量def__init__(self,name,age):Person.__init__(self,name,age)ifnamenotinProgrammer.programmers:Programmer.programmers[name]=0Programmer.programmers[name]+=1这里做了一个统计,统计有多少人有相同的名字。每次创建实例时计数。fromuserimportProgrammerp=Programmer('Green',18)print(Programmer.programmers)类方法是直接属于类的方法。定义时,与成员方法不同的是第一个参数不是self而是cls[thisclass]。并且需要在方法上添加装饰器。@classmethoddefcount_by_name(cls,name):returncls.programmers[name]装饰器以@开头,写在def上面一行。具体内容会在后面的高级语法中介绍。@classmethod定义后面的方法是类方法,没有self参数,因为没有实例化。第一个参数是cls,代表当前类,通过cls可以调用类变量,相当于Programmer.programmers。使用时直接通过类名调用。没有实例化的方法。count=Programmer.count_by_name('Green')静态方法类似于类方法,但是更加独立,在类中只作为托管方法出现。定义时,没有self参数,也没有cls参数。下面是上一个方法的静态方法版本。@staticmethoddefcount_by_name(name):returnProgrammer.programmers[name]@staticmethod将以下方法定义为静态方法。其他一切都与类方法相同。在使用中,静态方法一般与类无关,不涉及类的操作。是一种相对独立的方法。继承问题:类变量是子类共享的,父类没有这样的类变量。类方法也可以被继承和复制,当有复制时,应该调用本类的类方法。静态方法也是如此。单元测试单元测试是一个测试框架。让我们直接看一个简单的示例代码。importunittest#UnittestframeworkfromuserimportStudent#ClasstotestclassTestStudentMethods(unittest.TestCase):deftest_student(self):#测试用例s=Student('Green',12)self.assertEqual(12,s.get_age())self.assertEqual('Green',s.get_name())if__name__=='__main__':#当用作启动文件时unittest.main()unittest是要导入的包。创建一个扩展unittest.TestCase类的类。那么就不需要其他方法了。我们直接开始编写测试用例。以test开头的成员方法是一个用例。定义好类后,我们开始测试unittest.main()。前面的if语句判断文件是否启动文件,也是常用的方法。启动后,单元测试会自动执行所有用例并给出结果。测试用例:deftest_student(self):#测试用例s=Student('Green',12)self.assertEqual(12,s.get_age())self.assertEqual('Green',s.get_name())firstSome操作代码是我们的用例流程,其中创建了一个Student对象。那么我们就需要使用单元测试类提供的assert系列方法来判断结果是否正确。assertEqual是最常用的方法之一。参数1写入期望值,参数2写入待验证数据。运行时会进行比较,两者相等则正确,不相等则错误。常用的断言方法包括:assertNotEqual、assertTrue、assertFalse、assertIsNone、assertIsNotNone、assertIn等。一个单元测试可以包含多个测试用例,一次一个测试。测试结果:当所有断言都正确时,测试通过。当有一个用例没有通过时,它会告诉你哪个用例的哪个断言有问题,哪个不符合预期值。习题1.模拟一个小游戏,写一个Sprite类【有血量和攻击力,攻击两个属性】。然后继承怪物类和英雄类,让两者每一轮互相攻击一次,直到其中一个死亡。每次攻击扣掉对方血量,血量是攻击力正负N的随机值。2.接口请求写单元测试。github:https://github.com/lvancer/course_python
