@property装饰器

上一篇基础篇中的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。

  1. class Person():
  2. def __init__(self, name, age):
  3. self._name = name
  4. self._age = age
  5. # 访问器 - getter方法
  6. @property
  7. def name(self):
  8. return self._name
  9. # 访问器 - getter方法
  10. @property
  11. def age(self):
  12. print("age1 called")
  13. return self._age
  14. # 修改器 - setter方法
  15. @age.setter
  16. def age(self, age):
  17. print("age2 called")
  18. self._age = age
  19. def play(self):
  20. if self._age <= 16:
  21. print('%s正在玩飞行器' % self._name)
  22. else:
  23. print('%s正在玩斗地主' % self._name)
  24. def main():
  25. person = Person('王大锤', 12)
  26. person.play()
  27. person.age = 22
  28. person.play()
  29. # person.name = '白元芳' # AttributeError: can't set attribute
  30. if __name__ == "__main__":
  31. main()
  32. """"" 输出
  33. 王大锤正在玩飞行器
  34. age2 called
  35. 王大锤正在玩斗地主
  36. """""

slots魔法

Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义 __slots__ 变量来进行限定。需要注意的是 __slots__ 的限定只对当前类的对象生效,对子类并不起任何作用。

  1. class Person():
  2. # 限定Person对象只能绑定_name, _age和_gender属性
  3. __slots__ = ('_name', '_age', '_gender')
  4. def __init__(self, name, age):
  5. self._name = name
  6. self._age = age
  7. # 访问器 - getter方法
  8. @property
  9. def name(self):
  10. return self._name
  11. # 访问器 - getter方法
  12. @property
  13. def age(self):
  14. return self._age
  15. # 修改器 - setter方法
  16. @age.setter
  17. def age(self, age):
  18. self._age = age
  19. def play(self):
  20. if self._age <= 16:
  21. print('%s正在玩飞行器' % self._name)
  22. else:
  23. print('%s正在玩斗地主' % self._name)
  24. def main():
  25. person = Person('王大锤', 12)
  26. person.play()
  27. person._gender = '男'
  28. # AttributeError: 'Person' object has no attribute '_is_gay'
  29. # person._is_gay = True
  30. if __name__ == "__main__":
  31. main()

静态方法和类方法

之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示

  1. from math import sqrt
  2. class Triangle():
  3. def __init__(self, a, b, c):
  4. self._a = a
  5. self._b = b
  6. self._c = c
  7. @staticmethod
  8. def is_valid(a, b, c):
  9. return a + b > c and b + c > a and a + c > b
  10. def perimeter(self):
  11. return self._a + self._b + self._c
  12. def area(self):
  13. half = self.perimeter() / 2
  14. return sqrt(half * (half - self._a) * (half - self._b) * (half - self._c))
  15. def main():
  16. a, b, c = 3, 4, 5
  17. # 静态方法和类方法都是通过类发信息来调用的
  18. if Triangle.is_valid(a, b, c):
  19. t = Triangle(a, b, c)
  20. print(t.perimeter())
  21. # 也可以通过类发消息来调用对象方法,但是要传入接收消息的对象作为参数
  22. # print(Triangle.perimeter(t))
  23. print(t.area())
  24. # print(Triangle.area(t))
  25. else:
  26. print('无法构成三角形')
  27. if __name__ == "__main__":
  28. main()

和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为 cls ,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示

  1. from time import time, localtime, sleep
  2. class Clock(object):
  3. """数字时钟"""
  4. def __init__(self, hour=0, minute=0, second=0):
  5. self._hour = hour
  6. self._minute = minute
  7. self._second = second
  8. @classmethod
  9. def now(cls):
  10. ctime = localtime(time())
  11. return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
  12. def run(self):
  13. """走字"""
  14. self._second += 1
  15. if self._second == 60:
  16. self._second = 0
  17. self._minute += 1
  18. if self._minute == 60:
  19. self._minute = 0
  20. self._hour += 1
  21. if self._hour == 24:
  22. self._hour = 0
  23. def show(self):
  24. """显示时间"""
  25. return '%02d:%02d:%02d' % \
  26. (self._hour, self._minute, self._second)
  27. def main():
  28. # 通过类方法创建对象并获取系统时间
  29. clock = Clock.now()
  30. while True:
  31. print(clock.show())
  32. sleep(1)
  33. clock.run()
  34. if __name__ == '__main__':
  35. main()

继承和多态

  1. class Person(object):
  2. """人"""
  3. def __init__(self, name, age):
  4. self._name = name
  5. self._age = age
  6. @property
  7. def name(self):
  8. return self._name
  9. @property
  10. def age(self):
  11. return self._age
  12. @age.setter
  13. def age(self, age):
  14. self._age = age
  15. def play(self):
  16. print('%s正在愉快的玩耍.' % self._name)
  17. def watch_av(self):
  18. if self._age >= 18:
  19. print('%s正在观看爱情动作片.' % self._name)
  20. else:
  21. print('%s只能观看《熊出没》.' % self._name)
  22. class Student(Person):
  23. def __init__(self, name, age, grade):
  24. super().__init__(name, age)
  25. self._grade = grade
  26. @property
  27. def grade(self):
  28. return self._grade
  29. @grade.setter
  30. def grade(self, grade):
  31. self._grade = grade
  32. def study(self, course):
  33. print('%s的%s正在学习%s.' % (self._grade, self._name, course))
  34. class Teacher(Person):
  35. """老师"""
  36. def __init__(self, name, age, title):
  37. super().__init__(name, age)
  38. self._title = title
  39. @property
  40. def title(self):
  41. return self._title
  42. @title.setter
  43. def title(self, title):
  44. self._title = title
  45. def teach(self, course):
  46. print('%s%s正在讲%s.' % (self._name, self._title, course))
  47. def main():
  48. stu = Student('王大锤', 15, '初三')
  49. stu.study('数学')
  50. stu.watch_av()
  51. t = Teacher('骆昊', 38, '砖家')
  52. t.teach('Python程序设计')
  53. t.watch_av()
  54. if __name__ == "__main__":
  55. main()

子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)

  1. from abc import ABCMeta, abstractclassmethod
  2. class Pet(object, metaclass=ABCMeta):
  3. def __init__(self, nickname):
  4. self._nickname = nickname
  5. @abstractclassmethod
  6. def make_voice(self):
  7. pass
  8. class Dog(Pet):
  9. def make_voice(self):
  10. print("%s:汪汪汪..." % self._nickname)
  11. class Cat(Pet):
  12. def make_voice(self):
  13. print("%s:喵...喵..." % self._nickname)
  14. def main():
  15. pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
  16. for pet in pets:
  17. pet.make_voice()
  18. if __name__ == '__main__':
  19. main()

在上面的代码中,我们将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,DogCat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)

实例:奥特曼打小怪兽

  1. from abc import ABCMeta, abstractclassmethod
  2. from random import randint, randrange
  3. class Fighter(metaclass=ABCMeta):
  4. # 通过__slots__魔法限定对象可以绑定的成员变量
  5. __slots__ = ('_name', '_hp')
  6. def __init__(self, name, hp):
  7. self._name = name
  8. self._hp = hp
  9. @property
  10. def name(self):
  11. return self._name
  12. @property
  13. def hp(self):
  14. return self._hp
  15. @hp.setter
  16. def hp(self, hp):
  17. self._hp = hp if hp >= 0 else 0
  18. @property
  19. def alive(self):
  20. return self._hp > 0
  21. @abstractclassmethod
  22. def attack(self, other):
  23. pass
  24. class Ultraman(Fighter):
  25. __slots__ = ('_name', '_hp', '_mp')
  26. def __init__(self, name, hp, mp):
  27. super().__init__(name, hp)
  28. self._mp = mp
  29. def attack(self, other):
  30. other.hp -= randint(15, 25)
  31. def huge_attack(self, other):
  32. # 究极必杀技(打掉对方至少50点或四分之三的血)
  33. if self._mp >= 50:
  34. self._mp -= 50
  35. injury = other.hp * 3 // 4
  36. injury = injury if injury >= 50 else 50
  37. other.hp -= injury
  38. return True
  39. else:
  40. self.attack(other)
  41. return False
  42. def magic_attack(self, others):
  43. if self._mp >= 20:
  44. self._mp -= 20
  45. for temp in others:
  46. if temp.alive:
  47. temp.hp -= randint(10, 15)
  48. return True
  49. else:
  50. return False
  51. def resume(self):
  52. # 恢复魔法
  53. incr_point = randint(1, 10)
  54. self._mp += incr_point
  55. return incr_point
  56. def __str__(self):
  57. return '~~~%s奥特曼~~~\n' % self._name + \
  58. '生命值: %d\n' % self._hp + \
  59. '魔法值: %d\n' % self._mp
  60. class Monster(Fighter):
  61. __slots__ = ('_name', '_hp')
  62. def attack(self, other):
  63. other.hp -= randint(10, 20)
  64. def __str__(self):
  65. return '~~~%s小怪兽~~~\n' % self._name + \
  66. '生命值: %d\n' % self._hp
  67. def is_any_alive(monsters):
  68. for monster in monsters:
  69. if monster.alive > 0:
  70. return True
  71. return False
  72. def select_alive_one(monsters):
  73. monsters_len = len(monsters)
  74. while True:
  75. index = randrange(monsters_len)
  76. monster = monsters[index]
  77. if monster.alive > 0:
  78. return monster
  79. def display_info(ultraman, monsters):
  80. print(ultraman)
  81. for monster in monsters:
  82. print(monster, end='')
  83. def main():
  84. u = Ultraman('骆昊', 1000, 120)
  85. m1 = Monster('狄仁杰', 250)
  86. m2 = Monster('白元芳', 500)
  87. m3 = Monster('王大锤', 750)
  88. ms = [m1, m2, m3]
  89. fight_round = 1
  90. while u.alive and is_any_alive(ms):
  91. print('========第%02d回合========' % fight_round)
  92. m = select_alive_one(ms) # 选中一只小怪兽
  93. skill = randint(1, 10) # 通过随机数选择使用哪种技能
  94. if skill <= 6: # 60%的概率使用普通攻击
  95. print('%s使用普通攻击打了%s.' % (u.name, m.name))
  96. u.attack(m)
  97. print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
  98. elif skill <= 9: # 30%的概率使用魔法攻击(可能因魔法值不足而失败)
  99. if u.magic_attack(ms):
  100. print('%s使用了魔法攻击.' % u.name)
  101. else:
  102. print('%s使用魔法失败.' % u.name)
  103. else: # 10%的概率使用究极必杀技(如果魔法值不足则使用普通攻击)
  104. if u.huge_attack(m):
  105. print('%s使用究极必杀技虐了%s.' % (u.name, m.name))
  106. else:
  107. print('%s使用普通攻击打了%s.' % (u.name, m.name))
  108. print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
  109. if m.alive > 0: # 如果选中的小怪兽没有死就回击奥特曼
  110. print('%s回击了%s.' % (m.name, u.name))
  111. m.attack(u)
  112. display_info(u, ms) # 每个回合结束后显示奥特曼和小怪兽的信息
  113. fight_round += 1
  114. print('\n========战斗结束!========\n')
  115. if u.alive > 0:
  116. print('%s奥特曼胜利!' % u.name)
  117. else:
  118. print('小怪兽胜利!')
  119. if __name__ == '__main__':
  120. main()