@property装饰器
上一篇基础篇中的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。
class Person():def __init__(self, name, age):self._name = nameself._age = age# 访问器 - getter方法@propertydef name(self):return self._name# 访问器 - getter方法@propertydef age(self):print("age1 called")return self._age# 修改器 - setter方法@age.setterdef age(self, age):print("age2 called")self._age = agedef play(self):if self._age <= 16:print('%s正在玩飞行器' % self._name)else:print('%s正在玩斗地主' % self._name)def main():person = Person('王大锤', 12)person.play()person.age = 22person.play()# person.name = '白元芳' # AttributeError: can't set attributeif __name__ == "__main__":main()""""" 输出王大锤正在玩飞行器age2 called王大锤正在玩斗地主"""""
slots魔法
Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义 __slots__ 变量来进行限定。需要注意的是 __slots__ 的限定只对当前类的对象生效,对子类并不起任何作用。
class Person():# 限定Person对象只能绑定_name, _age和_gender属性__slots__ = ('_name', '_age', '_gender')def __init__(self, name, age):self._name = nameself._age = age# 访问器 - getter方法@propertydef name(self):return self._name# 访问器 - getter方法@propertydef age(self):return self._age# 修改器 - setter方法@age.setterdef age(self, age):self._age = agedef play(self):if self._age <= 16:print('%s正在玩飞行器' % self._name)else:print('%s正在玩斗地主' % self._name)def main():person = Person('王大锤', 12)person.play()person._gender = '男'# AttributeError: 'Person' object has no attribute '_is_gay'# person._is_gay = Trueif __name__ == "__main__":main()
静态方法和类方法
之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示
from math import sqrtclass Triangle():def __init__(self, a, b, c):self._a = aself._b = bself._c = c@staticmethoddef is_valid(a, b, c):return a + b > c and b + c > a and a + c > bdef perimeter(self):return self._a + self._b + self._cdef area(self):half = self.perimeter() / 2return 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()
和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为 cls ,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示
from time import time, localtime, sleepclass Clock(object):"""数字时钟"""def __init__(self, hour=0, minute=0, second=0):self._hour = hourself._minute = minuteself._second = second@classmethoddef now(cls):ctime = localtime(time())return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)def run(self):"""走字"""self._second += 1if self._second == 60:self._second = 0self._minute += 1if self._minute == 60:self._minute = 0self._hour += 1if self._hour == 24:self._hour = 0def show(self):"""显示时间"""return '%02d:%02d:%02d' % \(self._hour, self._minute, self._second)def main():# 通过类方法创建对象并获取系统时间clock = Clock.now()while True:print(clock.show())sleep(1)clock.run()if __name__ == '__main__':main()
继承和多态
class Person(object):"""人"""def __init__(self, name, age):self._name = nameself._age = age@propertydef name(self):return self._name@propertydef age(self):return self._age@age.setterdef age(self, age):self._age = agedef play(self):print('%s正在愉快的玩耍.' % self._name)def watch_av(self):if self._age >= 18:print('%s正在观看爱情动作片.' % self._name)else:print('%s只能观看《熊出没》.' % self._name)class Student(Person):def __init__(self, name, age, grade):super().__init__(name, age)self._grade = grade@propertydef grade(self):return self._grade@grade.setterdef grade(self, grade):self._grade = gradedef 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@propertydef title(self):return self._title@title.setterdef title(self, title):self._title = titledef teach(self, course):print('%s%s正在讲%s.' % (self._name, self._title, course))def main():stu = Student('王大锤', 15, '初三')stu.study('数学')stu.watch_av()t = Teacher('骆昊', 38, '砖家')t.teach('Python程序设计')t.watch_av()if __name__ == "__main__":main()
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)
from abc import ABCMeta, abstractclassmethodclass Pet(object, metaclass=ABCMeta):def __init__(self, nickname):self._nickname = nickname@abstractclassmethoddef make_voice(self):passclass Dog(Pet):def make_voice(self):print("%s:汪汪汪..." % self._nickname)class Cat(Pet):def make_voice(self):print("%s:喵...喵..." % self._nickname)def main():pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]for pet in pets:pet.make_voice()if __name__ == '__main__':main()
在上面的代码中,我们将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,Dog和Cat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)
实例:奥特曼打小怪兽
from abc import ABCMeta, abstractclassmethodfrom random import randint, randrangeclass Fighter(metaclass=ABCMeta):# 通过__slots__魔法限定对象可以绑定的成员变量__slots__ = ('_name', '_hp')def __init__(self, name, hp):self._name = nameself._hp = hp@propertydef name(self):return self._name@propertydef hp(self):return self._hp@hp.setterdef hp(self, hp):self._hp = hp if hp >= 0 else 0@propertydef alive(self):return self._hp > 0@abstractclassmethoddef attack(self, other):passclass Ultraman(Fighter):__slots__ = ('_name', '_hp', '_mp')def __init__(self, name, hp, mp):super().__init__(name, hp)self._mp = mpdef attack(self, other):other.hp -= randint(15, 25)def huge_attack(self, other):# 究极必杀技(打掉对方至少50点或四分之三的血)if self._mp >= 50:self._mp -= 50injury = other.hp * 3 // 4injury = injury if injury >= 50 else 50other.hp -= injuryreturn Trueelse:self.attack(other)return Falsedef magic_attack(self, others):if self._mp >= 20:self._mp -= 20for temp in others:if temp.alive:temp.hp -= randint(10, 15)return Trueelse:return Falsedef resume(self):# 恢复魔法incr_point = randint(1, 10)self._mp += incr_pointreturn incr_pointdef __str__(self):return '~~~%s奥特曼~~~\n' % self._name + \'生命值: %d\n' % self._hp + \'魔法值: %d\n' % self._mpclass Monster(Fighter):__slots__ = ('_name', '_hp')def attack(self, other):other.hp -= randint(10, 20)def __str__(self):return '~~~%s小怪兽~~~\n' % self._name + \'生命值: %d\n' % self._hpdef is_any_alive(monsters):for monster in monsters:if monster.alive > 0:return Truereturn Falsedef select_alive_one(monsters):monsters_len = len(monsters)while True:index = randrange(monsters_len)monster = monsters[index]if monster.alive > 0:return monsterdef display_info(ultraman, monsters):print(ultraman)for monster in monsters:print(monster, end='')def main():u = Ultraman('骆昊', 1000, 120)m1 = Monster('狄仁杰', 250)m2 = Monster('白元芳', 500)m3 = Monster('王大锤', 750)ms = [m1, m2, m3]fight_round = 1while u.alive and is_any_alive(ms):print('========第%02d回合========' % fight_round)m = select_alive_one(ms) # 选中一只小怪兽skill = randint(1, 10) # 通过随机数选择使用哪种技能if skill <= 6: # 60%的概率使用普通攻击print('%s使用普通攻击打了%s.' % (u.name, m.name))u.attack(m)print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))elif skill <= 9: # 30%的概率使用魔法攻击(可能因魔法值不足而失败)if u.magic_attack(ms):print('%s使用了魔法攻击.' % u.name)else:print('%s使用魔法失败.' % u.name)else: # 10%的概率使用究极必杀技(如果魔法值不足则使用普通攻击)if u.huge_attack(m):print('%s使用究极必杀技虐了%s.' % (u.name, m.name))else:print('%s使用普通攻击打了%s.' % (u.name, m.name))print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))if m.alive > 0: # 如果选中的小怪兽没有死就回击奥特曼print('%s回击了%s.' % (m.name, u.name))m.attack(u)display_info(u, ms) # 每个回合结束后显示奥特曼和小怪兽的信息fight_round += 1print('\n========战斗结束!========\n')if u.alive > 0:print('%s奥特曼胜利!' % u.name)else:print('小怪兽胜利!')if __name__ == '__main__':main()
