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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值