Python(8)面向对象编程(上)

简介: Python(8)面向对象编程

一、什么是面向对象编程


面向对象编程(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)则指一个个具体的对象,例如上面的zhangsan和lisi就是两个具体的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__方法,在创建实例的时候,把name和score等属性绑定,例如:


>>> 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类中,根据对象创建的实例里,都有各自的name和score的数据,可以通过函数来访问这些数据,例如:

>>> 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

总结:


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


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


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


和静态语音不同,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'方法添加了参数控制

注意:


在Python中,变量名称类似于__xx__这样的以双下划线开头和结尾的是特殊变量,特殊变量是可以直接访问的,并不是私有变量


以一个下划线开头的,类似于_name这样的变量,是可以被外部访问的,但是在看到这样的变量时,要把它当成私有变量,不要随意访问,这也是一个不成文的规定


根据拥有私有变量的类去创建的实例,在调用时,不要直接使用类似于zhangsan.__score 这样的,因为虽然在类中定义的是__score,但其实Python解释器对外的名称是_Student__score,所以直接调用或修改zhangsan.__score,其实是调用的是另外一个变量


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


-继承


  • 在上面的内容中,有说到过继承这一概念,例如:


class Student(object)


在这里Student(object)的object就是Student继承的类,也就是说,我们在定义类时,是可以继承现有的类的,创建的新类叫做子类(subclass),而被继承的类叫做父类,也可以叫做基类、超类


下面来看几个案例:

- 定义一个'Animal'类,添加一个'run'方法
>>> class Animal(object):
...     def run(self):
...             return print('Animal is running!!!')
... 
- 定义'Dog'和'Cat'类,两个类都继承'Animal'类
>>> class Dog(Animal):
...     pass
... 
>>> class Cat(Animal):
...     pass
... 
- 创建'Dog'和'Cat'的实例,发现创建后的实例是可以直接使用'Animal'的方法的
>>> dog = Dog()
>>> dog.run()
Animal is running!!!
>>> cat = Cat()
>>> cat.run()
Animal is running!!!
  • 对于上面的案例来说,DogCat就是Animal的子类,AnimalDogCat的父类
  • 可以看到在DogCat继承Animal后,是可以直接使用Animal中的方法的,这也是继承最大的好处:子类可以获得父类的全部功能
  • 当然,也可以对子类修改或增加一些方法,例如:
>>> class Dog(Animal):
...     def run(self): 
...             return print('Dog is running!!!')
...     def eat(self):
...             return print('Dog is Eating meat') 
... 
- 需要注意的是,在修改完'Dog'类之后,直接调用'dog.run'变量,值还是原来的,需要重新创建实例
>>> dog.run()
Animal is running!!!
>>> dog = Dog()
>>> dog.run()   
Dog is running!!!
>>> dog.eat()  
Dog is Eating meat

在对子类添加、修改方法后,可以看到创建实例并调用时,输出的run变量的值变成了子类Dog中的run方法的输出,而不是Animal类中的run方法的输出,这是因为:当子类和父类同时存在相同的方法时,在调用子类的时候,子类的方法会覆盖掉父类的方法。


例如:Dog是Animal的子类,并且Dog和Animal类中都有run的方法,在这种情况下调用Dog的实例dog.run(),输出的是Dog类中run方法的输出,而不是Animal的

目录
相关文章
|
8月前
|
Python
Python编程作业五:面向对象编程
Python编程作业五:面向对象编程
85 1
|
3月前
|
Java C# Python
Python学习七:面向对象编程(中)
这篇文章是关于Python面向对象编程的中级教程,涵盖了析构函数、对象的三大特征(封装、继承、多态)、类属性与实例属性、以及类方法与静态方法的对比。
44 2
|
8月前
|
Python
【Python进阶(三)】——面向对象编程
【Python进阶(三)】——面向对象编程
|
3月前
|
设计模式 安全 JavaScript
Python学习八:面向对象编程(下):异常、私有等
这篇文章详细介绍了Python面向对象编程中的私有属性、私有方法、异常处理及动态添加属性和方法等关键概念。
40 1
|
4月前
|
存储 Java 程序员
30天拿下Python之面向对象编程
30天拿下Python之面向对象编程
26 3
|
4月前
|
Java Python
全网最适合入门的面向对象编程教程:50 Python函数方法与接口-接口和抽象基类
【9月更文挑战第18天】在 Python 中,虽无明确的 `interface` 关键字,但可通过约定实现类似功能。接口主要规定了需实现的方法,不提供具体实现。抽象基类(ABC)则通过 `@abstractmethod` 装饰器定义抽象方法,子类必须实现这些方法。使用抽象基类可使继承结构更清晰、规范,并确保子类遵循指定的方法实现。然而,其使用应根据实际需求决定,避免过度设计导致代码复杂。
|
4月前
|
Python
全网最适合入门的面向对象编程教程:Python函数方法与接口-函数与方法的区别和lamda匿名函数
【9月更文挑战第15天】在 Python 中,函数与方法有所区别:函数是独立的代码块,可通过函数名直接调用,不依赖特定类或对象;方法则是与类或对象关联的函数,通常在类内部定义并通过对象调用。Lambda 函数是一种简洁的匿名函数定义方式,常用于简单的操作或作为其他函数的参数。根据需求,可选择使用函数、方法或 lambda 函数来实现代码逻辑。
|
3月前
|
Java Python
Python学习六:面向对象编程(上)
这篇文章是关于Python面向对象编程的基础知识,包括类和对象的概念、实例方法、属性、self关键字以及魔法方法等。
28 0
|
5月前
|
Python
Python 中的面向对象编程 (OOP)
【8月更文挑战第29天】
52 7
|
5月前
|
机器学习/深度学习 PHP 开发者
探索PHP中的面向对象编程构建你的首个机器学习模型:以Python和scikit-learn为例
【8月更文挑战第30天】在PHP的世界中,面向对象编程(OOP)是一块基石,它让代码更加模块化、易于管理和维护。本文将深入探讨PHP中面向对象的魔法,从类和对象的定义开始,到继承、多态性、封装等核心概念,再到实战中如何应用这些理念来构建更健壮的应用。我们将通过示例代码,一起见证PHP中OOP的魔力,并理解其背后的设计哲学。

热门文章

最新文章