python 元类工厂模式_python学习笔记(十九)

本文介绍了Python的元类和工厂模式,并通过实例展示了如何使用元类创建类以及工厂模式在员工工资结算系统中的应用。同时,文章还讨论了面向对象的三大支柱、类与类之间的关系以及对象的复制、垃圾回收等概念。最后,提到了Python中的迭代器、生成器和协程,并给出了相关代码示例。
摘要由CSDN通过智能技术生成

### 面向对象相关知识

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

例子:工资结算系统。

```Python

"""

月薪结算系统 - 部门经理每月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):

"""创建员工"""

all_emp_types = {'M': Manager, 'P': Programmer, 'S': Salesman}

cls = all_emp_types[emp_type.upper()]

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()

```

- 类与类之间的关系

- is-a关系:继承

- has-a关系:关联 / 聚合 / 合成

- use-a关系:依赖

例子:扑克游戏。

```Python

"""

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

"""

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 __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()

```

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

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

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

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

```C

typedef struct_object {

/* 引用计数 */

int ob_refcnt;

/* 对象指针 */

struct_typeobject *ob_type;

} PyObject;

```

```C

/* 增加引用计数的宏定义 */

#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除了引用技术还引入了标记清理和分代回收

# 在Python 3.6以前如果重写__del__魔术方法会导致循环引用处理失效

# 如果不想造成循环引用可以使用弱引用

list1 = []

list2 = []

list1.append(list2)

list2.append(list1)

```

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

- 调用`gc.collect()`

- `gc`模块的计数器达到阀值

- 程序退出

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

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

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

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

- 自定义的对象能不能使用运算符做运算?

- 自定义的对象能不能放到`set`中?能去重吗?

- 自定义的对象能不能作为`dict`的键?

- 自定义的对象能不能使用上下文语法?

- 混入(Mixin)

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

```Python

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)

```

- 元编程和元类

对象是通过类创建的,类是通过元类创建的,元类提供了创建类的元信息。所有的类都直接或间接的继承自`object`,所有的元类都直接或间接的继承自`type`。

例子:用元类实现单例模式。

```Python

import threading

class SingletonMeta(type):

"""自定义元类"""

def __init__(cls, *args, **kwargs):

cls.__instance = None

cls.__lock = threading.RLock()

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

```

- 面向对象设计原则

- 单一职责原则 (**S**RP)- 一个类只做该做的事情(类的设计要高内聚)

- 开闭原则 (**O**CP)- 软件实体应该对扩展开发对修改关闭

- 依赖倒转原则(DIP)- 面向抽象编程(在弱类型语言中已经被弱化)

- 里氏替换原则(**L**SP) - 任何时候可以用子类对象替换掉父类对象

- 接口隔离原则(**I**SP)- 接口要小而专不要大而全(Python中没有接口的概念)

- 合成聚合复用原则(CARP) - 优先使用强关联关系而不是继承关系复用代码

- 最少知识原则(迪米特法则,Lo**D**)- 不要给没有必然联系的对象发消息

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

- GoF设计模式

- 创建型模式:单例、工厂、建造者、原型

- 结构型模式:适配器、门面(外观)、代理

- 行为型模式:迭代器、观察者、状态、策略

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

```Python

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.6.tgz', 'rb') as stream:

print(hasher1.to_digest(stream))

hasher2 = StreamHasher('sha1')

with open('Python-3.7.6.tgz', 'rb') as stream:

print(hasher2(stream))

if __name__ == '__main__':

main()

```

### 迭代器和生成器

- 迭代器是实现了迭代器协议的对象。

- Python中没有像`protocol`或`interface`这样的定义协议的关键字。

- Python中用魔术方法表示协议。

- `__iter__`和`__next__`魔术方法就是迭代器协议。

```Python

class Fib(object):

"""迭代器"""

def __init__(self, num):

self.num = num

self.a, self.b = 0, 1

self.idx = 0

def __iter__(self):

return self

def __next__(self):

if self.idx < self.num:

self.a, self.b = self.b, self.a + self.b

self.idx += 1

return self.a

raise StopIteration()

```

- 生成器是语法简化版的迭代器。

```Python

def fib(num):

"""生成器"""

a, b = 0, 1

for _ in range(num):

a, b = b, a + b

yield a

```

- 生成器进化为协程。

生成器对象可以使用`send()`方法发送数据,发送的数据会成为生成器函数中通过`yield`表达式获得的值。这样,生成器就可以作为协程使用,协程简单的说就是可以相互协作的子程序。

```Python

def calc_avg():

"""流式计算平均值"""

total, counter = 0, 0

avg_value = None

while True:

value = yield avg_value

total, counter = total + value, counter + 1

avg_value = total / counter

gen = calc_avg()

next(gen)

print(gen.send(10))

print(gen.send(20))

print(gen.send(30))

```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值