面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。 在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。 面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
类和实例
面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。 在Python中,定义类是通过class关键字,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的 创建实例是通过类名+()实现 Python与静态语言不同的是可以自由地给一个实例变量绑定属性,两个实例变量可能都是同一个类的不同实例,但拥有的属性名称都可以不同 通过定义一个特殊的__init__方法,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数 和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数
class Student(object):
pass
bart = Student()
bart.name = 'Bart Simpson'
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
bart = Student('Bart Simpson', 59)
数据封装
在Student类的内部定义访问数据的函数,这样就把“数据”给封装起来了。封装数据的函数是和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))
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮 如果外部代码要获取和修改属性,可以增加getter和setter 在Python中,变量名类似__xxx__,以双下划线开头并且以双下划线结尾的是特殊变量,特殊变量是可以直接访问的,不是private变量 以一个下划线开头的实例变量名按照约定俗成的规定“视为私有变量” 双下划线开头的实例变量也可以通过_(Classname)__(privateproperty)的形式访问,如可以通过_Student__name来访问__name变量,但是强烈不建议这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。 总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。
练习
请把下面的Student对象的gender字段对外隐藏起来,用get_gender()和set_gender()代替,并检查参数有效性:
class Student(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def get_gender(self):
return self.gender
def set_gender(self, gender):
if (gender=='male') or (gender=='female'):
self.gender = gender
else:
print('Illegal Argument!')
继承和多态
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。 继承获得了父类的所有功能,并且可以实现多态 对于一个变量,我们只需要知道它的父类类型,无需确切地知道它的子类型,就可以放心地调用方法,而具体调用的方法是作用在哪一个对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种父类的子类时,只要确保方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则: 对扩展开放:允许新增子类; 对修改封闭:不需要修改依赖父类类型的方法函数
鸭子(duck)类型
动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的,不用传入父类类型,只要传入的对象有一个所必须的方法即可
获取对象信息
获取对象的类型和方法:使用type()函数判断对象类型,对于class的继承关系可以使用isinstance()函数 总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽” 要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
>>> dir('123')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果调用len()函数试图获取一个对象的长度,实际上在len()函数内部会自动去调用该对象的__len__()方法 配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:has判断有没有该属性,get获得该属性,set设置属性
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
>>> hasattr(obj, 'x')
True
>>> obj.x
9
>>> hasattr(obj, 'y')
False
>>> setattr(obj, 'y', 19)
>>> hasattr(obj, 'y')
True
>>> getattr(obj, 'y')
19
>>> obj.y
19
>>> getattr(obj, 'z')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
>>> getattr(obj, 'z', 404)
404
>>> hasattr(obj, 'power')
True
>>> getattr(obj, 'power')
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power')
>>> fn
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn()
81
只有在不知道对象信息的时候,我们才会去获取对象信息 假设希望从文件流fp中读取图像,首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。 请注意,在Python这类动态语言中,根据鸭子类型,有read()方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能。
实例属性和类属性
Python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量,或者通过self变量 如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有,当定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。类似于Java中的static member 在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
class Student(object):
name = 'Student'
练习
为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
class Student(object):
count = 0
def __init__(self, name):
self.name = name
Student.count = Student.count + 1
|