Python学习之路(36)——面向对象

封装

封装:将内容封装到某个地方,后续再去调用被封装在某处的内容。

1、将内容封装到某处

class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age

obj1 = Foo('nicolas', 18)
obj2 = Foo('xiaomi', 2018)

self是一个形参,当执行obj1 = Foo('nicolas', 18)时,self等于obj1。(obj2一样)

'nicolas'和18这2个内容被封装到了对象 obj1 和 obj2 中,每个对象都有name属性和age属性。

2、从某处调用被封装的内容

调用被封装内容有2种方式:

1)通过对象直接调用

class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age

obj1 = Foo('nicolas', 18)
print(obj1.name)
print(obj1.age)

######执行结果######
nicolas
18

2)通过self间接调用(执行类中的方法时,需要通过self间接调用被封装的内容)

class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def detail(self):
        print(self.name)
        print(self.age)

obj1 = Foo('nicolas', 18)
obj1.detail()


######执行结果######
nicolas
18

综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或self间接获取被封装的内容。

 

继承

继承:面向对象中的继承和现实生活中的继承相同,即子可以继承父的内容。

class Animal:
    def eat(self):
        print("%s eat" % self.name)

    def drink(self):
        print("%s drink" % self.name)

 
class Cat(Animal):
    def __init__(self, name):
        self.name = name
        self.breed = '猫'

    def cry(self):
        print('喵喵叫')

class Dog(Animal):
    def __init__(self, name):
        self.name = name
        self.breed = '狗'

    def cry(self):
        print('汪汪叫')

c1 = Cat('小白猫')
c2 = Cat('小黑猫')
d1 = Dog('大黄狗')

c1.eat()
c2.drink()
d1.eat()
c1.cry()
d1.cry()


######执行结果######
小白猫 eat
小黑猫 drink
大黄狗 eat
喵喵叫
汪汪叫

对于面向对象的继承来说,其实就是讲多个类共有的方法提取到父类(基类)中,子类(派生类)仅需继承父类而不必一一实现每个方法。

 

多重继承

Python的类可以继承多个类,如果继承的多个类中每个类都定义了相同的函数,那么子类会继承哪一个类的函数呢?寻找的方法有两种:深度优先和广度优先

1)当类为经典类时,多重继承情况下,会按照深度优先方式查找

2)当类为新式类时,多重继承情况下,会按照广度优先方式查找

(注意:Python 2.x中默认都是经典类,只有显示继承了object才是新式类;Python 3.x中默认都是新式类,不必显式的继承object)

Python 2.x示例(经典类):

class D:
    def bar(self):
        print('D.bar')

class C(D):
    def bar(self):
        print('C.bar')

class B(D):
    def bar1(self):
        print('B.bar')

class A(B, C):
    def bar1(self):
        print('A.bar')

a = A()
a.bar()

######运行结果######
C:\Python27\python.exe D:/Project/Python/Pro_py2/test.py
D.bar

Process finished with exit code 0

Python 2.x示例(新式类):

class D(object):
    def bar(self):
        print('D.bar')

class C(D):
    def bar(self):
        print('C.bar')

class B(D):
    def bar1(self):
        print('B.bar')

class A(B, C):
    def bar1(self):
        print('A.bar')

a = A()
a.bar()

######运行结果######
C:\Python27\python.exe D:/Project/Python/Pro_py2/test.py
C.bar

Process finished with exit code 0

(注意:如果是在Python 3.x,上面2个代码的运行结果都是C.bar)

经典类:首先去A类中查找,如果A类中没有,则依次去B类->D类->C类中找,如果都没有找到则报错。

新式类:首先去A类中查找,如果A类中没有,则依次去B类->C类->D类中找,如果都没有找到则报错。

 

多态

实际上Python不支持多态,并且也用不到多态,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”(dyck typing)。

鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。

示例:

class AudioFile:
    def __init__(self, filename):
        if not filename.endswith(self.ext):
            raise Exception("Invalid file format")
        self.filename = filename

class MP3File(AudioFile):
    ext = "mp3"
    def play(self):
        print("Playing {} as mp3".format(self.filename))

class WavFile(AudioFile):
    ext = "wav"
    def play(self):
        print("Playing {} as wav".format(self.filename))

class OggFile(AudioFile):
    ext = "ogg"
    def play(self):
        print("Playing {} as ogg".format(self.filename))

class FlacFile:
    """
    Though FlacFile class doesn't inherit AudioFile class,
    it also has the same interface as three subclass of AudioFile.

    It is called duck typing.
    """
    def __init__(self, filename):
        if not filename.endswith(".flac"):
            raise Exception("Invalid file format")
        self.filename = filename

    def play(self):
        print("Playing {} as flac".format(self.filename))
上面的代码中,MP3FileWavFileOggFile三个类型继承了AudioFile这一积累,而FlacFile没有扩展AudioFile,但是可以在python中使用完全相同的接口与之交互。
因为任何提供正确接口的对象都可以在python中交替使用,它减少了多态的一般超类的需求。继承仍然可以用来共享代码,但是如果所有被共享的都是公共接口,鸭子类型就是所有所需的。这减少了继承的需要,同时也减少了多重继承的需要;通常,当多重继承似乎是一个有效方案的时候,我们只需要使用鸭子类型去模拟多个超类之一(定义和那个超类一样的接口和实现)就可以了。

posted on 2018-03-16 18:34  nicolas_Z  阅读(253)  评论(0)    收藏  举报

导航