python学习笔记---面向对象编程【廖雪峰】
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
面向对象编程
面向对象编程——Object Oriented Programming简称OOP是一种程序设计思想。OOP把对象作为程序的基本单元一个对象包含了数据和操作数据的函数。
面向过程 VS 面向对象
●面向过程的程序设计把计算机程序视为一系列的命令集合即一组函数的顺序执行。为了简化程序设计面向过程把函数继续切分为子函数即把大块函数通过切割成小块函数来降低系统的复杂度。
●而面向对象的程序设计把计算机程序视为一组对象的集合而每个对象都可以接收其他对象发过来的消息并处理这些消息计算机程序的执行就是一系列消息在各个对象之间传递。
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))
# 给对象发消息[对象的方法Method]
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
类和实例
类中定义的函数
①可以在创建实例的时候把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__
方法在创建实例的时候就把name
score
等属性绑上去
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
__init__
方法的第一个参数永远是self
表示创建的实例本身因此在__init__
方法内部就可以把各种属性绑定到self
因为self
就指向创建的实例本身。
②和普通的函数相比在类中定义的函数只有一点不同就是第一个参数永远是实例变量self
并且调用时不用传递该参数。除此之外类的方法和普通函数没有什么区别所以你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
数据封装
面向对象编程的三大特征之一。本质就是对象内部数据的操作细节对外不暴露实例只需要进行传参就可以实现对对象的属性进行相关的操作无需知道方法内部的实现细节。
访问限制
NO1
# 外部代码还是可以自由地修改一个实例的name、score属性
>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99
NO2
**让内部属性不被外部访问可以把属性的名称前加上两个下划线__
**在Python中实例的变量名如果以__
开头就变成了一个私有变量private只有内部可以访问外部不能访问所以我们把Student类改一改
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))
改完后对于外部代码来说没什么变动但是已经无法从外部访问实例变量.__name
和实例变量.__score
了
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
这样就确保了外部代码不能随意修改对象内部的状态这样通过访问限制的保护代码更加健壮。
NO3
如果外部代码要获取name和score怎么办可以给Student类增加get_name
和get_score
这样的方法
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
NO4
允许外部代码修改score怎么办可以再给Student类增加set_score
方法
class Student(object):
...
def set_score(self, score):
self.__score = score
你也许会问原先那种直接通过
bart.score = 99
也可以修改啊为什么要定义一个方法大费周折因为在方法中可以对参数做检查避免传入无效的参数class Student(object): ... def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score')
约定俗成
①在Python中变量名类似__xxx__
的也就是以双下划线开头并且以双下划线结尾的是特殊变量特殊变量是可以直接访问的不是private变量所以不能用__name__
、__score__
这样的变量名。
②你会看到以一个下划线开头的实例变量名比如_name
这样的实例变量外部是可以访问的但是按照约定俗成的规定当你看到这样的变量时意思就是“虽然我可以被访问但是请把我视为私有变量不要随意访问”。
双下划线开头的实例变量也可以从外部访问
不能直接访问
__name
是因为Python解释器对外把__name
变量改成了_Student__name
[不同版本的Python解释器可能会把__name
改成不同的变量名]所以仍然可以通过_Student__name
来访问__name
变量>>> bart._Student__name 'Bart Simpson'
最后注意下面的这种错误写法
>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量
>>> bart.__name
'New Name'
表面上看外部代码“成功”地设置了__name
变量但实际上这个__name
变量和class内部的__name
变量不是一个变量内部的__name
变量已经被Python解释器自动改成了_Student__name
而外部代码给bart
新增了一个__name
变量。不信试试
>>> bart.get_name() # get_name()内部返回self.__name
'Bart Simpson'
继承和多态
在OOP程序设计中当我们定义一个class的时候可以从某个现有的class继承新的class称为子类Subclass而被继承的class称为基类、父类或超类Base class、Super class。
继承可以把父类的所有功能都直接拿过来这样就不必重零做起【继承】子类只需要新增自己特有的方法也可以把父类不适合的方法覆盖重写【多态】。
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal): # 由于Animial实现了run()方法因此Dog作为它的子类什么事也没干就自动拥有了父类的run()方法
pass
class Cat(Animal):
def run(self): # 多态实例对象调用run()实际上调用的是自己的
print('Cat is running...')
def run_twice(animal):
animal.run()
animal.run()
class Timer(object):
def run(self):
print('Start...')
run_twice(Dog()) # 由于Animial实现了run()方法因此Dog作为它的子类什么事也没干就自动拥有了父类的run()方法
run_twice(Cat()) # 多态实例对象调用run()实际上调用的是自己的【子类的run()覆盖了父类的run()】
run_twice(Timer()) # 体现了“鸭子类型”即为只要传进去的是一个对象并且该对象有run()就可以体现出run()的特性
获取对象信息
当我们拿到一个对象的引用时如何知道这个对象是什么类型、有哪些方法呢
使用type()
基本类型都可以用type()
判断
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
如果一个变量指向函数或者类也可以用type()
判断
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
①判断基本数据类型可以直接写int
str
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
②判断一个对象是否是函数怎么办可以使用types
模块中定义的常量
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
使用isinstance()
对于class的继承关系来说使用type()
就很不方便。我们要判断class的类型可以使用isinstance()
函数。
涉及到一个向上继承向下多态
继承关系是
object -> Animal -> Dog -> Husky >>> a = Animal() >>> d = Dog() >>> h = Husky()
>>> isinstance(h, Husky) True >>> isinstance(h, Dog) True >>> isinstance(h, Animal) True >>> isinstance(d, Husky) False
并且还可以判断一个变量是否是某些类型中的一种比如下面的代码就可以判断是否是list或者tuple
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
总是优先使用isinstance()判断类型可以将指定类型及其子类“一网打尽”。
使用dir()
如果要**获得一个对象的所有属性和方法可以使用dir()
函数它返回一个包含字符串的list**比如获得一个str对象的所有属性和方法
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
类似__xxx__
的属性和方法在Python中都是有特殊用途的比如__len__
方法返回长度。在Python中如果你调用len()
函数试图获取一个对象的长度实际上在len()
函数内部它自动去调用该对象的__len__()
方法所以下面的代码是等价的
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
所以我们是可以对这些内置函数进行改写的
通过内置的一系列getattr()
、setattr()
以及hasattr()
函数我们可以对任意一个Python对象进行剖析拿到其内部的数据,直接操作一个对象的状态。
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
>>> hasattr(obj, 'x') # 有属性'x'吗
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
>>> getattr(obj, 'z', 404) # 获取属性'z'如果不存在返回默认值404
404
>>> hasattr(obj, 'power') # 有属性'power'吗
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
实际用途
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
假设我们希望从文件流fp中读取图像我们首先要判断该fp对象是否存在read方法如果存在则该对象是一个流如果不存在则无法读取。hasattr()
就派上了用场。
请注意在Python这类动态语言中根据鸭子类型有read()
方法不代表该fp对象就是一个文件流它也可能是网络流也可能是内存中的一个字节流但只要read()
方法返回的是有效的图像数据就不影响读取图像的功能。
实例属性和类属性
在编写程序的时候千万不要对实例属性和类属性使用相同的名字因为相同名称的实例属性将屏蔽掉类属性【实例属性优先级比类属性高】但是当你删除实例属性后再使用相同的名称访问到的将是类属性。
①给实例绑定属性的方法是通过实例变量或者通过self
变量
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
直接在class中定义属性这种属性是类属性归Student
类所有
class Student(object):
name = 'Student'
当我们定义了一个类属性后这个属性虽然归类所有但类的所有实例都可以访问到。来测试一下
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性因为实例并没有name属性所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高因此它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name由于实例的name属性没有找到类的name属性就显示出来了
Student