Python当歌(十):面向对象(基础篇)

概述

(1)面向过程:根据业务逻辑从上到下写垒代码

(2)函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可

(3)面向对象:对函数进行分类和封装,让开发“更快更好更强...”

面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,即:将之前实现的代码块复制到现需功能处。

while True:
    if cpu利用率 > 90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接

    if 硬盘使用空间 > 90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接

    if 内存占用 > 80%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接

随着时间的推移,开始使用了函数式编程,增强代码的重用性和可读性,就变成了这样:

def 发送邮件(内容)
    #发送邮件提醒
    连接邮箱服务器
    发送邮件
    关闭连接

while True:

    if cpu利用率 > 90%:
        发送邮件('CPU报警')

    if 硬盘使用空间 > 90%:
        发送邮件('硬盘报警')

    if 内存占用 > 80%:
        发送邮件('内存报警')

创建类和对象

面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。

类就是一个模板,模板里可以包含多个函数,函数里实现一些功能

对象则是根据模板创建的实例,通过实例对象可以执行类中的函数

10-1

(1)class是关键字,表示类

(2)创建对象,类名称后加括号即可

ps:类中的函数第一个参数必须是self(详细见:类的三大特性之封装)
   类中定义的函数叫做 “方法”

# 创建类
class Foo:

    def Bar(self):
        print 'Bar'

    def Hello(self, name):
        print 'i am %s' %name

# 根据类Foo创建对象obj
obj = Foo()
obj.Bar()            #执行Bar方法
obj.Hello(‘Pan') #执行Hello方法 

面向对象三大特性

面向对象的三大特性是指:封装、继承和多态。

封装

封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。要明确的区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用,类中定义私有的,只在类的内部使用,外部无法访问。所以,在使用面向对象的封装特性时,需要:

  • 将内容封装到某处
  • 从某处调用被封装的内容

第一步:将内容封装到某处

10-2

self 是一个形式参数,当执行 obj1 = Foo('Pan', 18 ) 时,self 等于 obj1;

当执行 obj2 = Foo('xwq', 78 ) 时,self 等于 obj2

所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,在内存里类似于下图来保存。

10-3

第二步:从某处调用被封装的内容

调用被封装的内容时,有两种情况:

  • 通过对象直接调用
  • 通过self间接调用

(1)通过对象直接调用被封装的内容

上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名

class Foo:

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

obj1 = Foo('Pan', 18)
print obj1.name    # 直接调用obj1对象的name属性
print obj1.age     # 直接调用obj1对象的age属性

obj2 = Foo('xwq', 19)
print obj2.name    # 直接调用obj2对象的name属性
print obj2.age     # 直接调用obj2对象的age属性

(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('Pan', 18)
obj1.detail()  # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 Pan ;self.age 是 18

obj2 = Foo('xwq', 73)
obj2.detail()  # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 xwq ; self.age 是 78

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

(3)如何隐藏类的内部属性

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的),其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形,类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:

class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.

 #A._A__N是可以访问到的,这种,在外部是无法通过__x这个名字访问到。

这种变形需要注意的问题是:

1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。

2.变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形

3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

10-4

(4)封装不是单纯意义的隐藏

1:封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。

class Teacher:
    def __init__(self,name,age):
        # self.__name=name
        # self.__age=age
        self.set_info(name,age)

    def tell_info(self):
        print('姓名:%s,年龄:%s' %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError('姓名必须是字符串类型')
        if not isinstance(age,int):
            raise TypeError('年龄必须是整型')
        self.__name=name
        self.__age=age

t=Teacher('egon',18)
t.tell_info()

t.set_info('egon',19)
t.tell_info()

2:封装方法:目的是隔离复杂度

取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱。对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做。隔离了复杂度,同时也提升了安全性

class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输入取款金额')
    def __print_bill(self):
        print('打印账单')
    def __take_money(self):
        print('取款')

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

(5)封装与扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
        return self.__width * self.__length

#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area

#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了

    return self.__width * self.__length * self.__high

#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area()

继承

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

例如:
  
  猫可以:喵喵叫、吃、喝、拉、撒

狗可以:汪汪叫、吃、喝、拉、撒

如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,如下所示:

(伪代码)
class cat:

    def cat_mews(self):
        print (‘喵喵叫’)

    def eat(self):
        # do something

    def drink(self):
        # do something

    def pull(self):
        # do something

    def cast(self):
        # do something

class dog:

    def bark(self):
        print (‘汪汪叫’)

    def eat(self):
        # do something

    def drink(self):
        # do something

    def pull(self):
        # do something

    def cast(self):
        # do something

上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:

  动物:吃、喝、拉、撒

   猫:喵喵叫(猫继承动物的功能)

   狗:汪汪叫(狗继承动物的功能)

(伪代码)
class animal:

    def eat(self):
        # do something

    def drink(self):
        # do something

    def pull(self):
        # do something

    def cast(self):
        # do something

# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类

class cat(animal):

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

# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class dog(animal):

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

(代码实现)
class Animal:
    def eat(self):
        print('%s eat'%(self.name))

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

    def shit(self):
        print('%s pull' %(self.name))

    def pee(self):
        print('%s cast' %(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('小白家的小黑猫')
c1.eat()

c2 = Cat('小黑的小白猫')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()

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

注:除了子类和父类的称谓,你可能看到过 派生类 和 基类 ,他们与子类和父类只是叫法不同而已。

10-5

那么问题又来了,多继承呢?

是否可以继承多个类

如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?

1、Python的类可以继承多个类,Java和C#中则只能继承一个类

2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先

10-6

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

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

经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。

10-7

10-8

(经典类多继承)
class D:

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

class C(D):

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

class B(D):

def bar(self):
    print ('B.bar')

class A(B, C):

def bar(self):
    print ('A.bar')

a = A()

执行bar方法时首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错,所以,查找顺序:A --> B --> D --> C。在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了a.bar()

(新式类多继承)
class D(object):

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

class C(D):

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

class B(D):

    def bar(self):
        print ('B.bar')

class A(B, C):

    def bar(self):
        print ('A.bar')

a = A()

执行bar方法时,首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错,所以,查找顺序:A --> B --> C --> D,在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了a.bar()

经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错

新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错

注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了

多态

类的继承有两层意义:1、改变 2、扩展

多态就是类的这两层意义的一个具体的实现机制。即调用不同的类实例化的对象下的相同的方法,实现的过程不一样。python中的标准类型就是多态概念的一个很好的示范。

多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需要考虑他们的具体的类。多态一定是反应在运行时的状态。

(1)理解多态

多态指的是一类事物有多种形态

动物有多种形态:人,狗,猪

import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

文件有多种形态:文本文件,可执行文件

import abc
class File(metaclass=abc.ABCMeta): #同一类事物:文件
    @abc.abstractmethod
    def click(self):
        pass

class Text(File): #文件的形态之一:文本文件
    def click(self):
        print('open file')

class ExeFile(File): #文件的形态之二:可执行文件
    def click(self):
        print('execute file')

(2)多态性

多态性是指在不考虑实例类型的情况下使用实例

在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同。

多态性分为静态多态性和动态多态性

静态多态性:如任何类型都可以用运算符+进行运算

动态多态性:如下

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()

(3)为什么要使用多态性

1.增加了程序的灵活性

以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

2.增加了程序额可扩展性

通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用

class Cat(Animal): #属于动物的另外一种形态:猫
    def talk(self):
        print('say miao')

def func(animal): #对于使用者来说,自己的代码根本无需改动
    animal.talk()

cat1=Cat() #实例出一只猫
func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能

这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)

|| 版权声明
作者:废权
链接:https://blog.yjscloud.com/archives/51
声明:如无特别声明本文即为原创文章仅代表个人观点,版权归《废权的博客》所有,欢迎转载,转载请保留原文链接。
THE END
分享
二维码
Python当歌(十):面向对象(基础篇)
概述 (1)面向过程:根据业务逻辑从上到下写垒代码 (2)函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 (3)面向对象:对函数进行……
<<上一篇
下一篇>>
文章目录
关闭
目 录