编程范式及函数式编程

一、编程范式

编程范式主要有三种:命令式编程(Imperative)、声明式编程(Declarative)和函数式编程(Functional)。

1、命令式编程(Imperative):

命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。

import random
list1=[random.randrange(10) for i in range(5)]
result=[]
for x in list1:
    if x>5:result.append(x)
print(result)

很明显,这个样子的代码是很常见的一种,不管你用的是 C, C++ 还是 C#, Java, Javascript, BASIC, Python, Ruby 等等,你都可以以这个方式写。

2、声明式编程(Declarative):

声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。 SQL 语句就是最明显的一种声明式编程的例子,例如:

SELECT * FROM collection WHERE num > 5

除了 SQL,网页编程中用到的 HTML 和 CSS 也都属于声明式编程。通过观察声明式编程的代码我们可以发现它有一个特点是它不需要创建变量用来存储数据。另一个特点是它不包含循环控制的代码如 for, while。

3、函数式编程(Functional):

函数式编程和声明式编程是有所关联的,因为他们思想是一致的:即只关注做什么而不是怎么做。但函数式编程不仅仅局限于声明式编程。函数式编程最重要的特点是“函数第一位”,即函数可以出现在任何地方,比如你可以把函数作为参数传递给另一个函数,不仅如此你还可以将函数作为返回值,还可以将函数赋值给一个变量(即让变量指向函数)。常见的编程语言一半都已经提供了对这种编程方式的支持,如 JavaScript,再有 C# 中的 LINQ 和 Java 中的 Lambda 和闭包的概念。

二、函数式编程(Functional Programming)

函数式编程是一种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。其中将一个个函数视为最小的运算单元,不关心中间的过程,而重在最后的输出,对于同样的输入,必定有同样的输出,不依赖周围环境,不改变其它变量或者状态。这里的函数可理解成是数学意义上的函数:给定一个数集A,对A施加对应法则f,记作f(A),得到另一数集B,也就是B=f(A)。那么这个关系式就叫函数关系式,简称函数。函数概念含有三个要素:定义域A、值域C和对应法则f。其中核心是对应法则f,它是函数关系的本质特征。函数式编程,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

1、函数嵌套定义及变量的作用域

Python允许函数的嵌套定义,在函数内部可以再定义另外一个函数。下面这个例子中,在嵌套函数里,演示了全局变量、局部变量、nonlocal变量的作用域关系及关键字global、nonlocal的作用。

spam="我是最初的全局变量"
def scope_test():
    def do_local():
       		spam="我是局部变量"
        
    def do_nonlocal():
        nonlocal spam
        spam="我不是局部变量也不是全部变量"
    
    def do_global():
        global spam
        spam="我是全部变量"
        
    spam="原来的值"
    do_local()
    print("局部变量赋值后:",spam)
    do_nonlocal()
    print("nonlocal变量赋值后:",spam)
    do_global()
    print("全局变量赋值后:",spam)
print(spam)
scope_test()
print(spam)

2、可调用对象

函数属于Python可调用对象之一,由于构造方法的存在,类也是可调用的。像list(),tuple(),dict()、set()这样的工厂函数实际上都是调用了类的构造方法。另外,任何包含__call__方法的类的对象也是可调用的。实质上,可调用对象是由于实现了方法__call__,内置函数callable()就是检测该方法的存在情况。故此,判断Python对象是否可调用有三种方法:

(1)使用内置的callable()函数

callable(func)

(2)判断对象是否实现__call__方法

hasattr(obj,"__call__")

(3)判断对象类型是否FunctionType

type(func) is FunctionType 
#或者
isinstance(func,FunctionType)

上面第1、2种验证方法是等价的,第3种方法,需要注意的是,在验证类方法和实例方法时,会返回False,原因是它们的类型都是,不是FunctionType。在Python中分为函数(function)和方法(method)。函数是Python中一个可调用对象(用户自定义的可调用对象,lambda表达式创建的函数,其类型都是FunctionType),方法是一种特殊的函数,类方法和类绑定,实例方法与实例绑定,所以两者的类型都是method,而静态方法,本身既不和类进行绑定,也不和实例绑定,不符合上述定义,故此其类型应该是FunctionType。

class ClassA:

    @staticmethod
    def func_a():
        pass

    @classmethod
    def func_b(cls, arg):
        pass

    def func_c(self, arg):
        pass


def func_d():
    pass

class_a = ClassA()

3、高阶函数

变量可以指向函数,函数名也是变量

把函数当做参数传入函数——map(),reduce(),filter(),sort()

函数作为返回值

4、函数闭包(Closure)与自由变量

闭包并不只是一个python中的概念,在函数式编程语言中应用较为广泛。理解python中的闭包一方面是能够正确的使用闭包,另一方面可以好好体会和思考闭包的设计思想。

维基上对闭包的解释:在计算机科学中,闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。(注:与点集里的闭包联想)

程序被加载到内存执行时,函数定义的代码被存放在代码段中。函数被调用时,会在栈上创建其执行环境,也就是初始化其中定义的变量和外部传入的形参以便函数进行下一步的执行操作。当函数执行完成并返回函数结果后,函数栈帧便会被销毁掉。函数中的临时变量以及存储的中间计算结果都不会保留。下次调用时唯一发生变化的就是函数传入的形参可能会不一样,栈帧会重新初始化。

#举例:
def cook():
    food = 'apple'
    def wrapper():
        print(food)
   	 return wrapper
#以下运行情况
value = cook()
value()    #按道理来说 food 变量就应该随cook()调用结束被销毁掉了,但实际上 value() 顺利的输出了 food 的值。
#以下删除cook()函索后再运行
del cook
value()   #即使将 cook() 函数销毁了,food 的值都还存在。

可见,高阶函数中,内层函数携带外层函数中的参数、变量及其环境,一同存在的状态(即使已经离开了创造它的外层函数)被称之为函数闭包。被携带的外层变量被称为自由变量,有时候也被形容为外层变量被闭包捕获了。外层函数传递的参数甚至可以是个函数。总之闭包是一个嵌套函数,是从外层函数返回的内层函数,它可以访问外层作用域中的自由变量。

既然外层函数可以携带参数,那被返回的内层函数当然也可以带参数。用闭包携带参数并返回函数的这个特性,可以很方便的在一个底层的函数框架上,组装出不同的功能。

闭包中的自由变量有两个神奇的特性:闭包可以持有状态,即自由变量在闭包存在的期间,其中的值也会一直存在;闭包与闭包之间的状态是隔离的。如:

# 记录每次取得的分数
def make_score():
    lis = []      #自由变量是可修改变量
    def inner(x):
        lis.append(x)
        print(lis)
    return inner
score1 = make_score()
score1 = make_score()
score1(1)
score1(1)
score2(3)
score2(4)
# 记录成绩总分
def make_score():
    total = 0    #自由变量是不可修改变量,需要内层函数里加关键字nonlocal
    def inner(x):
        nonlocal total
        total += x
        print(total)
    return inner

total1 = make_score()
total2 = make_score()
total1(2)
total1(5)
total2(10)
total2(20)

因为内层函数只有在调用时才执行(延迟执行的),故此有延迟陷阱的现象。如:

funcs = []
for i in range(3):
    def inner():
        print(i)
    funcs.append(inner)

print(f'i is: {i}')
funcs[0]()
funcs[1]()
funcs[2]()
# 输出:
# 2
# 2
# 2

#解决方案就是用闭包将 i 的值立即捕获:

funcs = []
for i in range(3):
    def outer(a):
        def inner():
            print(a)
        return inner
    funcs.append(outer(i))

print(f'i is: {i}')
funcs[0]()
funcs[1]()
funcs[2]()

函数闭包(function closures)有许多应用,如装饰器、组合函数、函数柯里化等。

5、修饰器(decorator)

修饰器是函数闭包的一个重要应用。修饰器本质上是一个嵌套函数,只不过这个函数接收被修饰函数作为参数并对其进行一定改造之后返回新函数(内层函数)。静态方法@staticmethod、类方法@classmethod、属性@property等也都是通过修饰器实现的,Python中还有很多这样的用法。

def a_new_decorator(a_func):
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction

def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my 
          foul smell")
 
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
 
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
 
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
#        I am the function which needs some decoration to remove my foul smell
#        I am doing some boring work after executing a_func()

上面展示了Python 运用了函数嵌套定义实现装饰器的原理!它们封装一个函数,并且用这样或者那样的方式来修改它的行为。现在你也许疑惑,我们在代码里并没有使用 @ 符号?那只是一个简短的方式来生成一个被装饰的函数。这里是我们如何使用 @ 来运行之前的代码:

@a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to remove my foul smell")
 
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()

但是,如果我们运行如下代码会存在一个问题:

print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction
print(a_function_requiring_decoration.__doc__)
# Output: ""

这并不是我们想要的!Ouput输出应该是"a_function_requiring_decoration"。这里的函数被warpTheFunction替代了。它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题,那就是functools.wraps。我们修改上一个例子来使用functools.wraps:

from functools import wraps
 
def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction
 
@a_new_decorator
def a_function_requiring_decoration():
    """Hey yo! Decorate me!"""
    print("I am the function which needs some decoration to remove my foul smell")

现在再查看被修饰函数的函数名字和注释文档(docstring),显示的就是正确的了。一般定义修饰器的蓝本规范如下:

from functools import wraps

def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"
        return f(*args, **kwargs)
    return decorated
 
@decorator_name
def func():
    return("Function is running")
 
can_run = True
print(func())
# Output: Function is running
 
can_run = False
print(func())
# Output: Function will not run

注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。

你可能感兴趣的:(python)