面向对象编程是一种程序设计思想。
咱们到目前为止所编写的程序,都是基于面向过程
的。python
面向过程就是咱们在写程序过程当中,将需求拆成多个流程
,每一个封装一个方法
,逐个调用。
好比买菜、洗菜、炒菜、装盘这四个步骤,咱们对于的方法就是buy
、wash
、cook
、dish
。
若是需求有变化,如我还要买米、作饭,用铁锅炒菜,用微波炉作菜等等。这时候用面向过程就很是麻烦且会愈来愈乱。git
面向对象则是从另外一个角度来思考,它用对象
来取代方法做为程序的基础
。
对象是对事物的抽象
,如人能够抽象成名字、年龄以及能作的事情,这就是类
。
咱们在程序设计时,就只要建立这个对象,让它来作它能作的事情
。
仍是买菜、洗菜、炒菜、装盘这个四个步骤,如今咱们就能够有超市
、厨师
两个对象。超市负责买菜,厨师负责洗菜、炒菜、装盘。
进一步厨师用什么洗菜,用什么作菜怎么作、怎么装盘又是一系列的对象来处理。
超市这个对象提供买米、买菜、买肉等等功能,必要时还能够继续添加
。
除了基本的功能外,沃尔玛和永辉卖的东西就有差异,即便一样是卖米也会有差别。
这就是继承,沃尔玛和永辉都是超市的继承,但并非相同的。github
类【class
】就是对对象的描述
。
咱们要把人的名字和年龄以及作的事情用python的类写出来。编程
建立一个类:class
。
咱们建立一个user包,在包里建立一个Person模块。在模块中编写Person类。框架
class Person: def __init__(self): pass
class后面就是类名Person,其下的代码块就是类的定义。类名使用驼峰写法
。一个文件一个类
,大部分状况咱们会这样作。Python中并不反对一个文件多个类
。
第一个在类中要定义的方法就是构造方法
,方法名为__init__
。这是类默认存在
的方法。
构造方法里的第一个参数是self
,表明对象本身
。默认
类的方法第一个参数
都是self
。单元测试
实例化:将类建立出来进行使用。
咱们建立一个main.py来实例化这个类。测试
from user import Person p1 = Person.Person()
from user.Person import Person p1 = Person()
上面两种方式是在包下的类实例化方式,与方法的调用时同样的。
对于类,因为一文件一类的原则,咱们更但愿能简单一点调用到包里的类。以下:spa
from user import Person p1 = Person() p2 = Person()
那么咱们就要在包的__init__.py
文件里作文章了,写入设计
from .Person import Person
而后咱们就能够直接从包导入Person类了。3d
构造方法:用于初始化
,每一个类被实例化时会默认调用的方法。
def __init__(self, name, age): self.name = name # 名字 self.age = age # 年龄
咱们加入Person类有的数据,名字和年龄。self
表明本身,它点
出来的变量
称为成员变量
。self.name
和self.age
分别被赋值给构造方法传入的参数name和age,用于初始化
。
实例化并获取成员变量:
p1 = Person('Green', 12) print(p1.name, p1.age)
使用构造方法进行建立,而后直接用建立好的变量,点出成员变量便可。
成员变量原则上
只能在构造方法
中声明
。
成员方法:构造方法实际上是一个特殊的成员方法。
def sleep(self, t): print('{} sleeps for {} seconds'.format(self.name, t))
定义了一个sleep方法,一样的第一个参数
是self
,表明该方法是成员方法。
在成员方法内部,咱们就能够用self
来得到成员变量
。调用
:一样使用点
的方式调用该方法
。
p2 = Person('Lucy', 14) p2.sleep(10)
这时若是咱们须要在sleep前添加一步到床上的步骤,咱们能够这样作,
添加一个go2bed成员方法,并在sleep方法中用self
调用这个成员方法
。
class Person: def __init__(self, name, age): self.name = name # 名字 self.age = age # 年龄 def sleep(self, t): self.go2bed() print('{} sleeps for {} seconds.'.format(self.name, t)) def go2bed(self): print(self.name + ' go to bed.')
访问限制:对成员变量和成员方法进行限制,不让使用者能够直接使用。成员变量
做为一个类的数据,不该该能够直接被使用者修改,保证类被正确使用。
变量名用 两个下划线
【__
】开头的就是被限制访问
的私有变量
,只可内部访问
。
def __init__(self, name, age): self.__name = name # 名字 self.__age = age # 年龄
调用时报错
若是须要获取这两个变量的值,或进行修改,咱们定义get
,set
方法进行。
def get_age(self): return self.__age def set_age(self, age): self.__age = age def get_name(self): return self.__name
咱们但愿age能够进行设置,而name没有set方法。这样成员变量的任何修改都在咱们的控制范围内。
成员方法也使用两个下划线
来作限制。__go2bed
就是只能内部访问
的方法了。
class Person: def __init__(self, name, age): self.__name = name # 名字 self.__age = age # 年龄 def sleep(self, t): self.__go2bed() print('{} sleeps for {} seconds.'.format(self.__name, t)) def __go2bed(self): print(self.__name + ' go to bed.') def get_name(self): return self.__name def get_age(self): return self.__age
主要目的是:
一、不但愿使用者调用到。
二、对用户提供的方法也能够叫作接口
。接口就是使用者能够调用到的全部方法了。这样对于使用者来讲也是最好的。
继承是在已有类【父类
】的基础上,再编写一个子类
,其拥有父类的全部内容。
from user import Person class Student(Person): pass
在类名Student后面用括号抱起来的Person就是父类。
此时这个子类没有任何的代码,可是它已经继承了父类全部的内容,使用方法同样。
from user import Student student1 = Student('Green', 12) print(student1.get_name())
全部方法变量自动继承。
object类:默认的Python继承类。
没有作继承的类,其实都默认继承
了python的object类
【对象类】。
class Person(object):
object能够不写。
添加成员变量和方法:子类通常拥有比父类更强大的功能。
在Students类中,咱们添加一个grade变量来标识年级,并添加一个新方法。
class Student(Person): def __init__(self, name, age, grade=1): Person.__init__(self, name, age) # 父类构造方法 self.__grade = grade # 年级 def get_grade(self): return self.__grade
代码中的Person.__init__
表示了咱们在新的构造方法中先调用了Person父类
的构造方法
。
这样咱们就减小了重复的代码。
复写成员方法:修改
父类的方法
内容。
方法的复写必须保证方法名
和参数
与父类一致
。
def sleep(self, t): print('student ' + self.get_name()) Person.sleep(self, t) # 父类方法
经过调用父类的方法来扩充该方法。或者直接不调用父类方法所有重写也是能够的。
def sleep(self, t): print('student {} sleeps for {} seconds.'.format(self.__name, t))
子类
的方法覆盖
了父类
的方法,在被调用时就会运行子类方法。
复写实现了面向对象中多态
的概念。
此时咱们发现代码报错
了,缘由在于__name
变量是只能Person类内部访问
的成员变量。
只能使用self.get_name()
来得到名字,子类要如何调用父类的私有变量?
继承中的访问限制:
两个下划线使得该成员变量和方法只能在这个类中内部使用。
但在继承中
,成员变量和方法
除了不能被外部调用外,其子类
应该是能够正常使用
的。
使用一个下划线
【_
】开头定义的成员变量和方法就能够实现。
def __init__(self, name, age): self._name = name # 名字 self._age = age # 年龄 def _go2bed(self): print(self._name + ' go to bed.')
这样刚才的复写方法中就可使用了。
def sleep(self, t): print('student {} sleeps for {} seconds.'.format(self._name, t))
访问限制分类: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 # False type(s1) == Student # True isinstance(s1, Person) # True isinstance(s1, Student) # True
咱们发现子类变量在type的相等判断中,并不等于父类。
但在isinstance
判断中,子类被判断为与父类同样,这就是多态
。
一个类能够是本身,也能够是其父类,逻辑上也是通的,学生也是一我的。
反之则不对,一我的未必是学生。
多态的用处:
def poly_test(person): if not isinstance(person, Person): raise Exception('数据类型传入错误') return person.sleep(10)
这个方法里,咱们对传入的类型进行了限定。
这样咱们就能够直接传入Student、Person或者其余子类均可以。
sleep也是父类就有方法,直接调用不会有问题。
当咱们新增一个子类,如Programmer类,咱们就不须要对这个方法进行修改,直接使用。
根据对象的真实类型
来运行
,就是多态的用处。
除了成员变量和成员方法,类中还能够定义静态属性
:类变量
和类方法
。静态
的意思是不须要实例化
就可使用。
类变量的数据属于全部实例共享
一个。
直接建立在类的定义中与方法同级。调用时使用类名 . 变量名
。
class Programmer(Person): programmers = {} # 静态变量 def __init__(self, name, age): Person.__init__(self, name, age) if name not in Programmer.programmers: Programmer.programmers[name] = 0 Programmer.programmers[name] += 1
这里作了一个统计,统计同一姓名的人有几个。每建立一个实例就统计一次。
from user import Programmer p = Programmer('Green', 18) print(Programmer.programmers)
类方法是直接属于类的方法。
定义时,与成员方法不一样的是第一个参数不是self而是cls
【这个类】。并须要在方法上添加一个装饰器
。
@classmethod def count_by_name(cls, name): return cls.programmers[name]
以@开头
并写在def上面一行的就是装饰器
,具体内容之后的高阶语法再介绍。@classmethod
就定义了下面
的方法是一个类方法
,且没有self参数,由于没有实例化。
第一参数是cls
,表明了当前类
,能够经过cls调用类变量,至关于Programmer.programmers
。
使用时直接经过类名 . 方法
调用,无需实例化
。
count = Programmer.count_by_name('Green')
静态方法与类方法相似,但更为独立
,仅仅表现为放在类里面托管的方法。
定义时,没有self参数
,也没有cls参数
。下面是上个方法的静态方法版本。
@staticmethod def count_by_name(name): return Programmer.programmers[name]
@staticmethod
就定义了下面的方法是一个静态方法
。其余都与类方法同样。
使用上,静态方法通常与类没有什么关系,不会涉及类的操做,是一个比较独立的方法。
继承问题:类变量与子类共享,父类没有该类变量。
类方法一样能够继承和复写,有复写时要调用这个类的类方法。
静态方法也同样。
单元测试是一个测试框架
。
咱们直接来看一个简单的示例代码。
import unittest # 单元测试框架 from user import Student # 要测试的类 class TestStudentMethods(unittest.TestCase): def test_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语句判断了该文件是否启动文件
,也是一种经常使用的方法。
启动后,单元测试会自动执行全部用例
,并给出结果。
测试用例:
def test_student(self): # 测试用例 s = Student('Green', 12) self.assertEqual(12, s.get_age()) self.assertEqual('Green', s.get_name())
先进行一些操做代码,就是咱们的用例过程,这里建立了一个Student对象。
而后就要使用单元测试类给咱们提供的assert系列方法
来判断结果是否正确。assertEqual
是最经常使用的方法之一了,参数1写入指望值
,参数2写入要验证的数据
。
在运行过程当中就会进行对比,若是二者相等则正确,不等则错误。
经常使用的assert方法还有:assertNotEqual
、assertTrue
、assertFalse
、assertIsNone
、assertIsNotNone
、assertIn
等。
一个单元测试能够包含不少测试用例,一次进行测试。
测试结果:全部assert都正确的状况下,即测试经过。
有没有经过的用例时,会告诉你哪一个用例的哪一个assert有问题,与指望值不符。
一、模拟一个小游戏,编写一个Sprite类【拥有血量和攻击力两个属性,并进行攻击】。
而后继承出Monster类和Hero类,让二者每回合互相攻击一次,直到有一方死亡。
每次攻击扣除对方的血量是攻击力加减N的随机值。
二、编写一个进行接口请求的单元测试。