面向对象相关知识

  • 三大支柱:封装、继承、多态

    例子:工资结算系统。

    ```Python “”” 月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成 “”” from abc import ABCMeta, abstractmethod

class Employee(metaclass=ABCMeta): “””员工(抽象类)”””

  1. def __init__(self, name):
  2. self.name = name
  3. @abstractmethod
  4. def get_salary(self):
  5. """结算月薪(抽象方法)"""
  6. pass

class Manager(Employee): “””部门经理”””

  1. def get_salary(self):
  2. return 15000.0

class Programmer(Employee): “””程序员”””

  1. def __init__(self, name, working_hour=0):
  2. self.working_hour = working_hour
  3. super().__init__(name)
  4. def get_salary(self):
  5. return 200.0 * self.working_hour

class Salesman(Employee): “””销售员”””

  1. def __init__(self, name, sales=0.0):
  2. self.sales = sales
  3. super().__init__(name)
  4. def get_salary(self):
  5. return 1800.0 + self.sales * 0.05

class EmployeeFactory: “””创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)”””

  1. @staticmethod
  2. def create(emp_type, *args, **kwargs):
  3. """创建员工"""
  4. all_emp_types = {'M': Manager, 'P': Programmer, 'S': Salesman}
  5. cls = all_emp_types[emp_type.upper()]
  6. return cls(*args, **kwargs) if cls else None

def main(): “””主函数””” emps = [ EmployeeFactory.create(‘M’, ‘曹操’), EmployeeFactory.create(‘P’, ‘荀彧’, 120), EmployeeFactory.create(‘P’, ‘郭嘉’, 85), EmployeeFactory.create(‘S’, ‘典韦’, 123000), ] for emp in emps: print(f’{emp.name}: {emp.get_salary():.2f}元’)

if name == ‘main‘: main()

  1. - 类与类之间的关系
  2. - is-a关系:继承
  3. - has-a关系:关联 / 聚合 / 合成
  4. - use-a关系:依赖
  5. 例子:扑克游戏。
  6. ```Python
  7. """
  8. 经验:符号常量总是优于字面常量,枚举类型是定义符号常量的最佳选择
  9. """
  10. from enum import Enum, unique
  11. import random
  12. @unique
  13. class Suite(Enum):
  14. """花色"""
  15. SPADE, HEART, CLUB, DIAMOND = range(4)
  16. def __lt__(self, other):
  17. return self.value < other.value
  18. class Card():
  19. """牌"""
  20. def __init__(self, suite, face):
  21. """初始化方法"""
  22. self.suite = suite
  23. self.face = face
  24. def show(self):
  25. """显示牌面"""
  26. suites = ['♠︎', '♥︎', '♣︎', '♦︎']
  27. faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
  28. return f'{suites[self.suite.value]}{faces[self.face]}'
  29. def __repr__(self):
  30. return self.show()
  31. class Poker():
  32. """扑克"""
  33. def __init__(self):
  34. self.index = 0
  35. self.cards = [Card(suite, face)
  36. for suite in Suite
  37. for face in range(1, 14)]
  38. def shuffle(self):
  39. """洗牌(随机乱序)"""
  40. random.shuffle(self.cards)
  41. self.index = 0
  42. def deal(self):
  43. """发牌"""
  44. card = self.cards[self.index]
  45. self.index += 1
  46. return card
  47. @property
  48. def has_more(self):
  49. return self.index < len(self.cards)
  50. class Player():
  51. """玩家"""
  52. def __init__(self, name):
  53. self.name = name
  54. self.cards = []
  55. def get_one(self, card):
  56. """摸一张牌"""
  57. self.cards.append(card)
  58. def sort(self, comp=lambda card: (card.suite, card.face)):
  59. """整理手上的牌"""
  60. self.cards.sort(key=comp)
  61. def main():
  62. """主函数"""
  63. poker = Poker()
  64. poker.shuffle()
  65. players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
  66. while poker.has_more:
  67. for player in players:
  68. player.get_one(poker.deal())
  69. for player in players:
  70. player.sort()
  71. print(player.name, end=': ')
  72. print(player.cards)
  73. if __name__ == '__main__':
  74. main()

说明:上面的代码中使用了Emoji字符来表示扑克牌的四种花色,在某些不支持Emoji字符的系统上可能无法显示。

  • 对象的复制(深复制/深拷贝/深度克隆和浅复制/浅拷贝/影子克隆)

  • 垃圾回收、循环引用和弱引用

    Python使用了自动化内存管理,这种管理机制以引用计数为基础,同时也引入了标记-清除分代收集两种机制为辅的策略。

    1. typedef struct _object {
    2. /* 引用计数 */
    3. int ob_refcnt;
    4. /* 对象指针 */
    5. struct _typeobject *ob_type;
    6. } PyObject;
    1. /* 增加引用计数的宏定义 */
    2. #define Py_INCREF(op) ((op)->ob_refcnt++)
    3. /* 减少引用计数的宏定义 */
    4. #define Py_DECREF(op) \ //减少计数
    5. if (--(op)->ob_refcnt != 0) \
    6. ; \
    7. else \
    8. __Py_Dealloc((PyObject *)(op))

    导致引用计数+1的情况:

    • 对象被创建,例如a = 23
    • 对象被引用,例如b = a
    • 对象被作为参数,传入到一个函数中,例如f(a)
    • 对象作为一个元素,存储在容器中,例如list1 = [a, a]

    导致引用计数-1的情况:

    • 对象的别名被显式销毁,例如del a
    • 对象的别名被赋予新的对象,例如a = 24
    • 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量(全局变量不会)
    • 对象所在的容器被销毁,或从容器中删除对象

    引用计数可能会导致循环引用问题,而循环引用会导致内存泄露,如下面的代码所示。为了解决这个问题,Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候,对象被放在第一代中,如果在第一代的垃圾检查中对象存活了下来,该对象就会被放到第二代中,同理在第二代的垃圾检查中对象存活下来,该对象就会被放到第三代中。

    1. # 循环引用会导致内存泄露 - Python除了引用技术还引入了标记清理和分代回收
    2. # 在Python 3.6以前如果重写__del__魔术方法会导致循环引用处理失效
    3. # 如果不想造成循环引用可以使用弱引用
    4. list1 = []
    5. list2 = []
    6. list1.append(list2)
    7. list2.append(list1)

    以下情况会导致垃圾回收:

    • 调用gc.collect()
    • gc模块的计数器达到阀值
    • 程序退出

    如果循环引用中两个对象都定义了__del__方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的__del__方法,这个问题在Python 3.6中得到了解决。

    也可以通过weakref模块构造弱引用的方式来解决循环引用的问题。

  • 魔法属性和方法(请参考《Python魔法方法指南》)

    有几个小问题请大家思考:

    • 自定义的对象能不能使用运算符做运算?
    • 自定义的对象能不能放到set中?能去重吗?
    • 自定义的对象能不能作为dict的键?
    • 自定义的对象能不能使用上下文语法?
  • 混入(Mixin)

    例子:自定义字典限制只有在指定的key不存在时才能在字典中设置键值对。

    ```Python class SetOnceMappingMixin:

    1. """自定义混入类"""
    2. __slots__ = ()
    3. def __setitem__(self, key, value):
    4. if key in self:
    5. raise KeyError(str(key) + ' already set')
    6. return super().__setitem__(key, value)

class SetOnceDict(SetOnceMappingMixin, dict): “””自定义字典””” pass

my_dict= SetOnceDict() try: my_dict[‘username’] = ‘jackfrued’ my_dict[‘username’] = ‘hellokitty’ except KeyError: pass print(my_dict)

  1. - 元编程和元类
  2. 对象是通过类创建的,类是通过元类创建的,元类提供了创建类的元信息。所有的类都直接或间接的继承自`object`,所有的元类都直接或间接的继承自`type`
  3. 例子:用元类实现单例模式。
  4. ```Python
  5. import threading
  6. class SingletonMeta(type):
  7. """自定义元类"""
  8. def __init__(cls, *args, **kwargs):
  9. cls.__instance = None
  10. cls.__lock = threading.RLock()
  11. super().__init__(*args, **kwargs)
  12. def __call__(cls, *args, **kwargs):
  13. if cls.__instance is None:
  14. with cls.__lock:
  15. if cls.__instance is None:
  16. cls.__instance = super().__call__(*args, **kwargs)
  17. return cls.__instance
  18. class President(metaclass=SingletonMeta):
  19. """总统(单例类)"""
  20. pass
  • 面向对象设计原则

    • 单一职责原则 (SRP)- 一个类只做该做的事情(类的设计要高内聚)
    • 开闭原则 (OCP)- 软件实体应该对扩展开发对修改关闭
    • 依赖倒转原则(DIP)- 面向抽象编程(在弱类型语言中已经被弱化)
    • 里氏替换原则(LSP) - 任何时候可以用子类对象替换掉父类对象
    • 接口隔离原则(ISP)- 接口要小而专不要大而全(Python中没有接口的概念)
    • 合成聚合复用原则(CARP) - 优先使用强关联关系而不是继承关系复用代码
    • 最少知识原则(迪米特法则,LoD)- 不要给没有必然联系的对象发消息

    说明:上面加粗的字母放在一起称为面向对象的SOLID原则。

  • GoF设计模式

    • 创建型模式:单例、工厂、建造者、原型
    • 结构型模式:适配器、门面(外观)、代理
    • 行为型模式:迭代器、观察者、状态、策略

    例子:可插拔的哈希算法(策略模式)。

    ```Python class StreamHasher():

    1. """哈希摘要生成器"""
    2. def __init__(self, alg='md5', size=4096):
    3. self.size = size
    4. alg = alg.lower()
    5. self.hasher = getattr(__import__('hashlib'), alg.lower())()
    6. def __call__(self, stream):
    7. return self.to_digest(stream)
    8. def to_digest(self, stream):
    9. """生成十六进制形式的摘要"""
    10. for buf in iter(lambda: stream.read(self.size), b''):
    11. self.hasher.update(buf)
    12. return self.hasher.hexdigest()

    def main():

    1. """主函数"""
    2. hasher1 = StreamHasher()
    3. with open('Python-3.7.6.tgz', 'rb') as stream:
    4. print(hasher1.to_digest(stream))
    5. hasher2 = StreamHasher('sha1')
    6. with open('Python-3.7.6.tgz', 'rb') as stream:
    7. print(hasher2(stream))

if name == ‘main‘: main() ```