### @property装饰器 之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。 ```Python class Person(object): def __init__(self, name, age): self._name = name self._age = age # 访问器 - getter方法 @property def age(self): return self._age # 修改器 - setter方法 @age.setter def age(self, age): self._age = age def main(): person = Person('王大锤', 12) person.age = 22 # person.name = '白元芳' # AttributeError: can't set attribute if __name__ == '__main__': main() ``` ### \_\_slots\_\_魔法 我们讲到这里,不知道大家是否已经意识到,Python是一门[动态语言](https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%AF%AD%E8%A8%80)。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义\_\_slots\_\_变量来进行限定。需要注意的是\_\_slots\_\_的限定只对当前类的对象生效,对子类并不起任何作用。 ```Python class Person(object): # 限定Person对象只能绑定_name, _age和_gender属性 __slots__ = ('_name', '_age', '_gender') def __init__(self, name, age): self._name = name self._age = age ``` ### 静态方法和类方法 之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。 ```Python from math import sqrt class Triangle(object): def __init__(self, a, b, c): self._a = a self._b = b self._c = c @classmethod def create_basic(cls): return cls(1,2,3) @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 main(): a, b, c = 3, 4, 5 # 静态方法和类方法都是通过给类发消息来调用的 print(True if Triangle.is_valid(a, b, c)) triangle = Triangle.create_basic() print(triangle.perimeter()) #6 if __name__ == '__main__': main() ``` ### 继承和多态 刚才我们提到了,可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为[里氏替换原则](https://zh.wikipedia.org/wiki/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99)。下面我们先看一个继承的例子。 ```Python def __init__(self, name, age, grade): super().__init__(name, age) self._grade = grade ``` 子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。 ```Python from abc import ABCMeta, abstractmethod class Pet(object, metaclass=ABCMeta): """宠物""" def __init__(self, nickname): self._nickname = nickname @abstractmethod def make_voice(self): """发出声音""" pass class Dog(Pet): """狗""" def make_voice(self): print('%s: 汪汪汪...' % self._nickname) def main(): pet = Cat('凯蒂') pet.make_voice() if __name__ == '__main__': main() ``` 在上面的代码中,我们将`Pet`类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过`abc`模块的`ABCMeta`元类和`abstractmethod`包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,`Dog`和`Cat`两个子类分别对`Pet`类中的`make_voice`抽象方法进行了重写并给出了不同的实现版本,当我们在`main`函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。 #### 案例2:扑克游戏。 ```Python import random class Card(object): """一张牌""" def __init__(self, suite, face): self._suite = suite self._face = face @property def face(self): return self._face @property def suite(self): return self._suite def __str__(self): if self._face == 1: face_str = 'A' elif self._face == 11: face_str = 'J' elif self._face == 12: face_str = 'Q' elif self._face == 13: face_str = 'K' else: face_str = str(self._face) return '%s%s' % (self._suite, face_str) def __repr__(self): return self.__str__() class Poker(object): """一副牌""" def __init__(self): self._cards = [Card(suite, face) for suite in '♠♥♣♦' for face in range(1, 14)] self._current = 0 @property def cards(self): return self._cards def shuffle(self): """洗牌(随机乱序)""" self._current = 0 random.shuffle(self._cards) @property def next(self): """发牌""" card = self._cards[self._current] self._current += 1 return card @property def has_next(self): """还有没有牌""" return self._current < len(self._cards) class Player(object): """玩家""" def __init__(self, name): self._name = name self._cards_on_hand = [] @property def name(self): return self._name @property def cards_on_hand(self): return self._cards_on_hand def get(self, card): """摸牌""" self._cards_on_hand.append(card) def arrange(self, card_key): """玩家整理手上的牌""" self._cards_on_hand.sort(key=card_key) # 排序规则-先根据花色再根据点数排序 def get_key(card): return (card.suite, card.face) def main(): p = Poker() p.shuffle() players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')] for _ in range(13): for player in players: player.get(p.next) for player in players: print(player.name + ':', end=' ') player.arrange(get_key) print(player.cards_on_hand) if __name__ == '__main__': main() ``` #### 案例3:工资结算系统。 ```Python """ 某公司有三种类型的员工 分别是部门经理、程序员和销售员 需要设计一个工资结算系统 根据提供的员工信息来计算月薪 部门经理的月薪是每月固定15000元 程序员的月薪按本月工作时间计算 每小时150元 销售员的月薪是1200元的底薪加上销售额5%的提成 """ from abc import ABCMeta, abstractmethod class Employee(object, metaclass=ABCMeta): """员工""" def __init__(self, name): """ 初始化方法 :param name: 姓名 """ self._name = name @property def name(self): return self._name @abstractmethod def get_salary(self): """ 获得月薪 :return: 月薪 """ pass class Manager(Employee): """部门经理""" def get_salary(self): return 15000.0 class Programmer(Employee): """程序员""" def __init__(self, name, working_hour=0): super().__init__(name) self._working_hour = working_hour @property def working_hour(self): return self._working_hour @working_hour.setter def working_hour(self, working_hour): self._working_hour = working_hour if working_hour > 0 else 0 def get_salary(self): return 150.0 * self._working_hour class Salesman(Employee): """销售员""" def __init__(self, name, sales=0): super().__init__(name) self._sales = sales @property def sales(self): return self._sales @sales.setter def sales(self, sales): self._sales = sales if sales > 0 else 0 def get_salary(self): return 1200.0 + self._sales * 0.05 def main(): emps = [ Manager('刘备'), Programmer('诸葛亮'), Manager('曹操'), Salesman('荀彧'), Salesman('吕布'), Programmer('张辽'), Programmer('赵云') ] for emp in emps: if isinstance(emp, Programmer): emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name)) elif isinstance(emp, Salesman): emp.sales = float(input('请输入%s本月销售额: ' % emp.name)) # 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态) print('%s本月工资为: ¥%s元' % (emp.name, emp.get_salary())) if __name__ == '__main__': main() ```