python中的函数——笔记

在Python中,函数是一等对象。编程语言理论家把“一等对象”定义为满足下述条件的程序实体:

  • 在运行时创建
  • 能赋值给变量或数据结构中的元素
  • 能作为参数传给函数
  • 能作为函数的返回结果
    在Python中,整数、字符串和字典都是一等对象——没什么特别的。

一、把函数视作对象

Python函数是对象。下面的例子创建了一个函数,然后调用它,读取它的__doc__属性,并且确定函数对象本身是function类的实例。

def factorial(n):
    return 1 if n<2 else n*factorial(n-1)
factorial(42)
Out[92]: 1405006117752879898543142606244511569936384000000000
type(factorial)
Out[93]: function

们可以把factorial函数赋值给变量fact,然后通过变量名调用。我们还能把它作为参数传给map函数。map函数返回一个可迭代对象,里面的元素是把第一个参数(一个函数)应用到第二个参数(一个可迭代对象,这里是range(11))中各个元素上得到的结果。

fact = factorial
fact(5)
Out[95]: 120
list(map(fact, range(10)))
Out[96]: [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]

有了一等函数,就可以使用函数式风格编程。函数式编程的特点之一是使用高阶函数

二、高阶函数

接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higher-order function)。

fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=len)
Out[98]: ['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']

在函数式编程范式中,最为人熟知的高阶函数有map、filter、reduce和apply。不过多数使用场景下都有更好的替代品。

2.1、map、filter和reduce的现代替代品

函数式语言通常会提供map、filter和reduce三个高阶函数(有时使用不同的名称)。在Python 3中,map和filter还是内置函数,但是由于引入了列表推导和生成器表达式,它们变得没那么重要了。列表推导或生成器表达式具有map和filter两个函数的功能,而且更易于阅读。

list(map(fact, range(6)))
Out[99]: [1, 1, 2, 6, 24, 120]
[fact(x) for x in range(6)]
Out[100]: [1, 1, 2, 6, 24, 120]
list(map(fact, filter(lambda n: n%2, range(6))))
Out[101]: [1, 6, 120]
[fact(n) for n in range(6) if n%2]
Out[102]: [1, 6, 120]

三、匿名函数

lambda关键字在Python表达式内创建匿名函数。然而,Python简单的句法限制了lambda函数的定义体只能使用纯表达式。换句话说,lambda函数的定义体中不能赋值,也不能使用while和try等Python语句。在参数列表中最适合使用匿名函数。

fruits
Out[103]: ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
# 使用lambda表达式反转拼写,然后依此给单词列表排序
sorted(fruits, key=lambda word: word[::-1])
Out[104]: ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

除了作为参数传给高阶函数之外,Python很少使用匿名函数。由于句法上的限制,非平凡的lambda表达式要么难以阅读,要么无法写出。
lambda句法只是语法糖:与def语句一样,lambda表达式会创建函数对象。这是Python中几种可调用对象的一种。

四、可调用对象

除了用户定义的函数,调用运算符(即( ))还可以应用到其他对象上。如果想判断对象能否调用,可以使用内置的callable( )函数。Python数据模型文档列出了7种可调用对象:1)用户定义的函数:使用def语句或lambda表达式创建。2)内置函数:使用C语言(CPython)实现的函数,如len或time.strftime。3)内置方法:使用C语言实现的方法 4)方法:在类的定义体中定义的函数5):类:调用类时会运行类的__new__方法创建一个实例,然后运行__init__方法,初始化实例,最后把例返回给调用方;因为Python没有new运算符,所以调用类相当于调用函数。6)类的实例:如果类定义了__call__方法,那么它的实例可以作为函数调用。7)生成器函数:使用yield关键字的函数或方法。调用生成器函数返回的是生成器对象。生成器函数在很多方面与其他可调用对象不同。

五、用户定义的可调用类型

不仅Python函数是真正的对象,任何Python对象都可以表现得像函数。为此,只需实现实例方法__call__。

import random
class BingoCage:
    def __init__(self, items):
        self._items = list(items)
        random.shuffle(self._items)
    def pick(self):
        try:
            return self._items.pop()
        except IndexError:
            raise LookupError('pick from empty BingoCage')
    def __call__(self):
        return self.pick()

bingo = BingoCage(range(3))

bingo.pick()
Out[107]: 2

bingo()
Out[108]: 0

bingo._items
Out[109]: [1]

callable(bingo)
Out[110]: True

实现__call__方法的类是创建函数类对象的简便方式,此时必须在内部维护一个状态,让它在调用之间可用,例如BingoCage中的剩余元素。装饰器就是这样。装饰器必须是函数,而且有时要在多次调用之间“记住”某些事[例如备忘(memoization),即缓存消耗大的计算结果,供后面使用。创建保有内部状态的函数,还有一种截然不同的方式——使用闭包。

六、从定位参数到仅限关键词参数

Python最好的特性之一是提供了极为灵活的参数处理机制,而且Python 3进一步提供了仅限关键字参数(keyword-only argument)。与之密切相关的是,调用函数时使用*和**“展开”可迭代对象,映射到单个参数。

def tag(name, *content, cls=None, **attrs):
    if cls is not None:
        attrs['class'] = cls
    if attrs:
        attr_str = ''.join('%s="%s"'%(attr, value)
                           for attr, value in sorted(attrs.items()))
    else:
        attr_str = ''
    if content:
        return '\n'.join('<%s%s>%s'%
                         (name,attr_str,c,name) for c in content)
    else:
        return '<%s%s />'%(name, attr_str)
        
# 传入单个定位参数,生成一个指定名称的空标签。
tag('br')
Out[116]: '
'
# 第一个参数后面的任意个参数会被*content捕获,存入一个元组。 tag('p', 'hello') Out[117]: '

hello

'
tag('p', 'hello', 'world') Out[118]: '

hello

\n

world

'
# tag函数签名中没有明确指定名称的关键字参数会被**attrs捕获,存入一个字典 tag('p', 'hello', id=33) Out[119]: '

hello

'
# cls参数只能作为关键字参数传入。 tag('p', 'hello', 'world', cls='sidebar') Out[120]: '\n' #调用tag函数时,即便第一个定位参数也能作为关键字参数传入。 tag(content='testing', name='img',) Out[121]: '' my_tag = {'name':'img', 'title':'Sunset Boulevard', 'src':'sunset.jpg', 'cls':'framed'} # 在my_tag前面加上**,字典中的所有元素作为单个参数传入,同名键会绑定到对应的具名参数上,余下的则被**attrs捕获。 tag(**my_tag) Out[122]: ''

定义函数时若想指定仅限关键字参数,要把它们放到前面有的参数后面。如果不想支持数量不定的定位参数,但是想支持仅限关键字参数,在签名中放一个,如下所示:

def f(a, *, b):
    return a, b

f(1, b=2)
Out[131]: (1, 2)

七、支持函数式编程的包

1、operator模块

在函数式编程中,经常需要把算术运算符当作函数使用。例如,不使用递归计算阶乘。求和可以使用sum函数,但是求积则没有这样的函数。我们可以使用reduce函数,但是需要一个函数计算序列中两个元素之积。

from functools import reduce
def fact(n):
    return reduce(lambda a, b:a*b,range(1, n+1))

operator模块为多个算术运算符提供了对应的函数,从而避免编写lambda a, b: a*b这种平凡的匿名函数。使用reduce和operator.mul函数计算阶乘。

from functools import reduce
from operator import mul
def fact_oper(n):
    return reduce(mul, range(1, n+1))

operator模块中还有一类函数,能替代从序列中取出元素或读取对象属性的lambda表达式:因此,itemgetter和attrgetter其实会自行构建函数。

2、使用functools.partial冻结参数

functools模块提供了一系列高阶函数,其中最为人熟知的或许是reduce,我们在上一节已经介绍过。余下的函数中,最有用的是partial及其变体,partialmethod。
functools.partial这个高阶函数用于部分应用一个函数。部分应用是指,基于一个函数创建一个新的可调用对象,把原函数的某些参数固定。使用这个函数可以把接受一个或多个参数的函数改编成需要回调的API,这样参数更少。

from operator import mul
from functools import partial
triple = partial(mul, 3)
list(map(triple, range(1, 10)))
Out[142]: [3, 6, 9, 12, 15, 18, 21, 24, 27]

八、使用一等函数实现设计模式

电商领域有个功能明显可以使用“策略”模式,即根据客户的属性或订单中的商品计算折扣。假如一个网店制定了下述折扣规则。有1000或以上积分的顾客,每个订单享5%折扣。同一订单中,单个商品的数量达到20个或以上,享10%折扣。订单中的不同商品达到10个或以上,享7%折扣。简单起见,我们假定一个订单一次只能享用一个折扣。

  • 上下文:把一些计算委托给实现不同算法的可互换组件,它提供服务。在这个电商示例中,上下文是Order,它会根据不同的算法计算促销折扣。
  • 策略:实现不同算法的组件共同的接口。在这个示例中,名为Promotion的抽象类扮演这个角色。
  • 具体策略:“策略”的具体子类。fidelityPromo、BulkPromo和LargeOrderPromo是这里实现的三个具体策略。

from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
    def __init__(self, product, quantity, price):
        self.product = product
        self.quantity = quantity
        self.price = price
        
    def total(self):
        return self.price * self.quantity

# 上下文
class Order:
    def __init__(self, customer, cart, promotion=None):
        self.customer = customer
        self.cart = list(cart)
        self.promotion = promotion
        
    def total(self):
        if not hasattr(self, '__total'):
            self.__total = sum(item.total() for item in self.cart)
        return self.__total
    
    def due(self):
        if self.promotion is None:
            discount = 0
        else:
            discount = self.promotion.discount(self)
        return self.total() - discount
    
    def __repr__(self):
        fmt = ''
        return fmt.format(self.total(), self.due())
    

class Promotion(ABC):# 策略:抽象基类
    @abstractmethod
    def discount(self, order):
        '''返回折扣金额'''
    
# 策略一
class FidelityPromo(Promotion):
    # 积分1000或以上的顾客提供5%折扣
    def discount(self, order):
        return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
# 策略二
class BulkItemPromo(Promotion):
    # 单个商品为20或者以上时10%折扣
    def discount(self, order):
        discount = 0
        for item in order.cart:
            if item.quantity >=20:
                discount += item.total() * 0.1
        return discount
# 策略三
class LargeOrderPromo(Promotion):
    # 订单中的不同商品达到10个或者以上时提供7%折扣
    def discount(self, order):
        distinct_items = {item.product for item in order.cart}
        if len(distinct_items) >= 10:
            return order.total() *0.07
        return 0

cart = [LineItem('banana', 4, 0.5), LineItem('apple', 10, 1.5), LineItem('watermellon', 5, 5.0)]
Order(joe, cart, FidelityPromo())
Out[159]: <Order total:42.00 due:42.00>

Order(ann, cart, FidelityPromo())
Out[160]: <Order total:42.00 due:39.90>

banana_cart = [LineItem('banana', 30, 0.5), LineItem('apple', 10, 1.5)]

Order(joe, banana_cart, BulkItemPromo())
Out[164]: <Order total:30.00 due:28.50>

long_order=[LineItem(str(item_code),1, 1.0) for item_code in range(10)]
Order(joe, long_order, LargeOrderPromo())
Out[170]: <Order total:10.00 due:9.30>
1、使用函数实现策略模式

上面的例子中,每个具体策略都是一个类,而且都只定义了一个方法,即discount。此外,策略实例没有状态(没有实例属性)。它们看起来像是普通的函数——的确如此。

from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
    def __init__(self, product, quantity, price):
        self.product = product
        self.quantity = quantity
        self.price = price
        
    def total(self):
        return self.price * self.quantity

# 上下文
class Order:
    def __init__(self, customer, cart, promotion=None):
        self.customer = customer
        self.cart = list(cart)
        self.promotion = promotion
        
    def total(self):
        if not hasattr(self, '__total'):
            self.__total = sum(item.total() for item in self.cart)
        return self.__total
    
    def due(self):
        if self.promotion is None:
            discount = 0
        else:
            discount = self.promotion(self)
        return self.total() - discount
    
    def __repr__(self):
        fmt = ''
        return fmt.format(self.total(), self.due())

Order(joe, cart, fidelity_promo)
Out[174]: <Order total:42.00 due:42.00>

Order(ann, cart, fidelity_promo)
Out[175]: <Order total:42.00 due:39.90>

Order(joe, banana_cart, bulk_item_promo)
Out[176]: <Order total:30.00 due:28.50>

Order(joe, long_order, large_order_promo)
Out[177]: <Order total:10.00 due:9.30>

你可能感兴趣的:(python)