Python 开发指南:设计模式、能力式、操作符重载、单例模式

2022-09-1811:39:30编程语言入门到精通Comments708 views字数 3176阅读模式

能力式设计

能力式设计是所有动态语言的特性,比如,同为脚本语言的 Groovy。见:通过 Groovy 了解动态语言 - 掘金 (juejin.cn)。下面的函数 f 是这样声明的:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

def f(anyone) -> None:
    anyone.g()

在没有 上下文 ( Context ) 的环境下,我们不知道 anyone 是什么样的类型,也不确保它具备 g() 方法;这对于 Python 解释器也一样。anyone 参数的类型是被动态确定的,脚本语言通过牺牲了一部分性能换取了动态派发的能力。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

但从积极的角度思考,我们也可以认为:f() 函数不对 anyone 做任何的约束。正因如此,我们不需要事先从顶层定义任何的接口定义规范,仅仅是 "认为" anyone 应当能够提供 g() 方法。这种 "契约式开发" 的思想非常适用于轻量级项目的敏捷开发,同时保留了可拓展性。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

当然,一定要在 Python 里采用接口式编程来对类型作强约束也无可厚非。只不过代码可能会写成这个样子:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

class Foo:
    def g(self): raise NotImplementedError("declare a sub type and implement it.")

class Foo1(Foo):
    def g(self): print("method from Foo1")


class Foo2(Foo):
    def g(self): print("method from Foo2")

        
def f(anyone: Foo) -> None:
    assert isinstance(anyone, Foo), "anyone should implement class: Foo"
    anyone.g()
    
f(Foo1())
f(Foo2())    

不带任何类型标识的变量也被人戏称为 "鸭子类型"。它的典故来自于 Python 的这一设计理念:"如果它走路像鸭子,叫声也像鸭子,那它就是一只鸭子"。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

这里通过 isinstance 判断对象是否满足类型。我们还有其它手段去动态判断 ( 甚至是 ) 修改一个对象的属性和方法,见元编程部分。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

面向对象编程

准确的说,Python 的类 ( class ) 更贴近其它语言的特质 ( Trait ),或者是富接口 ( Interface ) 的概念,因为 Python 的类支持多重继承,我们能通过 组合 的方式让一个类获得强大的各种功能。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

class Swim:  # trait
    def swim(self):print(f"{self.__class__.__name__} swim")

class Quack:  # trait
    def quack(self):print(f"{self.__class__.__name__} quack")

# 括号表示继承,允许多重继承
class Duck(Swim,Quack): pass

duck = Duck()
duck.swim()  # Duck swim
duck.quack()  # Duck quack

前文已经涉及了一些 Python 类定义与实例创建的内容,这里主要对细节做进一步补充。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

方法接收者 self

Python 的实例方法 ( method ) 的首个参数必须为 self,它指代被调用的对象本身,可以把它理解成是类似 Go 语言的 "方法接收者"。但在调用时,应当忽略掉 self 参数。如果方法的参数列表不带 self 参数,则需要通过一个 @staticmethod 装饰器 ( 这个符号在 Java 中称之注解,见下文 ) 将其标注为静态方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

class Foo:
    def methood(self): print("a method")

    @staticmethod
    def functioon(): print("a function")

foo = Foo()
foo.methood()   # 调用实例方法 'methood'

# 不需要创建 'Foo' 的实例
Foo.functioon()  # 调用静态方法: 'functioon'

静态方法的设计主要是考虑到了模块命名空间的规范化管理。另外,通过类的实例调用静态方法也不会报错。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

foo.functioon()

另一个类似的装饰器是 @classmethod,用于标注类方法。它和 @staticmethod 的区别在于:它会携带一个 cls 参数表示类型。它可以被拿来做很多事情,比如说元编程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

*下划线前缀标识符

首先,单下划线 _xx 命名的标识符表示 模块空间或类的静态域 下的私有声明,这些声明不对外公开。比如以下声明:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

def _private_func(): print("only accessible in this module")
_private_var = 100

双下划线 __xx 命名的标识符表示实例的私有属性,这些属性不对外公开。比如以下声明:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

class Foo:
    def __init__(self,v_):
        self.__private_v = v_
    
    def __private_method(self): print("private method.")    

__xx__ 命名的方法称之为 Python 的魔法函数,Python 利用它们来实现各种语法糖,或者是 trick 机制。到目前为止,我们使用最多的魔法函数是 __init__() ,它可以被认为是类的初始化器。其内部通过 self.xxx 声明了类实例的属性。但事实上,我们是通过元信息注入的方式实现的属性声明,见后文的元编程。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

操作符重载

在 Python 提供的魔法函数中,有一部分是用于操作符重载的。这些函数名和操作符一一对应,比如:__add__() 对应 + 操作符,__sub__() 对应 - 操作符,__getitem__ 对应 [] 访问操作符等等,这里不一一列举。操作符重载机制极大丰富了 Python 程序的表达能力。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

class Pipeline:
    def __init__(self, seq_):
        self.seq = seq_
	
    def __getitem__(self, lamb):
        stream = [lamb(x) for x in self.seq if x is not None]
        return Pipeline(stream)

    def __iter__(self): return iter(self.seq)

# 这种设计可以优化,见后文的 "免费定理"。
pipe = Pipeline([1, 2, 3, 4, 5])[lambda x: x + 1][lambda x: x * 3] \
    [lambda x: x * 2 if x % 2 == 0 else x]  # 实现一个若为偶数则翻倍的偏函数

# [2, 4, 6, 8, 10]
print(*pipe) 

这里利用了指令链接和操作符重载创建了一个符号化的数据流管道,用户可以通过紧凑的 [] 操作符连续传递 lambda 表达式。管道内的数组将依次执行映射变换。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

除了 __getitem__() 可以重载 x[] 操作符之外,Python 还提供了可以重载 x() 操作符的 __call__() 方法。或者说:重载了此方法的对象将变成 可调用对象文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

class Foo:
    def __call__(self, *args, **kwargs):
        param = kwargs.get("param", "none")
        print(f"callable test:{param}")


foo = Foo()

print(callable(foo))  # True
foo(param="test")  # 可传入参数

由此可见,我们熟悉的操作符,在 Python 的不同语境下可能有完全不同的语义。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

单例模式

Python 的每一个 *.py 模块天然就是单例模式。比如,我们可以在第一个模块 A 内作如下定义:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

class Foo: pass
foo = Foo()

然后在另一个模块 B 下仅引入 foo 这一个引用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

from moduleA import foo

如果我们在网上搜索 "Python 单例模式",通常会得到五花八门的答案。但无论如何实现,Python 的单例模式仅仅是建立在约定上的,就像能力式设计那样。本质的原因是:我们无法从根本上禁止其它用户调用构造器文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

作者:花花子
来源:稀土掘金文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27840.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/27840.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定