加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 建站 > 正文

在Python中使用函数式编程的最佳实践!

发布时间:2019-01-29 13:49:49 所属栏目:建站 来源:菜鸟带你学编程
导读:简介 Python 是一种功能丰富的高级编程语言。它有通用的标准库,支持多种编程语言范式,还有许多内部的透明度。如果你愿意,还可以查看 Python 的底层并修改,甚至能在程序运行的时候直接修改运行时。 我最近注意到一个有经验的 Python 程序员使用 Python

还有许多高阶函数能用其他方式操作函数,其中最值得一提的就是 partial,它能锁定函数的一部分参数。这种方式也叫做“currying”,这个术语来自函数式编程的先驱者 Haskell Curry:

  1. def power(base, exp):  
  2.  return base ** exp  
  3. cube = partial(power, exp=3)  
  4. cube(5) # returns 125 

关于 Python 中的 FP 概念的具体介绍,以及怎样优先使用函数式进行编程,我推荐 Mary Rose Cook 的这篇文章(https://maryrosecook.com/blog/post/a-practical-introduction-to-functional-programming)。

这些函数可以将许多行的循环转变成极其精简的一行代码。但是,一般的程序员也很难理解这些代码,特别是 Python 原本与英语十分类似的语法流。个人经验,我永远都记不住参数的顺序,以及每个函数的功能,尽管我查了这么多次手册。但我强烈建议尝试一下这些函数,以了解一些 FP 的概念,而且有时候我认为它们才是正确的选择,如下一节的例子所示。

修饰器

高阶函数也以修饰器的形式融入了日常的 Python 编程中。定义修饰器的方法就反映了这一点,而@符号实际上只是个语法糖,将被修饰的函数传递给修饰器作为参数。下面就定义了一个简单的修饰器,它会将给定的代码重试三次,返回第一个成功的值,或者在三次尝试都失败之后放弃并抛出最后的异常。

  1. def retry(func):  
  2.  def retried_function(*args, **kwargs):  
  3.  exc = None  
  4.  for _ in range(3):  
  5.  try:  
  6.  return func(*args, **kwargs)  
  7.  except Exception as exc:  
  8.  print("Exception raised while calling %s with args:%s, kwargs: %s. Retrying" % (func, args, kwargs).  
  9.  raise exc  
  10.  return retried_function  
  11. @retry  
  12. def do_something_risky():  
  13.  ...  
  14. retried_function = retry(do_something_risky) # No need to use `@` 

这个修饰器的输入和输出的类型和值完全一样,但这并不是必须的。修饰器可以添加或减少参数,也可以改变参数的类型。它们也可以通过本身的参数进行配置。我想指出的是,修饰器本身不一定是“纯函数”,它们可以(而且经常会)有副作用,只不过是恰巧使用了高阶函数而已。

就像许多中级或高级 Python 技巧一样,这个功能非常强大,但也很容易造成混乱。你必须使用 functools.wrap 修饰器进行修饰,否则调用的函数和栈跟踪中看到的函数名字会不一样。我见过一些修饰器会做一些非常复杂或非常重要的事情,如解析 json blob 中的值,或者处理认证。我还见过同一个函数或方法定义上的多层修饰器,必须掌握修饰器的应用次序才能正确理解。我认为通过利用内置的修饰器如“staticmethod”可以帮助理解,或者编写最简单的修饰器来避免大量样板代码,但如果你想让你的代码符合类型检查的话,那么尽量不要去修改输入或输出的类型。

我的建议

函数式编程很有趣,而且学习舒适区之外的编程范式能够为你带来灵活性,而且也可以让你从另一个角度考虑问题。但是,我不推荐使用 Python 时以函数式为主,特别是在旧的代码库中不要这么做。除了上面我提到的那些坑之外,还有下面的理由:

  •  开始使用 Python 不需要理解 FP。这样做很可能会迷惑其他阅读者,或者迷惑未来的自己。
  •  你无法保证任何你依赖的代码(通过 pip 安装的模块,或其他同事的代码)是函数式的,是纯净的。你也不知道你自己的代码是否像你想象的那么纯净。与函数式为主的语言不同,Python 的语法或编译器不会帮你强制纯净,也不会帮你消灭某些 Bug。将副作用和高阶函数混合在一起回导致巨大的混乱,因为你需要论证两种不同的复杂性,其难度是两者的乘积。
  •  使用带有类型注释的高阶函数是高级技巧。类型签名通常是又长又笨拙的“Callable”的嵌套。例如,一个简单的返回输入函数的高阶修饰器,其定义是“F = TypeVar[‘F’, bound=Callable[..., Any]]”,然后标注是“def transparent(func: F) -> F: return func”。也许你懒得研究正确的签名的写法,而直接使用“Any”代替了。

那么,我们应该使用函数式编程的哪部分呢?

纯函数

只要可能并且合理,就应该尽量保持函数“纯净”,并仔细考虑应当在何处保持改变了的状态,并仔细地标记好。这样能让单元测试变得更容易,你不需要做太多 set-up 和 tear-down,也不需要太多 mocking,而且测试用例不论执行顺序如何,都会产生预期中的结果。

下面是个非函数式的例子。

  1. dictionary = ['fox', 'boss', 'orange', 'toes', 'fairy', 'cup']  
  2. def puralize(words):  
  3.  for i in range(len(words)):  
  4.  word = words[i]  
  5.  if word.endswith('s') or word.endswith('x'):  
  6.  word += 'es'  
  7.  if word.endswith('y'):  
  8.  wordword = word[:-1] + 'ies'  
  9.  else:  
  10.  word += 's'  
  11.  words[i] = word  
  12. def test_pluralize():  
  13.  pluralize(dictionary)  
  14.  assert dictionary == ['foxes', 'bosses', 'oranges', 'toeses', 'fairies', 'cups'] 

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读