深入理解python中的函数

一等函数

把函数视作对象

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

def factorial(n):
    """return n!"""
    return 1 if n < 2 else n*factorial(n-1)

print(factorial(10))    # 3628800
# __doc__ 是函数对象众多属性中的一个,用于生成对象的帮助文本
print(factorial.__doc__)    # return n!
print(type(factorial))  # <class 'function'>

# 我们可以把factorial 函数对象给变量fact,然后通过变量名调用
fact = factorial
print(fact)     # <function factorial at 0x0000021A9F7E8400>
120
print(fact(5))    # 120
list(map(fact, range(11)))   
 # [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

高阶函数

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

fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
# 内置函数sorted就是高阶函数:可选的key 参数用于提供一个函数
sorted(fruits, key=len)     
# ['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']

def reversed(word):
    return word[::-1]
sorted(fruits, key=reversed)    
# ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

# 匿名函数lambda
sorted(fruits, key=lambda word:word[::-1])
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

除此之外,常见的高阶函数还有map,filter, functools.reduce,map 和filter 返回生成器。

可调用对象
除了用户定义的函数,调用运算符(即())还可以应用到其他对象上。如果想判断对象能否调用,可以使用内置的callable() 函数

>>> [callable(obj) for obj in (abs, str, 13, reversed)]
[True, True, False, True]

""" 不仅Python 函数是真正的对象,任何Python 对象都可以表现得像函数"""
class BingoCage(object):
    def __init__(self, items):
        self.items = list(items)
    def pick(self):
        try:
            return self.items.pop()
        except IndentationError:
            raise LookupError('pick from empty Bingocage')
    def __call__(self):
        print('call me!')
        return self.pick()
        
>>> bingo = BingoCage(range(3))
>>> bingo.pick()    
2
>>> bingo()     
call me! 
1
>>> bingo()     
call me! 
1

函数内省
使用dir()函数查看函数的属性

>>> dir(factorial)
['__annotations__',
 '__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 ...]

仅限关键字参数
调用函数时使用* 和**是可迭代对象,映射到单个参数

def func(name, *args, age=18, **kwargs):
    for s in args:
        print(s)
    if kwargs:
        for k,v in kwargs.items():
            print(k,v)
    print("age is :",age)
    print("my name is :",name)
    
>>> func('john')
age is : 18
my name is : john

""" 第一个参数后面的任意个参数会被*args 捕获,存入一个元组 """
>>> func('john','alice','bob')
alice
bob
age is : 18
my name is : john

""" 没有明确指定名称的关键字参数会被**kwargs 捕获,存入一个字典
 age参数只能作为关键字参数传入"""
>>> func('john','alice',age=30,hallie=12, eric=31)
eric 12
age is : 18
my name is : zude

operator模块
operator 模块为多个算术运算符提供了对应的函数,从而避免编写lambda a, b: a*b 这种匿名函数

from functools import reduce
from operator import mul
from operator impoer itemgetter

def fact1(n):
    return reduce(lambda a,b:a*b, range(1, n+1))
def fact2(n):
    return reduce(mul ,range(1, n+1))
    
>>> metro_data = [
... ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
... ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))]

# 任何实现__getitem__ 方法的类都可以使用itemgetter
>>> for city in sorted(metro_data, key=itemgetter(1)):
        print(city)

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

来看一个小例子,假如一个网店制定了下述折扣规则

  • 有1000 或以上积分的顾客,每个订单享5% 折扣。
  • 同一订单中,单个商品的数量达到20 个或以上,享10% 折扣。
  • 订单中的不同商品达到10 个或以上,享7% 折扣。
    简单起见,我们假定一个订单一次只能享用一个折扣。 我们使用抽象基类和函数实现策略,代码如下
from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')

class LineItem(object):
    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(object):
    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 = '<Order total: {:.2f} due: {:.2f}>'
        return fmt.format(self.total(), self.due())

# 把Promotion定义为抽象基类(Abstract Base CLASS),
# 为了使用@abstractmethod装饰器,表明所用的模式
class Promotion(ABC):
    @abstractmethod
    def discount(self, order):
        """返回折扣金额(正值)"""

class FidelityPromo(Promotion):
    """为积分为1000或以上的顾客提供5%折扣"""
    def discount(self, order):
        return order.total() * .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() * .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() * .07
        return 0

结果测试如下:

joe = Customer('John Doe', 0)
ann = Customer('Ann Smith', 1100)
cart = [LineItem('banana', 4, .5), 
        LineItem('apple', 10, 1.5),
        LineItem('watermellon', 5, 5.0)]
Order(joe, cart, FidelityPromo()) # <Order total: 42.00 due: 42.00>
Order(ann, cart, FidelityPromo()) # <Order total: 42.00 due: 39.90>

我们也可以进行迭代操作,找到折扣额度最大的

promos = [fidelity_promo, bulk_item_promo, large_order_promo] 
def best_promo(order): 
    """选择可用的最佳折扣"""
    return max(promo(order) for promo in promos)

函数装饰器和闭包

装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即Python 加载模块时),如示例所示

registry = []
def register(func): 
    print('running register(%s)' % func)
    registry.append(func)
    return func 

@register 
def f1():
    print('running f1()')

@register
def f2():
    print('running f2()')

def f3(): 
    print('running f3()')

def main(): 
    print('running main()')
    print('registry ->', registry)
    f1()
    f2()
    f3()
    
if __name__=='__main__':
    main()
    
results:
running register(<function f1 at 0x00000220D84EA488>)
running register(<function f2 at 0x00000220D84EA1E0>)
running main()
registry -> [<function f1 at 0x00000220D84EA488>, <function f2 at 0x00000220D84EA1E0>]
running f1()
running f2()
running f3()

函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用 时运行。这突出了Python 程序员所说的导入时和运行时之间的区别

闭包
在函数体内定义函数

def make_averager():
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return averager

avg = make_averager()
print(avg(10)) # 10.0
print(avg(11) # 10.5

"""
series 是make_averager 函数的局部变量,因为那个函数的定义体中初始化了
series:series = []。可是,调用avg(10) 时,make_averager 函数已经返回了,
而它的本地作用域也一去不复返了
"""
# 审查返回的average对象
avg.__code__.co_varnames   # ('new_value', 'total')

# 自由变量
avg.__code__.co_freevars    # ('series',)

# 有个cell_contents 属性,保存着真正的值
avg.__closure__[0].cell_contents    # [10, 11]

nonlocal声明

def make_averager():
    count = 0
    total = 0
    def averager(new_value):
        count += 1
        total += new_value
        return total / count
    return averager

avg = make_averager()
print(avg(10))

---------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-25-3f4d448a9326> in <module>()
      9 
     10 avg = make_averager()
---> 11 print(avg(10))

<ipython-input-25-3f4d448a9326> in averager(new_value)
      3     total = 0
      4     def averager(new_value):
----> 5         count += 1
      6         total += new_value
      7         return total / count

UnboundLocalError: local variable 'count' referenced before assignment

问题是,当count 是数字或任何不可变类型时,count += 1 语句的作用其实与count = count + 1 一样。因此,我们在averager 的定义体中为count 赋值了,这会把count 变成局部变量。total 变量也受这个问题影响。上个例子没遇到这个问题,因为我们没有给series 赋值,我们只是调用series.append,并把它传给sum 和len。也就是说,我们利用了列表是可变的对象这一事实。
但是对数字、字符串、元组等不可变类型来说,只能读取,不能更新。如果尝试重新绑定,例如count = count+1,其实会隐式创建局部变量count。这样,count 就不是自由变量了,因此不会保存在闭包中

使用nonlocal把变量标记为自由变量,即使在函数中为变量赋予新值了,也会变成自由变量

def make_averager():
    count = 0
    total = 0
    def averager(new_value):
        nonlocal count, total
        count += 1
        total += new_value
        return total/count
    return averager

avg = make_averager()
print(avg(10)) # 10.0
print(avg(11)) # 10.5

更多关于装饰器的内容可以参考:https://blog.csdn.net/John_xyz/article/details/84190920

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值