Python中的面向对象编程(抽象、封装、继承、多态)
继承
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类,被继承的class称为基类、父类或超类。
如果已经定义了Person类,那么在需要定义新的Student类时,就可以直接从Person类继承:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student1(Person):
pass
class Student2(Person):
def __init__(self, name, gender, score):
super(Student2, self).__init__(name, gender)
self.score = score
t1 = Student1('Alice', 'Female')
t2 = Student2('Alice', 'Female', '90')
print(t1.name)
print(t2.name)
print(t2.score)
>> Alice
>> Alice
>> 90
如上,如果子类Student1只想继承父类Person的属性,则直接:
class Student1(Person):
pass
就实现了,但如果像Student2似的不光要继承父类Person的属性又要定义额外的属性,则一定要用 super(Student, self).init(name, gender) 去初始化父类,否则,继承自 Person 的 Student 将没有 name 和 gender。
继承可以继承多层,最后所有的类都归根与 object 。
判断类型
当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:
a = list() # a是list类型
b = Person() # b是Person类型
c = Student() # c是Student类型
【isinstance()】可以判断一个变量的类型,既可以用在Python内置的数据类型如str、list、dict,也可以用在我们自定义的类:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
p = Person('Tim', 'Male')
s = Student('Alice', 'Female', 88)
print(isinstance(p, Person))
print(isinstance(p, Student))
print(isinstance(s, Person))
print(isinstance(s, Student))
>>> True # p是Person类型
>>> False # p不是Student类型
>>> True # s是Person类型
>>> True # s是Student类型
以上结果说明:在继承链上,一个父类的实例不能是子类类型,因为子类比父类多了一些属性和方法;但是子类的实例是父类类型,因为子类是继承自父类的,虽然比父类多一些属性与方法,但是子类的实例可以看成是父类的实例,很容易理解学生一定是人类,但是人类不一定都是学生啊,还可以是老师,医生,司机等等等。。。。
多态
那么,如果我们给子类添加了一个和父类一样的方法,则:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def job(self):
print('I am a Person')
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def job(self):
print('I am a Student')
p = Person('Tim', 'Male')
s = Student('Alice', 'Female', 88)
Person.job(p)
Student.job(s)
>> I am a Person
>> I am a Student
以上即说明了多态,当子类和父类都存在相同的 job() 方法时,子类的 job() 会覆盖父类的 job() ,在代码运行的时候,总是会调用子类的 job() 。
我们知道,多态的意思就是不同的子类对象调用相同的父类方法,可以产生不同的执行结果。但是多态到底有什么好处呢?接下来来看看下面的例子吧:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def job(self):
print('I am a Person')
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def job(self):
print('I am a Student')
def print_twice(Person): # 在之前程序的基础上增加一个打印两次的函数
Person.job()
Person.job()
print_twice(Person('Tim', 'Male'))
print_twice(Student('Alice', 'Female', 88))
>>> I am a Person
>>> I am a Person
>>> I am a Student
>>> I am a Student
由输出结果可以看出,当传入Person实例的时候就打印 “I am a Person” 两次,当传入 Student 实例时就打印 “I am a Student” 两次,接下俩我们重新定义一个 Teacher 类型也继承自 Person ,可见:
class Teacher(Person):
def __init__(self, name, gender):
super(Teacher, self).__init__(name, gender)
def job(self):
print('I am a Teacher')
print_twice(Teacher('Bob', 'Male'))
>>> I am a Teacher
>>> I am a Teacher
我们发现再次调用 print_twice() 并传入Teacher实例的时候打印的是 “I am a Teacher” 两次,我们得出结论:当新增一个子类时,是不必对run_twice()做任何修改,而且任何依赖 Person 作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态 。
多态的好处就是:调用方只管调用,不用管细节,当新增一种子类时,只要确保方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增子类;
对修改封闭:不需要修改依赖父类类型的函数(如:print_twice())。
静态语言和动态语言
由于Python是动态语言,所以,传递给函数 print_twice 的参数不一定是 Person 或 Person 的子类型。任何数据类型的实例都可以,只要它有一个 job() 的方法即可:
lass Clock(object):
def job(self):
print('I am a Clock')
这是动态语言和静态语言(例如Java)最大的差别之一。动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用。即为动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
获取对象信息
对于一个变量,除了用 isinstance() 判断它是否是某种类型的实例外,还可以用【 type()】和【dir() 】函数来获取变量的类型和所有属性,用【getattr()】和【setattr( )】来获取或设置一个已知名称的对象的属性,具体内容见慕课网课程,讲的很合我意,哈哈。
面向对象高级编程
多重继承
继承是面向对象编程的一个重要的方式,通过继承,子类可以扩展父类的功能。
比如如我们之前的例子,Student 和 Teacher 是继承于 Person 的两个类:
接下来我们想按照女教师,女学生,男教师,男学生来分类:
如果再增加分类的类型,那么层次将变得格外复杂,一点儿也不“智能”,这时就需要多重继承(即从多个父类继承)了!接下来我们通过多重继承来分类女教师,女学生,男教师,男学生:
class Person(object):
pass
class Student(Person):
def job(self):
return 'student'
class Teacher(Person):
def job(self):
return 'teacher'
class GenderMixin(object):
pass
class MaleMixin(GenderMixin): # Mixin的目的就是给一个类增加多个功能,这样在设计类的时候,优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。
def gender(self):
return 'male'
class FemaleMixin(GenderMixin):
def gender(self):
return 'female'
class MStudent(Student, MaleMixin):
pass
class FStudent(Student, FemaleMixin):
pass
class MTeacher(Teacher, MaleMixin):
pass
class FTeacher(Teacher, FemaleMixin):
pass
Tom = MStudent()
print(ms.job(), ms.gender())
Alice = FStudent()
print(fs.job(), fs.gender())
Bob = MTeacher()
print(mt.job(), mt.gender())
Kate = FTeacher()
print(ft.job(), ft.gender())
>>> student male
>>> student female
>>> teacher male
>>> teacher female
上面的程序显示,四个人按照男女、教师学生进行了分类,实现了多重继承。
Python自带的很多库也使用了MixIn,例如,TCPServer 和 UDPServer 这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由 ForkingMixIn 和 ThreadingMixIn 提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务:
class MyTCPServer(TCPServer, ForkingMixIn):
pass
编写一个多线程模式的UDP服务:
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
可见,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
1__slots__
对于Python这种动态语言,任何实例在运行期间都可以动态地添加属性。但是我们还是可以做到限制实例属性的,利用一个特殊的__slots__来实现:
class Person(object):
__slots__ = ('name', 'gender')
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
__slots__ = ('score',)
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
s = Student('Bob', 'male', 59)
s.name = 'Tim'
s.score = 99
print(s.score)
s.grade = 'A'
print(s.grade)
>>> 99
>>> AttributeError: 'Student' object has no attribute 'grade'
由上可见,‘grade’ 没有被放到__slots__中,所以不能绑定 grade 属性,试图绑定 grade 将得到AttributeError的错误。slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,而在子类中也定义__slots__的话,子类实例允许定义的属性就是自身的__slots__加上父类的__slots。
@property
懒的后果就是粘来链接
测试成如下:
# @property
class Screen(object):
@property
def width(self):
return self._width
@width.setter
def width(self, value):
self._width = value
@property
def height(self):
return self._height
@width.setter
def height(self, value):
self._height = value
@property
def resolution(self):
return self._width * self._height
# 测试:
s = Screen()
s.width = 1024
s.height = 768
print('resolution =', s.resolution)
if s.resolution == 786432:
print('测试通过!')
else:
print('测试失败!')
定制类
1__str__和__repr__
若想把一个类的实例编程str,那么用特殊方法 str() 就可以实现:
class Student(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return '(Person: %s, %s)' % (self.name, self.gender)
s = Student('Michael', 'male')
print(s)
>>> (Person: Michael, male)
但是,在交互命令行下直接敲变量p:
>>> p
<main.Person object at 0x10c941890>
这时__str_()好像不被调用,因为 Python 定义了__str__()和__repr__()两种方法,str()用于显示给用户,而__repr__()用于显示给开发人员。有一个偷懒的定义__repr__的方法:
class Student(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return '(Person: %s, %s)' % (self.name, self.gender)
__repr__ = __str__
2__iter__
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
for n in Fib():
print(n)
>>> 1
1
2
3
5
8
...
75025
3__getitem__和4__getattr__
简单了解.
5__call__
在这.
枚举法
枚举法.
元类
元类.
太久太久了,最近忙的要分身了。python的基础学习到这里告一段落了,下面就是实操一段时间,12月份开始转战c了。我一直都还在,没有放弃。