python函数式编程_Python中的函数式编程:不可变的数据结构

python函数式编程

在这个由两部分组成的系列文章中,我将讨论如何从函数式编程方法中将思想导入Python,以便兼顾两者。

第一篇文章将探讨不可变数据结构如何提供帮助。 第二部分将使用toolz库探索Python中的高级功能编程概念。

让我们从考虑正方形和矩形开始。 如果我们从接口的角度考虑而忽略了实现细节,那么正方形是矩形的子类型吗?

子类型的定义基于Liskov替换原理 。 为了成为子类型,它必须能够执行超类型所做的所有事情。

我们如何定义矩形的接口?


   
   
from zope. interface import Interface

class IRectangle ( Interface ) :
    def get_length ( self ) :
        """Squares can do that"""
    def get_width ( self ) :
        """Squares can do that"""
    def set_dimensions ( self , length , width ) :
        """Uh oh"""

如果这是定义,则正方形不能是矩形的子类型; 如果长度和宽度不同,它们将无法响应set_dimensions方法。

另一种方法是选择使矩形不可变


   
   
class IRectangle ( Interface ) :
    def get_length ( self ) :
        """Squares can do that"""
    def get_width ( self ) :
        """Squares can do that"""
    def with_dimensions ( self , length , width ) :
        """Returns a new rectangle"""

现在,正方形可以是矩形。 调用with_dimensions时,它可以返回一个新的矩形(通常不会是正方形),但它不会停止成为正方形。

这似乎是一个学术问题,直到我们认为正方形和矩形在某种意义上是其侧面的容器。 在我们理解了这个示例之后,更实际的情况是使用了更多的传统容器。 例如,考虑随机存取阵列。

我们有ISquareIRectangle ,而ISquareIRectangle的子类型。

我们想将矩形放在随机访问数组中:


   
   
class IArrayOfRectangles ( Interface ) :
    def get_element ( self , i ) :
        """Returns Rectangle"""
    def set_element ( self , i , rectangle ) :
        """'rectangle' can be any IRectangle"""

我们也想将正方形放在随机访问数组中:


   
   
class IArrayOfSquare ( Interface ) :
    def get_element ( self , i ) :
        """Returns Square"""
    def set_element ( self , i , square ) :
        """'square' can be any ISquare"""

即使ISquareIRectangle的子类型,任何数组都不能同时实现IArrayOfSquareIArrayOfRectangle

为什么不? 假设bucket实现。


   
   
>>> rectangle = make_rectangle ( 3 , 4 )
>>> bucket. set_element ( 0 , rectangle ) # This is allowed by IArrayOfRectangle
>>> thing = bucket. get_element ( 0 ) # That has to be a square by IArrayOfSquare
>>> assert thing. height == thing. width
Traceback ( most recent call last ) :
  File "<stdin>" , line 1 , in < module >
AssertionError

即使ISquareIRectangle的子类型,也无法实现两者都意味着它们都不是另一个子类型。 问题是set_element方法:如果我们有一个只读数组, IArrayOfSquare将是一个亚型IArrayOfRectangle

在可变的IRectangle接口和可变的IArrayOf*接口中,可变性都使思考类型和子类型变得更加困难-放弃变异的能力意味着我们期望类型之间的直觉关系真正成立。

突变也可能具有非局部作用。 当两个位置之间的共享对象被一个突变时,就会发生这种情况。 经典示例是一个线程使另一个线程改变共享对象,但是即使在单线程程序中,相距较远的地方也很容易共享。 考虑到在Python中,大多数对象可以从许多地方访问:作为全局模块,在堆栈跟踪中或作为类属性。

如果我们不能限制共享,我们可能会考虑限制可变性。

这是一个利用attrs库的不可变矩形:


   
   
@ attr. s ( frozen = True )
class Rectange ( object ) :
    length = attr. ib ( )
    width = attr. ib ( )
    @ classmethod
    def with_dimensions ( cls , length , width ) :
        return cls ( length , width )

这是一个正方形:


   
   
@ attr. s ( frozen = True )
class Square ( object ) :
    side = attr. ib ( )
    @ classmethod
    def with_dimensions ( cls , length , width ) :
        return Rectangle ( length , width )

使用frozen参数,我们可以轻松地使attrs创建的类是不可变的。 正确编写__setitem__所有辛苦工作已经由他人完成,并且对我们来说是完全看不见的。

修改对象仍然很容易。 对它们进行突变几乎是不可能的。


   
   
too_long = Rectangle ( 100 , 4 )
reasonable = attr. evolve ( too_long , length = 10 )

Pyrsistent软件包使我们能够拥有不可变的容器。


   
   
# Vector of integers
a = pyrsistent.v(1, 2, 3)
# Not a vector of integers
b = a.set(1, "hello")

虽然b不是整数的向量,但没有什么能阻止a成为整数。

如果a长一百万个元素怎么办? b会复制其中的999,999吗? Pyrsistent具有“ big O”性能保证:所有操作都花费O(log n)时间。 它还带有一个可选的C扩展,以提高性能,超越了大O。

为了修改嵌套对象,它带有“变压器”的概念:


   
   
blog = pyrsistent. m (
    title = "My blog" ,
    links = pyrsistent. v ( "github" , "twitter" ) ,
    posts = pyrsistent. v (
        pyrsistent. m ( title = "no updates" ,
                     content = "I'm busy" ) ,
        pyrsistent. m ( title = "still no updates" ,
                     content = "still busy" ) ) )
new_blog = blog. transform ( [ "posts" , 1 , "content" ] ,
                          "pretty busy" )

现在, new_blog将等于


   
   
{ 'links' : [ 'github' , 'twitter' ] ,
  'posts' : [ { 'content' : "I'm busy" ,
            'title' : 'no updates' } ,
            { 'content' : 'pretty busy' ,
            'title' : 'still no updates' } ] ,
  'title' : 'My blog' }

但是blog还是一样。 这意味着引用旧对象的任何人都不会受到影响:转换仅具有局部效果。

当共享猖ramp时,这很有用。 例如,考虑默认参数:


   
   
def silly_sum ( a , b , extra = v ( 1 , 2 ) ) :
    extra = extra. extend ( [ a , b ] )
    return sum ( extra )

在这篇文章中,我们了解了为什么不变性对于思考我们的代码很有用,以及如何在不牺牲性能的前提下实现它。 下次,我们将学习不可变对象如何使我们能够使用功能强大的编程构造。

翻译自: https://opensource.com/article/18/10/functional-programming-python-immutable-data-structures

python函数式编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值