datetime:2019/5/16 15:26
author:nzb

面向对象相关知识

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

例子:工资结算系统。

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


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

     def __init__(self, name):
         self.name = name

     @abstractmethod
     def get_salary(self):
         """结算月薪(抽象方法)"""
         pass


 class Manager(Employee):
     """部门经理"""

     def get_salary(self):
         return 15000.0


 class Programmer(Employee):
     """程序员"""

     def __init__(self, name, working_hour=0):
         self.working_hour = working_hour
         super().__init__(name)

     def get_salary(self):
         return 200.0 * self.working_hour


 class Salesman(Employee):
     """销售员"""

     def __init__(self, name, sales=0.0):
         self.sales = sales
         super().__init__(name)

     def get_salary(self):
         return 1800.0 + self.sales * 0.05


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

     @staticmethod
     def create(emp_type, *args, **kwargs):
         """创建员工"""
         emp_type = emp_type.upper()
         emp = None
         if emp_type == 'M':
             emp = Manager(*args, **kwargs)
         elif emp_type == 'P':
             emp = Programmer(*args, **kwargs)
         elif emp_type == 'S':
             emp = Salesman(*args, **kwargs)
         return emp


 def main():
     """主函数"""
     emps = [
         EmployeeFactory.create('M', '曹操'), 
         EmployeeFactory.create('P', '荀彧', 120),
         EmployeeFactory.create('P', '郭嘉', 85), 
         EmployeeFactory.create('S', '典韦', 123000),
     ]
     for emp in emps:
         print('%s: %.2f元' % (emp.name, emp.get_salary()))


 if __name__ == '__main__':
     main()

类与类之间的关系

  • is-a关系:继承
  • has-a关系:关联 / 聚合 / 合成
  • use-a关系:依赖

    例子:扑克游戏。

"""
经验:符号常量总是优于字面常量,枚举类型是定义符号常量的最佳选择
"""
from enum import Enum, unique

import random


@unique
class Suite(Enum):
 """花色"""

 SPADE, HEART, CLUB, DIAMOND = range(4)

 def __lt__(self, other):
     return self.value < other.value


class Card():
 """牌"""

 def __init__(self, suite, face):
     """初始化方法"""
     self.suite = suite
     self.face = face

 def show(self):
     """显示牌面"""
     suites = ['♠️', '♥️', '♣️', '♦️']
     faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
     return f'{suites[self.suite.value]} {faces[self.face]}'

 def __str__(self):
     return self.show()

 def __repr__(self):
     return self.show()


class Poker():
 """扑克"""

 def __init__(self):
     self.index = 0
     self.cards = [Card(suite, face)
                   for suite in Suite
                   for face in range(1, 14)]

 def shuffle(self):
     """洗牌(随机乱序)"""
     random.shuffle(self.cards)
     self.index = 0

 def deal(self):
     """发牌"""
     card = self.cards[self.index]
     self.index += 1
     return card

 @property
 def has_more(self):
     return self.index < len(self.cards)


class Player():
 """玩家"""

 def __init__(self, name):
     self.name = name
     self.cards = []

 def get_one(self, card):
     """摸一张牌"""
     self.cards.append(card)

 def sort(self, comp=lambda card: (card.suite, card.face)):
     """整理手上的牌"""
     self.cards.sort(key=comp)


def main():
     """主函数"""
     poker = Poker()
     poker.shuffle()
     players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
     while poker.has_more:
         for player in players:
                 player.get_one(poker.deal())
     for player in players:
         player.sort()
         print(player.name, end=': ')
         print(player.cards)


if __name__ == '__main__':
 main()

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

垃圾回收和循环引用以及弱引用

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

 typedef struct_object {
     /* 引用计数 */
     int ob_refcnt;
     /* 对象指针 */
     struct_typeobject *ob_type;
 } PyObject;
 /* 增加引用计数的宏定义 */
 #define Py_INCREF(op)   ((op)->ob_refcnt++)
 /* 减少引用计数的宏定义 */
 #define Py_DECREF(op) \ //减少计数
     if (--(op)->ob_refcnt != 0) \
         ; \
     else \
         __Py_Dealloc((PyObject *)(op))

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

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

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

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

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

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

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

  • 调用gc.collect()

  • gc模块的计数器达到阀值
  • 程序退出

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

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

Python的内存管理机制及调优手段?

内存管理机制: 引用计数、垃圾回收、内存池

  • 引用计数:引用计数是一种非常高效的内存管理手段,当一个Python对象被引用时其引用计数增加1,
def DisplayItems(self):
    print "show all items---"
    for item in self.__list:
    print item
    if hasattr(Parent, 'x'):
    print(getattr(Parent, 'x'))
    setattr(Parent, 'x',3)
    print(getattr(Parent,'x'))

当其不再被一个变量引用时则计数减1,当引用计数等于0时对象被删除。弱引用不会增加引用计数

  • 垃圾回收:Python的垃圾回收机制采用引用计数机制为主,标记-清除和分代回收机制为辅的策略。
    • 1.引用计数 引用计数也是一种垃圾收集机制,而且也是一种最直观、最简单的垃圾收集技术。当Python的某个对象 的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建 对象,它被分配给某个引用,对象的引用计数变为1,如果引用被删除,对象的引用计数为0,那么该对 象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了。
    • 2.标记清除
    • 3.分代回收
  • 内存池

    当创建大量消耗小内存的对象时,频繁调用 new/malloc 会导致大量的内存碎片,致使效率降低。内存池的作用就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。

  • 调优手段

    • 1.手动垃圾回收
    • 2.调高垃圾回收阈值
    • 3.避免循环引用

内存泄露是什么?如何避免?

内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消 失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控 制,从而造成了内存的浪费。

del() 函数的对象间的循环引用是导致内存泄露的主凶。不使用一个对象时使用: del object 来 删除一个对象的引用计数就可以有效防止内存泄露问题。

通过Python扩展模块gc 来查看不能回收的对象的详细信息。

可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为0来判断是否内存泄露

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

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

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

混入(Mixin)

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

 class SetOnceMappingMixin():
     """自定义混入类"""
     __slots__ = ()

     def __setitem__(self, key, value):
         if key in self:
             raise KeyError(str(key) + ' already set')
         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)

元编程和元类

  • Python2和Python3实现元类
# py2
class Foo(object):
    __metaclass__ = ABCMeta
    [...]

# py3
class Simple1(object, metaclass=ABCMeta):
    [...]
  • 用元类实现单例模式。
 import threading


 class SingletonMeta(type):
     """自定义元类"""

     def __init__(cls, *args, **kwargs):
         cls.__instance = None
         cls.__lock = threading.Lock()
         super().__init__(*args, **kwargs)

     def __call__(cls, *args, **kwargs):
         if cls.__instance is None:
             with cls.__lock:
                 if cls.__instance is None:
                     cls.__instance = super().__call__(*args, **kwargs)
         return cls.__instance


 class President(metaclass=SingletonMeta):
     """总统(单例类)"""
     pass

面向对象设计原则

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

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

GoF设计模式

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

    例子:可插拔的哈希算法。

 class StreamHasher():
     """哈希摘要生成器(策略模式)"""

     def __init__(self, alg='md5', size=4096):
         self.size = size
         alg = alg.lower()
         self.hasher = getattr(__import__('hashlib'), alg.lower())()

     def __call__(self, stream):
         return self.to_digest(stream)

     def to_digest(self, stream):
         """生成十六进制形式的摘要"""
         for buf in iter(lambda: stream.read(self.size), b''):
             self.hasher.update(buf)
         return self.hasher.hexdigest()

 def main():
     """主函数"""
     hasher1 = StreamHasher()
     with open('Python-3.7.1.tgz', 'rb') as stream:
         print(hasher1.to_digest(stream))
     hasher2 = StreamHasher('sha1')
     with open('Python-3.7.1.tgz', 'rb') as stream:
         print(hasher2(stream))


 if __name__ == '__main__':
     main()

results matching ""

    No results matching ""