一等函数
把函数视作对象
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