《Fluent Python》第06章 使用一等函数实现设计模式

策略模式 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。来源菜鸟教程 翻译成人话就是:每个策略(解决问题的方法)都写成一个类,它们有一个共同的接口,供一个主对象调用。 主要解决:在有多种算法相似的情况下,使用if...else所带来的复杂和难以维护。 PS:王德发?! 书上举了个根据客户属性或订单中的商品计算折扣的例子: 策略一: 积分>=1000的顾客,每个订单享5%折扣 策略二: 同一订单中,单个商品的数量>=20,享10%折扣 策略三: 订单中的商品种类>=10,享7%折扣 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 from abc import ABC, abstractmethod class Customer: def __init__(self, name, fidelity=0): self....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第07章 函数装饰器和闭包

前言 函数装饰器用于在代码中“标记”函数,以某种方式增强函数的行为。 想理解与掌握这一功能必须先理解闭包;除此之外,闭包还是回调式异步编程和函数式编程风格的基础 装饰器 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把装饰后的函数返回,或者将其替换成另一个函数或可调用对象,然后返回。python也支持类装饰器。 1 2 3 4 5 6 7 8 9 10 11 12 def deco(func): def inner(): print('running inner()...') return inner @deco def target(): print('running target()...') target() >>>running inner()... print(target) >>><function deco.<locals>.inner at 0x7f63096898c8> 可见target现在是对inner的引用 python何时执行装饰器 装饰器的一个关键特性是,它们在被装饰的函数定义后立即执行。这通常是在导入时(即python加载模块时) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 registry = list() def register(func): print('running register(%s)' % func) registry....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第08章 对象引用、可变性和垃圾回收

前言 元组是不可变的,但是其中的值可以改变 is和== 1 2 3 4 5 6 7 d1 = {'one':1, 'two':2} d2 = {'one':1, 'two':2} print(d1 == d2) >>>True print(d1 is d2) >>>False 可见==是判断内容相等(使用__eq__()来判断),is判断是否为同一个引用(使用id())来判断 元组的相对不可变性 元组的不可变性指的是保存的对象的引用不可变,若保存的对象的内容是可变的(但元组保存的引用不变),元组也是某种意义上的可变 1 2 3 4 5 6 7 8 9 t1 = (1, 2, [3, 4]) t2 = (1, 2, [3, 4]) print(t1 == t2) >>>True t1[-1].append(5) print(t1) >>>(1, 2, [3, 4, 5]) print(t1 == t2) >>>False 默认做浅复制 复制list最简单的方式是使用内置的类型构造方法(书上的意思感觉是要说这是深复制,但是测试了还是浅复制(python3....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第09章 符合Python风格的对象

前言 得益于Python 数据模型,自定义类型的行为可以像内置类型那样自然。实现如此自然的行为,靠的不是继承,而是鸭子类型(duck typing) 对象的表示形式 python提供了两种对象表示形式 repr()以便于开发者理解的方式返回对象的字符串表示形式 str()以便于用户理解的方式返回对象的字符串表现形式 这一章的精华就在下面的例子里: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 from array import array from math import sqrt class Vector2d: __slots__ = ('__x', '__y') typecode = 'd' def __init__(self, x:float, y:float): self....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第10章 序列的修改、散列和切片

前言 不要检查它是不是鸭子、它的叫声像不像鸭子、它的走路姿势像不像鸭子,等等。具体检查什么取决于你想使用语言的哪些行为。(comp.lang.python,2000 年 7月 26 日) ————Alex Martelli 多维向量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from array import array from math import sqrt import reprlib class Vector: typecode = 'd' def __init__(self, components): self....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第11章 接口,从协议到抽象基类

前言 抽象类表示接口 ————Bjarne Stroustrup(C++ 之父) 本章讨论的主题是“鸭子类型”:对象的类型无关紧要,只要实现了特定的协议即 可。 Python文化中的接口和协议 Python语言没有interface关键字,而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或数据属性),包括特殊方法,如__getitem__或__add__。 按照定义,受保护的属性和私有属性 不在 接口中,即使“受保护的”属性也只是采用命名约定实现的(单个前导下划线);私有属性可以轻松地访问。不要违背这些约定 关于接口,这里有个实用的补充定义:对象公开方法的子集,让对象在系统中扮演特定的角色。接口是实现特定角色的方法集合,这样理解正是Smalltalk程序员所说的协议,其他动态语言社区都借鉴了这个术语。协议与继承没有关系。一个类可能会实现多个接口,从而让实例扮演多个角色。 协议是接口,但不是正式的(只由文档和约定定义),因此协议不能像正式接口那样施加限制,但抽象基类对接口一致性是强制的。一个类可能只实现部分接口,这是允许的。 对Python程序员来说,X 类对象, X 协议和X 接口都是一个意思。 python喜欢序列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Foo: def __getitem__(self, pos): return range(0, 30, 10)[pos] f = Foo() print(f[1]) >>>10 for x in f: print(x) >>>0 >>>10 >>>20 print(20 in f) >>>True print(15 in f) >>>False 虽然没有__iter__方法,但是Foo实例是可迭代的对象,因为发现有__getitem__方法时,Python会调用它,传入从 0 开始的整数索引,尝试迭代对象(这是一种后备机制)。尽管没有实现__contains__方法,但是 Python足够智能,能迭代Foo实例,因此也能使用in运算符...

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第12章 继承的优缺点

前言 本章重点说明子类化内置类型的缺点,多重继承和方法解析顺序 子类化内置类型 python2.2之后,内置类型可以子类化了,但是内置类型(使用c语言编写)不会调用用户定义的类覆盖方法(即重写“Override”) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class MyDict(dict): def __setitem__(self, key, value): super().__setitem__(key, [value]*2) d = MyDict({'one':1, 'two':2}) print(d) >>>{'one': 1, 'two': 2} d['three'] = 3 print(d) >>>{'one': 1, 'two': 2, 'three': [3, 3]} d.update(four=4) print(d) >>>{'one': 1, 'two': 2, 'three': [3, 3], 'four': 4} 上述的例子的意思是,继承自内置类型的两个方法a()和b(),b()方法中调用了a(),我们重写了a(),此时b()仍然调用的是原来的a(), 而不是我们重写后的a() 原生类型的这种行为违背了面向对象编程的一个基本原则:始终应该从实例(self)所属的类开始搜索方法,即使在超类实现的类中调用也是如此。 这样的问题只发生在C语言实现的内置类型内部的方法委托上,解决这个问题的方法是,使用collections.UserDict, collections.UserList, collections.UserString 多重继承和方法解析顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class A: def ping(self): print('ping: ', self) class B(A): def pong(self): print('pong from B: ', self) class C(A): def pong(self): print('pong from C: ', self) class D(B, C): def ping(self): super()....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀

《Fluent Python》第13章 正确的重载运算符

引言 有些事情让我不安,比如运算符重载。我决定不支持运算符重载,这完全是个人选择,因为我见过太多 C++ 程序员滥用它——James Gosling(Java 之父) ps: 运算符重载它不香吗 写在前面 对于复利公式,在python中只需要 1 interest = principal * ((1 + rate) ** periods - 1) 其中periods是整数, rate、interest和principal是精确的数字(Python 中decimal.Decimal类的实例) 但是在Java中,如果把float换成精度不定的BigDecimal,就无法再使用中缀运算符,因为中缀运算符只支持基本类型。用过java大数类的都知道会写吐的: 1 BigDecimal interest = principal.multiply(BigDecimal.ONE.add(rate).pow(periods).subtract(BigDecimal.ONE)); Python禁止重载内置类型的运算符 一元运算符 - (__neg__), 取负运算符 + (__pos__), 取正运算符 ~ (__invert__), 取反运算符 一元运算符要遵守运算符的一个基本规则:始终返回一个新对象 x和+x何时不等 当x是decimal.Decimal实例子时,由于+运算返回新的实例,可能会导致偏差, 所以导致内容不等, 即x != +x ps一般比较两个浮点数都要设置一个精度来判别 重载加法运算符+和乘法运算符* 为了支持涉及不同类型的运算,Python为中缀运算符特殊方法提供了特殊的分派机制。对 表达式a + b来说,解释器会执行以下几步操作 如果a有__add__方法,而且返回值不是NotImplemented,调用a.__add__(b),然后返回结果。 如果a没有__add__方法,或者调用__add__方法返回NotImplemented,检查b有没有__radd__方法,如果有,而且没有返回NotImplemented,调用b.__radd__(a),然后返回结果。 如果b没有__radd__方法,或者调用__radd__方法返回NotImplemented,抛出TypeError,并在错误消息中指明操作数类型不支持。 注意 实现一元运算符和中缀运算符的特殊方法一定不能修改操作数。使用这些运算符的表达式期待结果是新对象 如果由于类型不兼容而导致运算符特殊方法无法返回有效的结果,那么应该返回NotImplemented,而不是抛出 TypeError。返回 NotImplemented 时,另一个操作数所属的类型还有机会执行运算,即Python会尝试调用反向方法。 为了遵守鸭子类型精神,我们不能测试other操作数(即右操作数)的类型,我们要捕获异常,然后返回NotImplemented。如果解释器还未反转操作数,那么它将尝试去做。如果反向方法返回NotImplemented,那么 Python会抛出TypeError,并返回一个标准的错误消息,例如“unsupported operand type(s) for +: Vector and str”。 python3....

created: 2023-04-04  |  updated: 2023-04-04  |  阿秀