python程序设计第12课 第13课第六章面向对象程序设计

第六章 面向对象程序设计

"类"代码编程基础

1.面向对象程序设计的概念

面向对象程序设计将数据以及对数据的操作(方法)封装在一起组成一个整体(对象)。对于相同类型的对象(object)进行分类,抽象后得出共同的特征就形成了类(class),二者是一对多的关系。面向对象程序设计的关键就是如何合理地定义和组织这些类以及类之间的关系

2.定义类的语法

以下是一个类的实例

(python2.7)
'''接下来这行代码定义了一个类(class),这段代码被解释器执行以后会在内存中生成一个类对象。函数定义完了之后也会在内存中生成函数对象,尽管函数还没有被调用。定义类的目的是为了生成多个实例对象(instance object)'''
>>>class Car:

'''接下来是类里面的方法(class method,method=function defined in class)。类内部定义的实例方法(instance method)第一个参数必须是self,即使不需要参数也要加上一个参数self。_init_的两下划线提示你这是python保留的方法,这个名为init的函数是python类里面的构造函数,它并不是由你创造的,而是python已经事先替你定义好的。它同样必须有一个self参数,在我们这个例子里面每一个car都要有一个color属性,所以在它的构造函数里它需要接收第二个参数名为color'''

#有self作为第一个参数的类方法称为类的实例方法。还有其他种类的类方法,它们间的辨析是下一节的内容

...    def _init_(self,color):
...    self.color=color

'''在构造函数内部将用户指定的color参数值赋给另一个变量self.color。在这里self实际上就代表一个实例,因此两边的color实际上不是一个东西,左边的self.color是实例属性(instance attribute),或者说是实例self底下的color。'''

...
...    def information(self):
...        print'My car is  %s'%self.color

'''information是用户定义的一个方法,它同样要有self参数,哪怕不需要参数。这种类型的方法我们叫做实例方法。必须写成self.color,python知道到self这个实例对象里去找color属性'''

...
>>>
>>>
>>>car=Car('black')

'''color在类被构造的时候由用户指定,black作为参数传到类里面。通过指定color为black去构建一个黑色的car。通过定义的类Car生成了一个实例对象(instance)car。Car类代表一类东西,这一类有个可以由用户指定的数据为color,通过指定color可以生成多个实例。通过指定color生成class的实例的时候,python自动帮你去调用类的构造函数init。参数black被python自动赋予构造函数里的第二个参数'''

>>>
>>>
>>>car.color
'black'

'''instance.attribute:通过实例访问实例属性'''

>>>
>>>
>>>car.information()
My car is black

'''instance.method():通过实例访问实例方法,注意这里不用加self参数,python调用car.information()的时候自动把实例car传给information的第一个形参self'''

*类名每个单词首字母大写(如CamelCase),普通变量名和函数名首字母则不用大写且单词之间用下划线分隔(如snake_case)。这是python的命名规范,和C++和C不同。后者普通函数名和变量名的首字母小写且第二个单词首字母大写。

*isinstance用于判断是否为类的实例

>>>isinstance(car,Car)
#返回True or False

3.self参数

self 参数代表将来要创建的实例对象本身,在类方法中访问实例属性要以self为前缀, 在外部通过实例对象调用方法则不需要传递这个self参数

>>>class A:
...    def _init_(self,v):
...        self.value=v
...    def show(self)
...        print self.value

#方法的第一个参数self总是期待着接收方法调用的主体,也就是实例对象(instance object)
...
>>>
>>>a=A(3)

#通过class A生成一个实例对象,类生成实例化的时候init函数会被python自动调用

#调用实例的实例方法有如下两种方法1.通过实例对象调用实例方法2.通过类对象传参调用实例方法
>>>
>>>a.show()
3

'''a实例对象是方法调用的主体,调用过程中python会帮你把实例对象a传给实例对象方法show()的第一个参数self,而self.value之前在构造函数里面已经被设定。'''

>>>
>>>A.show(a)
3

'''类对象.实例方法(实例方法所需要的实例对象)的作用与上一行的命令等价。这也是上一句python实际在帮你做的事情。日常中一般用上一种方法'''

4.常用特殊方法

python中类的构造函数是_ init _()。用来为数据成员设置初值或进行其他的初始化工作。在创建实例对象时被自动调用执行。用户若没有定义自己的构造函数,python提供一个默认的构造函数用来进行必要的初始化工作

*析构函数_ del _()用来释放对象占用的资源。但是因为python动态自动管理内存,这个析构函数一般不用手动去写。

5.继承

新设计的类(子类,派生类)继承已有的类(父类,基类或超类)以提高工作效率。每次使用object.attribute 时,python会在运行期间去“爬树”来搜索属性,这种机制叫继承。继承由下至上,由左自右地搜索树来寻找属性名字所出现的最低的地方,树中位置较低对象继承树中位置较高对象属性

一个有关“类”的例子

这一部分将由简单到复杂(既需求变的越来越多,引用越来越多面向对象里的概念),把一个类外加一些测试用例写出来。

这个例子中构建两个类来记录和处理有关“公司人员”的信息。两个类分别为Person(表示,处理有关人员的信息)和Manage(一个通过“继承”来实现定制化的Person。表示,处理有关“经理”的信息)。同时我们还会创建两个类的实例并测试它们的功能

1.定义Person类并写一些测试代码并添加一些行为方法

#!/usr/bin/env python
class Person(object):
    def _inie_(self,name,job=None,pay=0):
        self.name=name
        self.job=job
        self.pay=pay
        
if _name_=='_main_':
    bob=Person('Bob Smith')
    sue=Person('Sue Jones',job='dev',pay=100000)
  
    print(bob.name,bob.pay)
    print(sue.name,sue.pay)
    
    print(bob.name.split()[-1])    #extract bob's last name
    sue.pay*=1.1                   #give sue's pay a raise
    print(sue.pay)
输出:
Bob Smith 0
Sue Jones 100000
Smith
110000.00000000001

进一步改进:使用封装(encapsulation)的思想,把操作对象的代码放在类定义中使其成为类的方法。 把对类属性的操作放入类定义中成为类的实例方法使得这些操作可以用于类的任何实例。

#!/usr/bin/env python
class Person(object):
    def _inie_(self,name,job=None,pay=0):
        self.name=name
        self.job=job
        self.pay=pay
    def last_name(self):
        return self.name.split()[-1]
    def give_raise(self,percent):
        self.pay=int(self.pay*(1+percent))
        
if _name_=='_main_':
    bob=Person('Bob Smith')
    sue=Person('Sue Jones',job='dev',pay=100000)

    print(bob.name,bob.pay)
    print(sue.name,sue.pay)
    
    print(bob.last_name(),sue.last_name())    #extract bob's last name
    sue.give_raise(0.1)                  #give sue's pay a raise
    print(sue.pay)
输出:
Bob Smith 0
Sue Jones 100000
Smith Jones
110000

2.运算符重载

这个时候你如果要print(bob)和print(sue),实例对象的默认显示格式不是很好,因为它会显示对象的类名及其在内存中的地址。如果我们想要多显示一些对人有用的信息就要使用运算符重载(operator overloading)。

实现运算符重载需要自己在类里面编写一些方法。这里我们要重载_ str _ ()方法(这是python已经帮你定义好的内置方法,是用于“对象字符串表达形式”的方法)。如果我们不定义自己的_ str _ ()方法,那么我们定义的类就会从它的超类继承一个默认的_ str _ ()方法,直接print(bob)和print(sue)得到的结果就是默认的_ str _ ()的行为。运算符重载就是为了在类的方法中拦截内置操作

#!/usr/bin/env python
class Person(object):
    def _inie_(self,name,job=None,pay=0):
        self.name=name
        self.job=job
        self.pay=pay
    def last_name(self):
        return self.name.split()[-1]
    def give_raise(self,percent):
        self.pay=int(self.pay*(1+percent))
    def _str_(self):
    return '[Person: %s,%s]' % (self.name,self.pay)
        
if _name_=='_main_':
    bob=Person('Bob Smith')
    sue=Person('Sue Jones',job='dev',pay=100000)

    print(bob)
    print(sue)
    sue.give_raise(0.1)                 
    
    print(sue.sue)
输出:
[Person: Bob Smith, 0]
[Person: Sue Jones,100000]
[Person: Sue Jones,110000]
*辨析_ str _ 和_repr _(考试不考,自己有兴趣去看课件)

输出对象的字符串表示的时候有两种功能类似的方法 _ str _ 和 _ repr _。需要注意这两者的区别。

_ str _是用于“对象字符串表达形式”的方法,即将对象变成一种字符串的表达形式
/*如果你不在类里重载python默认的_str_方法的时候,python给你提供了一种默认的形式就是类名+内存中默认地址*/
//例一
>>>class Spam(object):
...    def _init_(self,value=0):
...        self.value=value
...
>>>s=spam()
>>>
>>>print(s)
<_main_.Spam object at 0x0000022A6FBAA9E8>
>>>

//例二
>>>class Spam(object):
...    def _init_(self,value=0):
...        self.value=value
...
...    def _str_(self):
...        return '[Value:%d]'%self.value
>>>s=Spam()
>>>
>>>print(s)
[Value:0]
>>>
相同点:

_ str _和 _repr _都必须返回字符串

不同点:

打印操作(print)会首先操作 _ str _

_ repr _用于所有其它的环境中

也就是 _str _用于print这个函数中, _ repr _ 用于其他的环境中。即 _ repr _用于任何地方,除了当重载了一个 _ str _的时候。在交互模式(interactive mode),只会使用 _ repr _。即使重载了 _ str _,也不会被用到。具体的例子如下

/#例一:交互模式下,只重载了_repr_
>>>class Spam(object):
...    def _init_(self,value=0):
...        self.value=value
...
...    def _repr_(self):
...        return 'Spam(%r)'%self.value
>>>
>>>s=Spam(1)
>>>s      #在交互模式里,你可以直接把对象名直接输入在终端上面然后回车来打印它的值
Spam(1)   #在交互模式里对一个类重载了_repr_之后,你再输入s他就会使用repr(你定的repr形式)
>>>print(s)  #如果你没有定义str,print就会直接使用repr。
Spam(1)
>>>str(s),repr(s)
('Spam(1)','Spam(1)')

#例二:交互模式下,只重载了_str_
>>>class Spam(object):
...    def _init_(self,value=0):
...        self.value=value
...
...    def _str_(self):
...        return '[Value:%d]'%self.value
>>>s=Spam(1)
>>>s
<_main_.Spam object at 0x00000226FBB07F0>
>>>print(s)  //print会优先使用_str_
[Value:1]
>>>str(s),repr(s)
('[Value:1]'),'<_main_.Spam object at 0x00000226FBB07F0>'

#例三:交互模式下,既重载了_repr_,又重载了_str_
>>>class Spam(object):
...    def _init_(self,value=0):
...        self.value=value
...
...    def _str_(self):
...        return '[Value:%d]'%self.value
...
...    def _repr_(self):
...        return 'Spam(%r)'%self.value
...
>>>s=Spam(1)
>>>s
Spam(1)
>>>print(s)
[Value:1]
>>>str(s),repr(s)
('[Value:1]','Spam(1)')

这样设计的目的(具体见课件):

__ repr __ is for developers to understand the object。比如课件例子中你要去使用logging这个模块的时候,你的对象就必须要去提供 _ repr _ 重载。logging会直接使用你的重载把它放在日志记录里

*字符串格式化符 %r

%r和%s的区别

#一,
>>>class Spam(object):
...    def _init_(self,value=0):
...        self.value=value
...
...    def _repr_(self):
...        return 'Spam(%r)'%self.value
...
>>>s=Spam('1')
>>>s
Spam('1')

#二,
>>>class Spam(object):
...    def _init_(self,value=0):
...        self.value=value
...
...    def _repr_(self):
...        return 'Spam(%s)'%self.value
...
>>>s=Spam('1')
>>>s
Spam(1)

当给value赋一个字符串1,repr打印出来时还能显示出来输入的value是一个字符串,这样就起到了提示的作用。repr输出一般都用%r来尽量的在输出的字符串能保留原来的类型

3.通过继承来定制化

接下来定义一个Manager类,它代表公司中的经理。经理也是公司雇员,所以经理有雇员所有的属性。但是经理是特殊的雇员,除了有雇员的属性还有自己特殊的属性。这里可以使用继承来表达这种关系,让Person派生出来一个Manager类

class Person(object):
    def _inie_(self,name,job=None,pay=0):
        self.name=name
        self.job=job
        self.pay=pay
    def last_name(self):
        return self.name.split()[-1]
    def give_raise(self,percent):
        self.pay=int(self.pay*(1+percent))
    def _str_(self):
    return '[Person: %s,%s]' % (self.name,self.pay)
class Manager(Person):

'''Person是Manager的超类(基类,父类),定义"继承"关系时我们要把被继承的超类名写在子类的括号里,上面的object是所有类的超类(o不用大写)'''
    
    def give_raise(self,percent,bonus=0.1)
        pass

下来编写Manager类中的give_raise方法。 有两种实现方式,一好一坏

#法一:
class Manager(Person):
    def give_raise(self,percent,bonus=0.1):
        self.pay=int(self.pay*(1+pecent+bonus))

'''这种“复制粘贴”Person类方法中的代码,再在其上修修补补的方法缺点在于一旦涨薪水的方法发生了改变(即改变了Person的类方法give_raise)就必须修改两处代码'''

#法二:
class Manager(Person):
    def give_raise(self,percent,bonus=0.1):
        person.give_raise(self,pecent+bonus)

'''在Manager类的give_raise方法中直接调用Person类里的give_raise方法,把bonus+person作为参数传给person的give_raise方法。基本的give_raise方法现在只出现Person类定义里,这样就方便了代码维护

注:Manager类的give_raise方法是一个实例方法,它的第一个参数self用于接收以后调用give_raise的对象。这里在Manager类的give_raise方法中调用Person类里的give_raise方法的时候还没有实例对象,使用了类名.实例方法名然后显式地写出self参数的方法来调用Person类里的give_raise方法。
这种写法与这节课开头的A.show(a)类似,都是通过类调用实例方法,都需要把实例对象显式地传给self参数。另一种方法a.show()则是通过实例对象直接调用实例方法,因此不用再把a传给self参数。'''

*一个操作的意义取决于被操作对象的类型,这种依赖类型的行为称为多态(Polymorphism)

class Person(object):
    def _inie_(self,name,job=None,pay=0):
        self.name=name
        self.job=job
        self.pay=pay
    def last_name(self):
        return self.name.split()[-1]
    def give_raise(self,percent):
        self.pay=int(self.pay*(1+percent))
    def _str_(self):
    return '[Person: %s,%s]' % (self.name,self.pay)
class Manager(Person):
    def give_raise(self,percent,bonus=0.1):
        person.give_raise(self,pecent+bonus)
if _name_=='_main_':
    bob=Person('Bob Smith')
    sue=Person('Sue Jones',job='dev',pay=100000)
    tom=Manager('Tom Jones','mgr',500000)
    
    for obj in(bob,sue,tom);
        obj.give_raise(0.1)
        print (obj)
'''
输出:
[Person: Bob Smith, 0]
[Person: Sue Jones,100000]
[Person: Tom Jones,600000]
可见bob,sue和tom的加薪率不同。同样是在for循环里调用give_raise,python会自动根据对象类型为其选择对应的give_raise方法
'''

4.定制构造函数

创建Manager实例对象时必须为它提供一个job='mgr’参数。因为Manager这个名字已经暗示其职务是经理(mgr)。

接下来实现创建Manager实例对象时利用某种方式自动填入job=‘mgr’这个参数。为了实现该功能需要重新定义Manager类中的_ init _方法从而提供’mgr’这个字符串。我们在子类中通过类来调用父类Person中的 _ init _方法并在调用过程中直接把’mgr’作为参数传给构造函数,这样就不需要在使用Manager类的时候手动地去添加’mgr’

而这段代码写在重构(或者说是定制)的这个Manager构造函数里

class Person(object):
    def _inie_(self,name,job=None,pay=0):
        self.name=name
        self.job=job
        self.pay=pay
    def last_name(self):
        return self.name.split()[-1]
    def give_raise(self,percent):
        self.pay=int(self.pay*(1+percent))
    def _str_(self):
    return '[Person: %s,%s]' % (self.name,self.pay)
class Manager(Person):
    def _init_(self,name,pay):
        person._init_(self,name,'mgr',pay)
    def give_raise(self,percent,bonus=0.1):
        person.give_raise(self,pecent+bonus)
if _name_=='_main_':
    bob=Person('Bob Smith')
    sue=Person('Sue Jones',job='dev',pay=100000)
    tom=Manager('Tom Jones',500000)
    
    print(bob)
    print(sue)
    print(tom)
    
    sue.give_raise(0.1)
    tom.give_raise(0.1)
    
    print(sue)
    print(tom)
'''
输出:
[Person: Bob Smith, 0]
[Person: Sue Jones,100000]
[Person: Tom Jones,500000]
[Person: Sue Jones,110000]
[Person: Tom Jones,600000]    
'''

步骤6:使用内省工具

目前为止当打印经理tom时会显示"Person"。这是因为

1.Manager类继承了Person类的 _ str _方法

2.Manager也是一种Person,打印经理tom时输出"Person"没有问题

如果我们想更加精确一些,让打印经理tom时输出"Manager"该如何做?可以在Manager类定义里重载_ str _方法来实现,但是这不方便。

因此为了让实例对象自己知道自己的类的相关信息,可以使用内省(introspection)来实现,内省是程序在运行时检查对象类型的一种能力。python提供了可用于内省的工具(如instance. _ class _和instance. _dict _)来让对象知道自己的具体类型

下面先展示instance. _ class _和instance. _dict _的输出结果

>>>class Person(object):
...    def _init_(self,name,job=None,pay=0):
...        self.name=name
...        self.job=job
...        self.pay=pay
...
...    def _str_(self):
...        return '[Person:%s,%s]' %(self.name,self.pay)
...
>>>bob=Person('Bob Smith')
>>>print(bob)
[Person:Bob smith,0]
>>>
>>>bob._class_
<class'_main_.Person'>  #返回的字符串含有bob这个实例对象所对应的类型信息
>>>bob._Class_._name_   
'Person'                #直接把类的名字作为字符串显示出来
>>>bob._dict_
{'pay':0,'name':'Bob Smith','job':None} 
#返回一个字典,内部分别为bob的实例属性和实例属性对应的值
>>>
>>>for key in bob._dict_:          
#既然是字典就可以去遍历它,for key in bob._dict_就是依次取字典的键
...    print(key,'=>',bob._dict_[key]) //打印key和key所对应的value
...
pay=>0
name=>Bob Smith
job=>None
>>>
>>>for key in bob._dict_:
...    print(key,'=>',getattr(bob,key))
#getattr(get attribute)为很常用的内置函数,它接收的第一个参数是一个实例,第二个参数是这个实例的实例属性名,这样可以得到实例属性对应的值。效果和上一个for循环作用一样
...
pay=>0
name=>Bob Smith
job=>None

有了instance. _ class _和instance. _ class _ . _ name _和instance. _ dict _。我们就可以编写代码来获得某个实例的类型和它的属性。下面就是自己定义一个"通用显示工具类"来实现内省的功能

#!/usr/bin/env python
class AttrDisplay(object):
    def gather_attrs(self):
        attrs=['%s=%s'%(key,getattr(self,key)) for key in sorted(self._dict_)]
        return ','.join(attrs)

'''用','.join(attrs)将列表attrs内的多个字符串用’, ‘连接起来形成一个长字符串并返回。上一句则是一个列表推导式。
看列表推导式先看后面由什么循环来得到。self._dict_会返回一个字典,利用sorted对字典的键进行排序返回一个列表,通过for循环来遍历这个列表,实际上就是获得self所对应的属性名字key,然后把这个名字作为参数传给getattr这个函数得到属性名所对应的值,然后把属性名写在前面,对应的值写在后面返回。依次重复这个步骤得到一个列表'''

    def _str_(self):
        return '[%s:%s]'%(self._class_._name_,self.gather_attrs())
#第一个输出是实例所对应的类的名字,第二个则是AttrDisplay这个类的gather_attrs方法的返回值
    
if _name_=='_main_':
    calss TopTest(AttrDisplay):
#定义一个类TopTest继承于AttrDisplay,TopTest自然就拥有了AttrDisplay的gather_attrs与_str_两个方法,TopTest内部有一个属性count,下面利用这个count给两个属性attr1,attr2赋值
        count=0
        
        def _init_(self):
            self.attr1=TopTest.count
            self.attr2=TopTest.count+1
            TopTest.count+=2
            
    class SubTest(TopTest):
        pass
        
    t1=TopTest()
    s1=SubTest()
    s2=SubTest()
        
    print(t1)
    print(s1)
    print(s2)
'''
输出:
[TopTest:attr1=0,attr2=1]  先打印出t1这个类所对应的名字,然后是t1的属性名和属性值
[SubTest:attr1=2,attr2=3]
[SubTest:attr1=4,attr2=5]
'''

最后使用我们刚刚定义的AttrDisplay类,Person改为继承自AttrDisplay而不是object,这样Person就有了内省的功能。以上用于演示内省功能的AttrDisplay类可被视为一个“通用工具”,可以通过继承将其“混合”到任何类中,从而在派生类中可以方便的使用AttrDisplay类定义的显示格式

以下是这个例子的最终版:

#!/usr/bin/env python
from classtools import AttrDisplay

class Person(AttrDisplay)
    def _init_(self,name,job=None,pay=0):
        self.name=name
        self.job=job
        self.pay=pay
    
    def last_name(self):
        return self.name.spilt()[-1]
    
    def give_raise(self,percent)
        self.pay=int(self.pay*(1+percent))
        
class Manager(Person):
    def _init_(self,name,pay):
        Person._init_(self,name,'mgr',pay)
        
    def give_raise(self,percent,bonus=0.1)
        Person.give_raise(self,percent+bonus)
        
if _name_=='_main_':
    bob=Person('Bob Smith')
    sue=Person('Sue Jones',job='dev',pay=100000)
    tom=Manager('Tom Jones',500000)
    
    sue.give_raise(0.1)
    tom.give_raise(0.1)
    
    print(bob)
    print(sue)
    print(tom)
'''    
输出:
[Person:job=None,name=Bob Smith,pay=0]
[Person:job=dev,name=Sue Jones,pay=110000]
[Manager:job=mgr,name=Tom Jones,pay=600000]
'''

以上的例子涵盖了Python面向对象编程机制(OOP)中几乎所有的重要概念:

1.定义类:class Person(object):

2.在类方法中封装业务逻辑(class里面除了有变量,还有对变量操作的函数)

3.运算符重载(如果不喜欢继承自父类的一些函数功能,你可以在子类里面重新去定义这些方法)

4.通过继承得到子类:class Manager(Person):

5.在子类中定制行为(重载):重新定义子类中的方法以使其特殊化(如 _ str _(self), _ repr _(self))

6.在子类中定制构造函数(如 _ init _(self))

7.通过类创建实例:tom = Manager()

8.运行时类型检查(内省)

编辑于2020-5-6 17:35

你可能感兴趣的:(Python学习)