Python中的面向对象编程(下)

mac2026-08-14  6

Python中的面向对象编程(抽象、封装、继承、多态)

继承判断类型多态静态语言和动态语言获取对象信息面向对象高级编程多重继承1__slots__@property定制类1__str__和__repr__2__iter__3__getitem__和4__getattr__5__call__ 枚举法元类

继承

在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 的两个类:

Person Teacher Student

接下来我们想按照女教师,女学生,男教师,男学生来分类:

Person Teacher Student Female Male Female Male

如果再增加分类的类型,那么层次将变得格外复杂,一点儿也不“智能”,这时就需要多重继承(即从多个父类继承)了!接下来我们通过多重继承来分类女教师,女学生,男教师,男学生:

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了。我一直都还在,没有放弃。

最新回复(0)