面向对象编程: 把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。
面向对象思想三大要素:封装,继承和多态。
1 定义使用类
class Student(object): # __init__是一个特殊方法用于在创建对象时进行初始化操作 # 通过这个方法我们可以为学生对象绑定name和age两个属性 def __init__(self, name, age): self.name = name self.age = age def study(self, course_name): print('%s正在学习%s.' % (self.name, course_name)) # PEP 8要求标识符的名字用全小写多个单词用下划线连接 # 但是部分程序员和公司更倾向于使用驼峰命名法(驼峰标识) def watch_movie(self): if self.age < 18: print('%s只能观看海贼王' % self.name) else: print('%s正在观看动漫' % self.name) test = Student("非常君", 18 ) test.study("python") test.watch_movie()
2 访问可见性问题
在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,Python 中将成员和方法私有化的方式是在成员名或者方法名前面加两个下划线 ,Python 中访问私有成员变量的正确方式为:实例类.类名_变量名 (不建议使用)。
class PrivateTest: __name = "private" if __name__ == "__main__": pt = PrivateTest() print(pt._PrivateTest__name)
在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻。
Python内置的@property
装饰器就是负责把一个方法变成属性调用的。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便
class Person(object): def __init__(self, name, age): self._name = name self._age = age # 访问器 - getter方法 @property def name(self): return self._name # 访问器 - getter方法 @property def age(self): return self._age # 修改器 - setter方法 @age.setter def age(self, age): self._age = age def play(self): if self._age <= 16: print('%s正在玩飞行棋.' % self._name) else: print('%s正在玩斗地主.' % self._name) def main(): person = Person('王大锤', 12) person.play() person.age = 22 person.play() # person.name = '白元芳' # AttributeError: can't set attribute if __name__ == '__main__': main()
__slots__魔法
我们讲到这里,不知道大家是否已经意识到,Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
class Person(object): # 限定Person对象只能绑定_name, _age和_gender属性 __slots__ = ('_name', '_age', '_gender') def __init__(self, name, age): self._name = name self._age = age @property def name(self): return self._name @property def age(self): return self._age @age.setter def age(self, age): self._age = age def play(self): if self._age <= 16: print('%s正在玩飞行棋 %s.' %(self._name, self._gender)) else: print('%s正在玩斗地主 %s ' %(self._name, self._gender)) if __name__ == "__main__": person = Person('王大锤', 22) person._gender = '男' person.play()
3 静态方法和类方法
之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。
from math import sqrt class Triangle(object): def __init__(self, a, b, c): self._a = a self._b = b self._c = c @staticmethod def is_valid(a, b, c): return a + b > c and b + c > a and a + c > b def perimeter(self): return self._a + self._b + self._c def area(self): half = self.perimeter() / 2 return sqrt(half * (half - self._a) * (half - self._b) * (half - self._c)) def main(): a, b, c = 3, 4, 5 # 静态方法和类方法都是通过给类发消息来调用的 if Triangle.is_valid(a, b, c): t = Triangle(a, b, c) print(t.perimeter()) # 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数 # print(Triangle.perimeter(t)) print(t.area()) # print(Triangle.area(t)) else: print('无法构成三角形.') if __name__ == '__main__': main()
4 继承和多态
刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为里氏替换原则。下面我们先看一个继承的例子。
class Person(object): def __init__(self, name, age): self._name = name self._age = age @property def name(self): return self._name @property def age(self): return self._age @age.setter def age(self, age): self._age = age def study(self): print('%s study python ing '% self._name) def eating(self): if self._age >= 18: print('%s eat banana ing ' % self._name) else: print('%s eat watermelon ing' % self._name) class Student(Person): def __init__(self, name, age, grade): super().__init__(name, age) self._grade = grade @property def grade(self): return self._grade @grade.setter def grade(self, grade): self._grade = grade def study(self, course): print('%s的%s正在学习%s.' % (self._grade, self._name, course)) class Teacher(Person): def __init__(self, name, age, title): super().__init__(name, age) self._title = title @property def title(self): return self._title @title.setter def title(self, title): self._title = title def teach(self, course): print('%s%s正在讲%s.' % (self._name, self._title, course)) if __name__ == '__main__': stu = Student('wangdachui', 15, '初三') stu.study('数学') stu.eating() t = Teacher('lancer', 38, 'loser') t.teach('Python程序设计') t.eating()