Python学习笔记(八)之面向对象编程(上)
1. 面向过程和面向对象的区别
1.1 面向过程
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
1.2 面向对象
面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
-
面向对象的设计思想是从自然界中来的,在自然界中
类(class)
和实例(instance)
的概念是很自然的。-
来个实例就明白了:
# !user/bin/env python # coding=utf-8 __author__ = "zjw" class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_information(self): print self.name, self.score if __name__ == '__main__': stu = Student("Kangkang", 12) stu.print_information()
-
输出:
Kangkang 12
-
Class
是一种抽象概念,比如我们自定义一个学生(Student)类,包含学生姓名(name)和成绩(score)的类。 -
Instance
则是一个个具体的Student,比如stu
就是一个具体的Student
对象。
-
2. 类(Class) 和实例(Instance)
-
例2.1:
# !user/bin/python # coding=utf-8 __author__ = "zjw" # 定义的一个Person类 class Person(object): def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def print_information(self): print self.name, self.age, self.gender if __name__ == '__main__': # 实例化一个新的Person person = Person("Michael", 20, "男") person.print_information()
-
输出:
Michael 20 男
-
分析:
class
关键字后面跟的是一个类名,即Person
,类名通常规定大写字母开头。(object)
表示该类继承自object
类。通常没有合适的继承类,就直接继承object
类,这是所有类最终都会继承的类。__init__
是一个特殊方法,前后都有两个下划线。__init__
方法的第一个参数永远是self
,表示创建的实例本身,在__init__
方法内部,我们就可以将各种属性绑定到self
。- 注:有了
__init__
方法,创建实例时就不能传入空的参数了,必须传入与该方法匹配的参数,self
不需要传入。 - 如果想要传入的参数不受数量限制可以用
def __init__(self, **pro)
来进行传入的传递,或则直接不写__init__
方法。
- 注:有了
- 数据封装
- 面向对象的一个重要特点就是
数据封装
。 - 在上面
Person
类中定义一个print_information
方法,用于实例后的具体对象访问自身的属性内容。当然我们可以通过在外面写一个函数来实现,但是Person
实例本身就有这些数据,难道我们的个人信息需要别人来帮我们介绍吗,况且有些信息我们还想保密起来的,所以说访问类内部的属性就没必要从外面的函数去访问,可以直接在Person
类内部定义一个方法实现,即我们上面定义的一个print_information
方法,这样,我们就把数据给封装起来了。 - 之所以在这里不叫
print_information
函数,是因为该封装数据的函数是和Person
类本身是关联起来的,所以称之为类的方法
。 - 这样以来,我们在main函数中,创建实例时需要传入
name
,age
和gender
三个参数,而打印这个实例的各个属性,都是Person
内部数据封装
的事情了,这些数据和逻辑被“封装”起来了,调用起来容易,且外部调用者不需要考虑内部实现的细节。
- 面向对象的一个重要特点就是
3. 访问限制
3.1 思考
- 在java中,我们一般将Class内部的属性设置为private型,不让外部直接改变,而是通过public的setter和getter方法来改变和调用,那么我们在Python中该如何实现呢?
- 当我们想通过某种规则改变实例中的某个属性时,我们不必取得该属性,而是应该在该类中存在某种对应方法来改变,而如果我们是通过外部操作来改变,就不是实例该做的事情了,说明实例的行为受到了外部的干扰,强制改变了,这还有人性吗?所以一般情况下,我们会将实例中的属性设置为private型,来限制外部的随意改变,且设置一些方法来对实例中的属性进行相应的操作。
3.2 实例解析
-
在Class内部,有属性和方法,而外部的代码可以直接通过变量直接访问实例的方法来操作数据,这样我们可以影藏内部的复杂逻辑。
-
我们来看上面的Person类,因为没有多加限制,所以我们访问属性时,可以直接通过实例化后的变量名.属性名就可以直接访问和修改内部的属性值,按上面的思考这是很不合理的。如果想让内部的属性不被外部访问,这就涉及到了
作用域
,前面我们已经学到作用域的相关知识,是在变量前面加上两个下划线,即__
。 -
因此根据
作用域
的相关知识,我们运用到类内部就是在原来定义的属性名前面加上两个下划线__
,即属性就变成一个类内部的私有属性,即java中的private型。 -
说了这么多,我们何不改变一下上面的Person类来试一下咯。
-
实例3.2.1:
# !user/bin/python # coding=utf-8 __author__ = "zjw" # 定义的一个Person类 class Person(object): def __init__(self, name, age, gender): self.__name = name self.__age = age self.__gender = gender def print_information(self): print self.__name, self.__age, self.__gender if __name__ == '__main__': # 实例化一个新的Person person = Person("Michael", 20, "男") person.print_information()
-
输出:
Michael 20 男
-
分析:可以看到,我将
__init__
方法内部的属性都改成了私有属性,当然我们在外部实例的person对象调用print_information
方法是没有问题,但是如果我们通过person.__name
,那么很遗憾系统会报错。不妨来试一下吧。Traceback (most recent call last):
File "G:/PyCharm-workspace/Python_Learning/person.py", line 23, in
person.__name
AttributeError: 'Person' object has no attribute '__name'这些是用到
person.__name
时的报错信息。 -
改进思想:
-
刚刚我将各个属性改成了private型,那么我们该如何访问和修改实例的属性值呢?这就和java中的setter和getter方法一个道理了。我们可以写相应的方法来访问和修改相应的属性。
-
经过修改后的
Person
类是下面这样子的:# !user/bin/python # coding=utf-8 __author__ = "zjw" # 定义的一个Person类 class Person(object): def __init__(self, name, age, gender): self.__name = name self.__age = age self.__gender = gender def print_information(self): print self.__name, self.__age, self.__gender def get_name(self): return self.__name def get_age(self): return self.__age def get_gender(self): return self.__gender def set_name(self, name): self.__name = name def set_age(self, age): self.__age = age def set_gender(self, gender): self.__gender = gender if __name__ == '__main__': # 实例化一个新的Person person = Person("Michael", 20, "男") # 我们先打印一下此时的person实例 person.print_information() # 然后我们在通过相应的setter方法修改一下person实例中的相应属性值 person.set_name("Jane") person.set_age(12) person.set_gender("女") # 最后我们在通过相应的getter方法来获得此时person类中的相应属性值 # 我们用print来打印getter方法获得的属性值 print person.get_name(), person.get_age(), person.get_gender()
-
输出:
Michael 20 男
Jane 12 女 -
分析:我们可以看到,上面的
Person
类中,为三个属性name,age和gender都添加了相应的setter
和getter
方法,那么在main函数中我们就可以通过设置好的setter,getter方法自如的访问和修改person类中的相应的属性值。
-
-
3.3 注意事项
-
当我们将属性值设置成private型时,我们是否就无法通过直接获得属性值了,其实并不是,我们可以通过下面的方法获得。依旧以上面的例子延续下来,我们可以通过
_Person__name
来获得此时的person
类的__name
属性值。来试一下吧(修改一下上面的main函数):-
例3.3.1:
if __name__ == '__main__': # 实例化一个新的Person person = Person("Michael", 20, "男") print person._Person__name
-
输出:
Michael
-
可以看到我们通过
_Person__name
变量来获取到了__name的属性值,冒充了get_name()
方法,真是罪不可恕,浑水摸鱼的做法啊,所以这种方法是非常不推荐,应该说是禁止使用的。且在编译器中,我们会看到一些提示信息也建议我们修改掉这种用法。Python解释器本身不会阻止我们干这种坏事,一切全靠我们自觉咯。
-
-
接下来再说另一个错误示范(同样是修改了上面的main方法):
-
例3.3.2:
if __name__ == '__main__': # 实例化一个新的Person person = Person("Michael", 20, "男") # 我们先打印一下此时person的__name print person.get_name() # 我们通过一种错误的示范来修改__name值 person.__name = "NewName" # 再次打印__name值 print person.get_name() # 通过person.__name来打印__name值试一下 print person.__name
-
输出:
Michael
Michael
NewName -
分析:可以看到,上面的实例中我们通过
person.__name = "NewName"
该操作来修改__name
属性值,但是两次通过get_name
方法获得的值确实相同的,而最后我们通过person.__name
来取__name属性值时,获得的却是改变后的值,这是为什么呢? -
其实表面上,我们是成功的修改了
__name
变量,但实际上__name
变量和此时的class内部的__name
属性并不是同一变量!原来内部的变量被解释器解改成了_Person__name
,而外面我们通过该方法修改变量则是我们新设置的新的变量,所以修改的并非同一个变量,所以两次通过getter方法获得的__name
属性值是相同的。 -
所以得出结论,我们还是强烈不建议使用该方法获得和修改类中的相应的属性值。
-
4. 总结一下
- 这次学习Python的面向对象主要还是通过类比java的面向对象来学习的,通过联想java中的类,属性,方法以及实例,类比学习了Python中的面向对象,学习下来其实都是大同小异,只是在语法上存在着一些不同。
- 在设置属性为private上,通过
__
前缀实现的Python显得更加的简洁,也使我们编写的时候显得更加的明然,直接通过变量名即可判断属性和方法的公开性。 - 再则是构造函数上,java中的构造函数可以写很多个,且如果写了构造函数后,只要再写一个无参构造函数,那么外面实例化的时候就可以不传入参数也可以创建;但是Python中的构造函数
__init__
(这里暂且叫他构造函数吧),只要设置了相应的传入参数,那么在外部实例化时,则必须传入相对应的参数,也不能无参实例化,这可能是一个小小的弊端,或则说是学到这里,我并没有找到更好的方法来解决这个问题,只有上面提到的在__init__
函数的参数里用一个**pro
参数,即关键字参数
。我的一个猜想是可能Python的缔造者为了尽量简化Python代码,限制了传入的参数后,下面我们不必再一个个修改,反而增加了代码的行数。 - 接下来就是我们提到的,在Python中即使是设置成private的属性,也可以通过某种奇妙的方式来获得属性值,但是用该方式获得属性值其实是非法的,就像你的个人资料随意的让人修改一样,这是很不合理的,所以要避免使用这种操作。