07 Python 面向对象编程(OOP)
By Kevin Song
- 07-01 类和实例
- 07-02 访问限制
- 07-03 继承和多态
- 07-04 获取对象信息
- 07-05 实例属性和类属性
面向过程 的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度
面向对象 的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递
07-01 类(Class)和实例(Instance)
Python定义类(Class)
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))
Python创建对象并调用内部函数访问参数
kevin = Student('Kevin', 86)
kevin.print_score1()
数据封装
函数内部方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入
class Student(object):
...
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
>>> kevin.get_grade()
'B'
外部函数访问参数
>>> def print_score(std):
... print('%s: %s' % (std.name, std.score))
...
>>> print_score(kevin)
kevin: 86
07-02 访问限制
为了让内部name和score属性不被外部访问,需要通过两个下划线 “__” 私有化属性
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_name(self):
return self.__name
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score
私有变量(private),只有内部可以访问,外部不能访问
>>> kevin = Student('Kevin', 86)
>>> kevin.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
私有变量的好处:可以对参数做检查,避免传入无效的参数
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
- __xxx__
- 特殊变量,可以直接访问
- xxx
- 公开变量,可以直接访问
- _xxx
- 私有变量,可以直接访问,但是按照规定,请不要随意访问
- __xxx
- 私有变量,不可以直接访问,可以通过_类名__xxx强制访问(不建议)
07-03 继承和多态
- 子类Dog和Cat 继承 父类Animal
- 子类Dog和Cat 重写 父类run方法
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
>>> dog = Dog()
>>> dog.run()
Dog is running...
>>> cat = Cat()
>>> cat.run()
Cat is running...
- 多态: dog是Dog也是Animal
多态的优点:新增一个Animal的子类,不必对run_twice()做任何修改,就会自动调用实际类型的run()方法
def run_twice(animal):
animal.run()
animal.run()
>>> run_twice(Dog())
Dog is running...
Dog is running...
>>> run_twice(Cat())
Cat is running...
Cat is running...
“开闭”原则:
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
静态语言 vs 动态语言
静态语言(Java):如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法
动态语言(Python):不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就行,因此继承不是必须的
比如Timer也可以传入run_twice()方法
class Timer(object):
def run(self):
print('Start...')
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子
07-04 获取对象信息
- type()
- 返回对应Class类型
- isinstance()
- 返回布尔类型
- dir()
- 返回包含字符串的list
使用type()
判断对象类型
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
判断方法类型
>>> type(abs)
<class 'builtin_function_or_method'>
判断类类型
>>> type(a)
<class '__main__.Animal'>
type()函数返回的是对应的Class类型
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
使用isinstance()
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)
True
>>> isinstance(h, Animal)
True
判断一个变量是否是某些类型中的一种
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
使用dir()
>>> dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__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', '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']
调用len()函数试图获取一个对象的长度时,实际上len()函数内部自动去调用该对象的len()方法
下面代码等价
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
直接操作对象状态
- getattr(对象,’属性’)
- setattr(对象,’属性’)
- hasattr(对象,’属性’)
>>> 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
注意:
如果可以直接写
sum = obj.x + obj.y
就不要写
sum = getattr(obj, 'x') + getattr(obj, 'y')
原因
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
从文件流fp中读取图像,首先判断该fp对象是否存在read方法
- 如果存在,则该对象是一个流
- 如果不存在,则无法读取。
07-05 实例属性和类属性
给实例绑定属性:通过self变量
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
给类绑定属性:直接在class中定义属性
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
注意:
不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当删除实例属性后,再使用相同的名称,访问到的将是类属性