第五章 面向对象编程设计与开发——续

5.1   类、实例、属性、方法详解

类的语法

上面的代码其实有问题,属性名字和年龄都写死了,想传名字传不进去。

class Person(object):

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


p = Person("Alex", 22)
print(p.name, p.age)

为什么有__init__? 为什么有self? 此时的你一脸蒙逼,相信不画个图,你的智商是理解不了的!

画图之前, 你先注释掉这两句

# p = Person("Alex", 22)
# print(p.name, p.age)

#加上句
print(Person)

#执行结果
<class '__main__.Person'>

其实self,就是实例本身!你实例化时python解释器会自动把这个实例本身通过self参数传进去。

了解self的好处,得给Person类加个功能

class Person(object):

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

    def talk(self):
        print("Hello, my name is %s, I'm %s years old!" % (self.name, self.age))

p = Person("Alex", 22)
# print(p.name, p.age)
p.talk() #注意这里调用并未传递参数

执行输出

Hello, my name is Alex, I'm 22 years old!

为何p.talk()未传参数不报错,且为何talk方法定义要跟一个self参数?

我们上面讲到self其实是实例本身, 那p.talk() 其实相当于p.talk(p),你不需要自己把p实例传进去,解释器帮你干了,那为何要在talk定义时加self参数呢?

是因为,你的talk方法里有调用到实例的属性呀,这些属性又都是绑定在实例上的,你想调用实例属性,就必须要把实例传进去。

构造方法

__init__(...)被称为 构造方法或初始化方法,在例实例化过程中自动执行,目的是初始化实例的一些属性。每个实例通过__init__初始化的属性都是独有的

刚才定义的这个类体现了面向对象的第一个基本特性,封装,其实就是使用构造方法将内容封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内容

了解了基本定义,下面详解下类的方法和属性

类的方法

构造方法

刚才上面已经说了,主要作用是实例化时给实例一些初始化参数,或执行一些其它的初始化工作,总之,因为这个__init__只要一实例化,就会自动执行,so不管你在这个方法里写什么,它都会统统在实例化时执行一遍

普通方法

定义类的一些正常功能,比如人这个类, 可以说话、走路、吃饭等,每个方法其实想当于一个功能或动作

析构方法(解构方法)

实例在内存中被删除时,会自动执行这个方法,如你在内存里生成了一个人的实例,现在他被打死了,那这个人除了自己的实例要被删除外,可能它在实例外产生的一些痕迹也要清除掉,清除的动作就可以写在这个方法里

class Person(object):

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

    def talk(self):
        print("Hello, my name is %s, I'm %s years old!" % (self.name, self.age))

    def __del__(self):
        print("running del method, this person must be died.")


p = Person("Alex", 22)
p.talk()

del p

print('--end program--')

面向过程的程序设计

概念:

核心是“过程”二字,“过程”指的是解决问题的步骤,即先干什么再干什么......,基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式。若程序一开始是要着手解决一个大的问题,面向过程的基本设计思路就是把这个大的问题分解成很多个小问题或子过程,这些子过程在执行的过程中继续分解,直到小问题足够简单到可以在一个小步骤范围内解决。

优点是

复杂的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)

举个典型的面向过程的例子, 写一个数据远程备份程序, 分三步,本地数据打包,上传至云服务器,测试备份文件可用性。

import os

def data_backup(folder):
    print("找到备份目录: %s" %folder)
    print('正在备份......')
    zip_file='/tmp/backup20181103.zip'
    print('备份成功,备份文件为: %s' %zip_file)
    return zip_file

def cloud_upload(file):
    print("\nconnecting cloud storage center...")
    print("cloud storage connected.")
    print("upload file...%s...to cloud..." %file)
    link='http://www.xxx.com/bak/%s' %os.path.basename(file)
    print('close connection.....')
    return link

def data_backup_test(link):
    print("\n下载文件: %s , 验证文件是否无损" %link)

def main():
    #步骤一:本地数据打包
    zip_file = data_backup("c:\\users\\alex\欧美100G高清无码")

    #步骤二:上传至云服务器
    link=cloud_upload(zip_file)

    #步骤三:测试备份文件的可用性
    data_backup_test(link)

if __name__ == '__main__':
    main()

缺点是:

一套流水线或者流程就是用来解决一个问题,比如生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,与其相关的组件都需要修改,牵一发而动全身,扩展性极差。

比如我们修改了步骤二的函数cloud_upload的逻辑,那么依赖于步骤二结果才能正常执行的步骤三的函数data_backup_test相关的逻辑也需要修改,这就造成了连锁反应,而这一弊端会随着程序的增大而变得越发的糟糕,我们程序的维护难度将会越来越大。

import os

def data_backup(folder):
    print("找到备份目录: %s" %folder)
    print('正在备份......')
    zip_file='/tmp/backup20181103.zip'
    print('备份成功,备份文件为: %s' %zip_file)
    return zip_file

def cloud_upload(file): #加上异常处理,在出现异常的情况下,没有link返回
    try:
        print("\nconnecting cloud storage center...")
        print("cloud storage connected.")
        print("upload file...%s...to cloud..." % file)
        link = 'http://www.xxx.com/bak/%s' % os.path.basename(file)
        return link
    except Exception:
        print('upload error')
    finally:
        print('close connection.....')

def data_backup_test(link): #加上对参数link的判断
    if link:
        print("\n下载文件: %s , 验证文件是否无损" %link)
    else:
        print('\n链接不存在')
def main():
    #步骤一:本地数据打包
    zip_file = data_backup("c:\\users\\alex\欧美100G高清无码")

    #步骤二:上传至云服务器
    link=cloud_upload(zip_file)

    #步骤三:测试备份文件的可用性
    data_backup_test(link)

if __name__ == '__main__':
    main()

应用场景:

面向过程的程序设计思想一般用于那些功能一旦实现之后就很少需要改变的场景, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,著名的例子有Linux內核,git,以及Apache HTTP Server等。但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。

面向对象的程序设计

概念:

核心是“对象”二字,要理解对象为何物,必须把自己当成上帝,在上帝眼里,世间存在的万物皆为对象,不存在的也可以创造出来。程序员基于面向对象设计程序就好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来并没有考虑问题的解决流程,而是设计出了负责取经的师傅四人:唐僧,沙和尚,猪八戒,孙悟空,负责骚扰的一群妖魔鬼怪,以及负责保驾护航的一众神仙,这些全都是对象,然后取经开始,就是师徒四人与妖魔鬼怪神仙交互着直到完成取经任务。所以说基于面向对象设计程序就好比在创造一个世界,世界是由一个个对象组成,而你就是这个世界的上帝。

我们从西游记中的任何一个人物对象都不难总结出:对象是特征与技能的结合体。比如孙悟空的特征是:毛脸雷公嘴,技能是:七十二变、火眼金睛等。

与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界而非流程的模拟,是一种“上帝式”的思维方式。

优点是

解决了面向过程可扩展性低的问题,这一点我们将在5.2小节中为大家验证,需要强调的是,对于一个软件质量来说,面向对象的程序设计并不代表全部,面向对象的程序设计只是用来解决扩展性问题。

缺点是:

编程的复杂度远高于面向过程,不了解面向对象而立即上手并基于它设计程序,极容易出现过度设计的问题,而且在一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本程序就不适合用面向对象去设计,面向过程反而更加适合。

应用场景:

当然是应用于需求经常变化的软件中,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

5.2  类与对象

类与对象的概念

类即类别、种类,是面向对象设计最重要的概念,从一小节我们得知对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体。

那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看

  • 在现实世界中:肯定是先有对象,再有类
  • 世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,比如
    人类、动物类、植物类等概念。也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在,比如你无法告诉我人类
    具体指的是哪一个人。

    在程序中:务必保证先定义类,后产生对象

  • 这与函数的使用是类似的:先定义函数,后调用函数,类也是一样的:在程序中需要先定义类,后调用类。不一样的是:调用
    函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象

    定义类

    按照上述步骤,我们来定义一个类(我们站在老男孩学校的角度去看,在座的各位都是学生)

    • 在现实世界中,先有对象,再有类
    • 对象1:李坦克
          特征:
              学校=oldboy
              姓名=李坦克
              性别=男
              年龄=18
          技能:
              学习
              吃饭
              睡觉
      
      对象2:王大炮
          特征:
              学校=oldboy
              姓名=王大炮
              性别=女
              年龄=38
          技能:
              学习
              吃饭
              睡觉
      
      对象3:牛榴弹
          特征:
              学校=oldboy
              姓名=牛榴弹
              性别=男
              年龄=78
          技能:
              学习
              吃饭
              睡觉
      
      
      现实中的老男孩学生类
          相似的特征:
              学校=oldboy
          相似的技能:
              学习
              吃饭
              睡觉

      在程序中,务必保证:先定义(类),后使用类(用来产生对象)

    • #在Python中程序中的类用class关键字定义,而在程序中特征用变量标识,技能用函数标识,因而类中最常见的无非是:变量和函数的定义
      class OldboyStudent:
          school='oldboy'
          def learn(self):
              print('is learning')
      
          def eat(self):
              print('is eating')
      
          def sleep(self):
              print('is sleeping')

      注意:

      • 类中可以有任意python代码,这些代码在类定义阶段便会执行,因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过OldboyStudent.__dict__查看
      • 类中定义的名字,都是类的属性,点是访问属性的语法。
      • 对于经典类来说我们可以通过该字典操作类名称空间的名字,但新式类有限制(新式类与经典类的区别我们将在后续章节介绍)
      • 类的使用

        • 引用类的属性
        • OldboyStudent.school #
          OldboyStudent.school='Oldboy' #
          OldboyStudent.x=1 #
          del OldboyStudent.x #

          调用类,或称为实例化,得到程序中的对象

        • s1=OldboyStudent()
          s2=OldboyStudent()
          s3=OldboyStudent()
          
          #如此,s1、s2、s3都一样了,而这三者除了相似的属性之外还各种不同的属性,这就用到了__init__

          __init__方法

        • #注意:该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值
          class OldboyStudent:
              ......
              def __init__(self,name,age,sex):
                  self.name=name
                  self.age=age
                  self.sex=sex
              ......
          
          s1=OldboyStudent('李坦克','',18) #先调用类产生空对象s1,然后调用OldboyStudent.__init__(s1,'李坦克','男',18)
          s2=OldboyStudent('王大炮','',38)
          s3=OldboyStudent('牛榴弹','',78)

          对象的使用

#执行__init__,s1.name='牛榴弹',很明显也会产生对象的名称空间可以用s2.__dict__查看,查看结果为
{'name': '王大炮', 'age': '', 'sex': 38}

s2.name #查,等同于s2.__dict__['name']
s2.name='王三炮' #改,等同于s2.__dict__['name']='王三炮'
s2.course='python' #增,等同于s2.__dict__['course']='python'
del s2.course #删,等同于s2.__dict__.pop('course')

补充说明

  • 站的角度不同,定义出的类是截然不同的;
  • 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类等;
  • 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类。

5.3  属性查找与绑定方法

属性查找

类有两种属性:数据属性和函数属性

1、类的数据属性是所有对象共享的

#类的数据属性是所有对象共享的,id都一样
print(id(OldboyStudent.school))

print(id(s1.school)) #4377347328
print(id(s2.school)) #4377347328
print(id(s3.school)) #4377347328

2、类的函数数据是绑定给对象用的,称为绑定到对象的方法

#类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样

print(OldboyStudent.learn) #<function OldboyStudent.learn at 0x1021329d8>
print(s1.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>>
print(s2.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>>
print(s3.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>>

#ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准

在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常

绑定方法

定义类并实例化出三个对象

class OldboyStudent:
    school='oldboy'
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def learn(self):
        print('%s is learning' %self.name) #新增self.name

    def eat(self):
        print('%s is eating' %self.name)

    def sleep(self):
        print('%s is sleeping' %self.name)


s1=OldboyStudent('李坦克','',18)
s2=OldboyStudent('王大炮','',38)
s3=OldboyStudent('牛榴弹','',78)


类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数
OldboyStudent.learn(s1) #李坦克 is learning
OldboyStudent.learn(s2) #王大炮 is learning
OldboyStudent.learn(s3) #牛榴弹 is learning

类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数

OldboyStudent.learn(s1) #李坦克 is learning
OldboyStudent.learn(s2) #王大炮 is learning
OldboyStudent.learn(s3) #牛榴弹 is learning

类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法

强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)

s1.learn() #等同于OldboyStudent.learn(s1)
s2.learn() #等同于OldboyStudent.learn(s2)
s3.learn() #等同于OldboyStudent.learn(s3)

注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。

类即类型

python中一切皆为对象,且python3中类与类型是一个概念,类型就是类

#类型dict就是类dict
>>> list
<class 'list'>

#实例化的到3个对象l1,l2,l3
>>> l1=list()
>>> l2=list()
>>> l3=list()

#三个对象都有绑定方法append,是相同的功能,但内存地址不同
>>> l1.append
<built-in method append of list object at 0x10b482b48>
>>> l2.append
<built-in method append of list object at 0x10b482b88>
>>> l3.append
<built-in method append of list object at 0x10b482bc8>

#操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3
>>> l1.append(3)
>>> l1
[3]
>>> l2
[]
>>> l3
[]
#调用类list.append(l3,111)等同于l3.append(111)
>>> list.append(l3,111) #l3.append(111)
>>> l3
[111]

 

posted on 2018-09-07 21:56  大王!  阅读(176)  评论(0编辑  收藏  举报