Python面向对象编程

Posted 礁之

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python面向对象编程相关的知识,希望对你有一定的参考价值。

文章目录


此文参考廖雪峰官网:面向对象编程 - 廖雪峰的官方网站 (liaoxuefeng.com)

一、什么是面向对象编程

  • 面向对象编程(Object Oriented Programming),简称OOP,是一种程序设计思想,OOP把对象当作程序的基本单元,一个对象包含了数据和操作数据的函数
  • 面向对象的程序设计把计算机程序当作一组对象的集合,每个对象都可以接受其他对象发来的消息,并且进行处理,而计算机程序的执行就是一系列消息在各对象之间进行传递,和面向对象不同,面向过程的程序设计把计算机程序当作一系列的命令集合,即一组函数的顺序执行,为了简化程序设计,面向过程把函数继续切分为子函数,从而降低系统的复杂度
  • 在Python中,所有的数据类型都可以看作为对象,当然也可以自定义对象,自定义的对象数据类型就是面向对象中类(class)的概念,下面来看一个案例来说明面向对象和面向过程的区别:
- 现在我们需要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以使用一个字典表示,例如:
# -*- coding: utf-8 -*-
std_1 = 'name':'zhangsan','score':98
std_2 = 'name':'lisi','score':97 

def print_score(std):
    return '%s : %s' % (std['name'],std['score'])
    
print(print_score(std_1))
print(print_score(std_2))

#输出:
zhangsan : 98
lisi : 97
  
- 如果使用面向对象的程序设计思想,首先思考的不是程序的执行流程,而是'学生'的这种数据类型应该被看作一个'对象',这个对象有'name''score'两种'属性(property)',如果想要输出一个学生的成绩,首先就需要先创建一个'学生'对应的对象,然后给这个'学生'对象发送一个'打印成绩''消息',让对象自己把指定的数据打印出来,例如:
# -*- coding: utf-8 -*-
class Student(object):

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

    def print_score(self):
        print('%s : %s' % (self.name,self.score))

- 给对象发送消息实际就是调用对象对应的'关联函数',这个关联函数也叫做'对象的方法',下面就是面向对象的程序调用
zhangsan = Student('zhangsan',98)
lisi = Student('lisi',97)
zhangsan.print_score()
lisi.print_score()

#输出:
zhangsan : 98
lisi : 97
  • 对象(class)是一种抽象的概念,上面定义的对象Student,指的就是学生这个概念,而实例(instance)则指一个个具体的对象,例如上面的zhangsanlisi就是两个具体的Student,也就是实例
  • 从上面的案例可以看出,面向对象的程序设计思想其实就是抽象出对象(class),然后根据对象创建实例(instance)
  • 最后,面向对象的抽象程度比函数高,因为一个对象既包含数据,也包含操作数据的方法,数据封装、继承、多态是面向对象的三大特点

二、类(class)和实例(instance)

  • 面对对象最重要的概念就是类(class)实例(instance),类是抽象的模板,比如上面的Student类,而实例是根据类创建出来的具体的对象,每个对象都有相同的方法,但是各自的数据可能不同,例如上面的zhangsanlisi
  • Student类为例:
# -*- coding: utf-8 -*-
class Student(object):
    pass

(1)先看第2行

class Student(object):
 
在Python中,类是通过'class'关键字进行定义的,'class'后面跟着的是类名,类名通常是以大写字母开头的,紧接着就是'(object)',这个表示的是'Student'类是从'object'类继承下来的,继承这个概念在后面会说
通常如果说没有合适的继承类,那么就可以直接使用'object'类,这是所有的类最终都会继承的类

(2)定义好了Student类,就可以根据Student类创建出Student的实例,而创建实例是通过类名()实现的,例如:

>>> class Student(object):
...     pass
... 
>>> zhangsan = Student()
>>> zhangsan
<__main__.Student object at 0x0000018C3E6A6A10>
>>> Student
<class '__main__.Student'>

可以看到,变量'zhangsan'指向的是'Student'实例,输出的信息中,'object at'后面是内存地址,每个object的地址都不一样,而'Student'本身就是一个类

还可以给一个实例变量自由的绑定属性,例如:
>>> zhangsan.name = 'zhangsan' 
>>> zhangsan.name
'zhangsan'

(3)由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些必要的属性写进入,通过一个特殊的__init__方法,在创建实例的时候,把namescore等属性绑定,例如:

>>> class Student(object):
...     def __init__(self,name,score):
...             self.name = name
...             self.score = score
... 
>>> zhangsan = Student('zhangsan',98) 
>>> zhangsan.name
'zhangsan'
>>> zhangsan.score
98
>>> lisi = Student()    #传入空参数
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Student.__init__() missing 2 required positional arguments: 'name' and 'score'
>>> lisi = Student('lisi',97,22)  #多传入一个参数
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Student.__init__() takes 3 positional arguments but 4 were given

#注意特殊方法__init__是两个_
可以看到在定义特殊方法'__init__'时,后面的第一个参数是'self',而且也必须是'self',这个参数表示创建的实例本身,因此,在'__init__'方法内部,就可以把各种属性绑定到'self',因为'self'就指向创建的实例本身
不过,在有了'__init__'方法之后,在创建实例的时候,就不能传入空参数或多个参数,必须传入和'__init__'方法相匹配的参数,但是'self'参数不需要传,Python解释器会自己把'实例变量'传入到self

(4)和普通函数相比,在类中定义的函数只有一点不同,那就是函数的第一个参数永远都是self,并且在调用该函数时,不用传递参数,除此之外,和普通函数没有其他区别,仍然可以使用默认参数、可变参数、关键字参数、命名关键字参数

三、特性之一——数据封装

  • 面向对面编程的一个重要的特点就是数据封装,在上面的Student类中,根据对象创建的实例里,都有各自的namescore的数据,可以通过函数来访问这些数据,例如:

    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.name = name
    ...             self.score = score
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.name
    'zhangsan'
    >>> zhangsan.score
    98
    >>> def print_score(std):
    ...     return '%s : %s' % (std.name,std.score) 
    ... 
    >>> print_score(zhangsan) 
    'zhangsan : 98'
    
  • 但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就可以把数据封装起来了,这些封装数据的函数和Student类本身是关联起来的,我们称之为类的方法,更改后可以这样写:

    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.name = name
    ...             self.score = score
    ...     def print_score(self):
    ...             return '%s : %s' % (self.name,self.score) 
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.print_score()           
    'zhangsan : 98'
    
    可以发现,'zhangsan'可以直接引用'Student'类中的'print_score'函数
    同样的'print_score'函数的参数也是'self',也是不用传递的,直接在实例变量上调用即可
    
    如果有第三个参数age,但是在__init__中并没有定义,可以这样写:
    # -*- coding: utf-8 -*-
    class Student(object):
        
        def __init__(self,name,score):
            self.name = name
            self.score = score
    
        def print_score(self):
            return print('%s %s %s' % (self.name,self.score,self.age))
    
    zhangsan = Student('zhangsan',98)
    zhangsan.age = 22  #定义实例变量age
    zhangsan.print_score()        
    
    #输出:
    zhangsan 98 22
    
  • 从上面可以看出,在根据Student类创建实例时,只需要指定namescore的值即可,关于如何打印出来,这些都是在Student类的内部定义的,从而使这些数据和逻辑被封装起来,调用时并不知道内部的细节

  • 封装的另一个好处就是可以给Student类增加新的方法,例如:

# -*- coding: utf-8 -*-
class Student(object):
    
    def __init__(self,name,score):
        self.name = name
        self.score = score

    def print_score(self):
        return print('%s : %s' % (self.name,self.score))

    def get_grade(self):
        if self.score >= 90 and self.score <= 100:
            return print('A')
        elif self.score >= 80:
            return print('B')
        else:
            return print('C')

zhangsan = Student('zhangsan',98)
zhangsan.print_score()    
zhangsan.get_grade()    

#输出:
zhangsan : 98
A
  • 总结:

    1. 类是创建实例的模板,而实例是一个个具体的对象,每个实例拥有的数据都是相互独立的,互不影响

    2. 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据

    3. 通过在实例上调用方法,其实就是直接操作了对象内部的数据,并且无需指定方法内部的实现细节

    4. 和静态语音不同,Python允许对实例变量绑定任何数据,这样的效果就是,就算是根据同一类创建出的实例,实例拥有的变量名称可能都是不一样的,例如:

      # -*- coding: utf-8 -*-
      class Student(object):
          
          def __init__(self,name,score):
              self.name = name
              self.score = score
      
      
      zhangsan = Student('zhangsan',98)
      lisi = Student('lisi',98)
      zhangsan.age =  22
      lisi.aaa = 333
      print(zhangsan.age)
      print(lisi.aaa)
      print(zhangsan.aaa)
      #输出
      22
      333
      Traceback (most recent call last):
        File "d:\\工作\\work\\py\\test02.py", line 15, in <module>
          print(zhangsan.aaa)
      AttributeError: 'Student' object has no attribute 'aaa'
          
          
      因为可以绑定任何数据,所有说'zhangsan'拥有name,score,age三个变量,而'lisi'则拥有name,score,aaa三个变量,因为是实例是相互独立的,所有'zhangsan''lisi'之间的变量数量、变量名等都不是互通的,所以zhangsan在最后调用aaa变量时报错了
      

四、访问限制

  • 类(class)中,可以有属性和方法,而外部代码可以通过调用实例变量的方法来操作或获取数据,从而隐藏了内部的复杂逻辑

  • 但是,从上面Student类的定义来看,外部代码可以随意改变一个实例的属性,例如:

    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.name = name
    ...             self.score = score
    ...     def print_score(self):
    ...             return print('%s : %s' % (self.name,self.score)) 
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.print_score()
    zhangsan : 98
    >>> zhangsan.score = 22   #修改score的值
    >>> zhangsan.print_score()           #再次调用,发现值已经变了
    zhangsan : 22
    
  • 可以看到实例属性的值是可以随意修改的,如果想要实例的内部属性不被外部修改,可以这样做:

    #可以在属性的名称前面加"__",在Python中,实例的变量名如果以__开头的话,那么这个变量就成了私有变量,只有内部可以访问,外部无法访问
    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.__name = name
    ...             self.__score = score
    ...     def print_score(self):
    ...             return print('%s : %s' % (self.__name,self.__score)) 
    ... 
    >>> zhangsan = Student('zhangsan',98)
    >>> zhangsan.print_score()
    zhangsan : 98
        
    #现在想对Student类中的score属性的值进行修改,再调用print_score方法时,发现并没有修改,最后发现,其实这相当于是新创建了一个score属性
    >>> zhangsan.score = 22   
    >>> zhangsan.print_score()   
    zhangsan : 98
    >>> zhangsan.score
    22
    
    #上面不行的话,有的人可能会说是因为属性名称不一样,那么现在来调用一下__score属性,发现也无法调用,这是因为在使用私有变量后,Python解释器就把__name的对外名称变成了_Student__score,使用这样的格式进行调用,是可以调用成功的,不过强烈建议不要使用这种方法进行修改、调用属性数据
    >>> zhangsan.__score 
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute '__score'. Did you mean: 'score'?
    >>> zhangsan._Student__score 
    98
    
    #在不使用_Student__score这种格式去访问、修改指定的属性时,可以修改一下类,例如
    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.__name = name
    ...             self.__score = score
    ...     def print_score(self):
    ...             return print('%s : %s' % (self.__name,self.__score))
    ...     def set_name(self,name):
    ...             self.__name = name
    ...     def set_score(self,score):
    ...             self.__score = score
    ...     def get_name(self):
    ...             return print(self.__name) 
    ...     def get_score(self):
    ...             return print(self.__score) 
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.print_score()
    zhangsan : 98
    >>> zhangsan.get_name()    
    zhangsan
    >>> zhangsan.set_name('lisi') 
    >>> zhangsan.get_name()       
    lisi
    
    #虽然原先的"zhangsan.name = 98"也可以之间进行修改,但是通过在类中添加方法可以进行参数控制,例如:
    >>> class Student(object):
    ...     def __init__(self,name,score):
    ...             self.__name = name
    ...             self.__score = score
    ...     def set_score(self,score):        
    ...             if score >= 90 and score <=100:
    ...                     self.__score = score
    ...             else:
    ...                     return print('error')
    ...     def get_score(self):
    ...             return self.__score
    ... 
    >>> zhangsan = Student('zhangsan',98) 
    >>> zhangsan.get_score()  
    98
    >>> zhangsan.set_score(80) 
    error
    >>> zhangsan.get_score()   
    98
    >>> zhangsan.set_score(96) 
    >>> zhangsan.get_score()   
    96
    
    可以看到在'Student'类中,'set_score'方法添加了参数控制
    
  • 注意:

    1. 在Python中,变量名称类似于__xx__这样的以双下划线开头和结尾的是特殊变量,特殊变量是可以直接访问的,并不是私有变量
    2. 以一个下划线开头的,类似于_name这样的变量,是可以被外部访问的,但是在看到这样的变量时,要把它当成私有变量,不要随意访问,这也是一个不成文的规定
    3. 根据拥有私有变量的类去创建的实例,在调用时,不要直接使用类似于zhangsan.__score 这样的,因为虽然在类中定义的是__score,但其实Python解释器对外的名称是_Student__score,所以直接调用或修改zhangsan.__score,其实是调用的是另外一个变量

五、特性之二、三——继承和多态

-继承