一等函数

一等函数即将函数看作一等对象。一等对象满足一下条件:

  1. 在运行时创建
  2. 能赋值给变量或数据结构中的元素
  3. 能作为参数传给函数
  4. 能作为函数的返回结果

1. 一等函数

例子1. 证明function是对象,调用__doc__属性,用type()检查它的类型

def factorial(n):
'''returns n!'''
return 1 if n < 2 else n * factorial(n-1) print(factorial(42)) #__doc__是函数对象众多属性的一个
print(factorial.__doc__)
#factorial是function类的实例
print(type(factorial))

例子2. Python中,函数可以作为变量,可以作为参数,符合一等对象的特性

def factorial(n):
'''returns n!'''
return 1 if n < 2 else n * factorial(n-1) #特性1.把函数赋值给变量
fact = factorial
print(fact(5)) #特性2.把函数作为参数传给函数
result = map(factorial, range(12))
print(result)
print(list(result))

2. 高阶函数(High-order function)

  1. 定义:接受函数作为参数,或者把函数作为结果返回的函数是高阶函数。
  2. 例如map函数是高阶函数,内置函数sorted也是,它的可选的key参数用于提供一个函数,应用到各个元素上排序。
  3. 大神Guido van Rossum不把Python当做函数式编程,但是Python有函数式编程的特性。

例子1. sorted中的key参数

fruits = ['strawberry', 'fig', 'cherry', 'apple', 'raspberry', 'banana']

#sorted默认按照首字母排序
print(sorted(fruits))
print(sorted(fruits, key=len))

Note: 如果想创建押韵词典,可以把各个单词反过来拼写,然后排序。

例子2. 根据反向拼写给一个单词列表排序(反向后按首字母)

fruits = ['strawberry', 'fig', 'cherry', 'apple', 'raspberry', 'banana']

def reverse(words):
return words[::-1] print(reverse('I love you!'))
print(sorted(fruits, key=reverse))

Note:在函数式编程范式中,最为人熟知的高阶函数有map、filter、reduce和apply(Python3已经移除)。如果想使用不定量的参数调用函数,可以编写fn(*args, **keywords)

3. map、filter和reduce的现代替代品

  1. 函数式语言通常会提供map、filter和reduce三个high-order function.在Python3中,map和filter是built-in函数(reduce在functools模块里,常用于求和,但内置的sum函数更好),但是引入列表推导式(list comprehensions)和生成器表达式(generator expressions),它们都具有map和filter的功能和更加好的易读性,所以以上提到的三个高阶函数显得没那么的重要了。
  2. In Python3, map和filter返回生成器(一种迭代器)

例子1. map和filter和列表推导式比较

def factorial(n):
'''returns n!'''
return 1 if n < 2 else n * factorial(n-1) #用map和list comprehensions求0!到5!
map1 = map(factorial, range(6))
listcomps1 = [factorial(n) for n in range(6)]
print('map1:', list(map1))
print('listcomps1:', listcomps1) #用map、filter、lambda和list comprehensions求0-5偶数项的阶乘
map_filter = map(factorial, filter(lambda n: n % 2, range(6)))
listcomps2 = [factorial(n) for n in range(6) if n % 2]
print(list(map_filter))
print(listcomps2) #print(list(filter(lambda n: n % 2, range(6))))

例子2. 使用reduce和sum计算0~99之和

from functools import reduce
from operator import add sum_reduce = reduce(add, range(100))
print(sum_reduce) #相比reduce(),sum()无需导入或者创建求和函数
sum_sum = sum(range(100))
print(sum_sum)
  1. sum和reduce的思想是把某个操作连续应用到序列的元素上,累计之前的结果,把一系列的值归约一个值。(分治?)
  2. all和any也是built-in的归约函数。
  3. all(iterable), iterable每个元素都是真值,返回True。all([1,0])返回False。
  4. any(iterable), iterable中有元素是真值,返回True。any([1,0])返回True。

4. 匿名函数(lambda function)

  1. 为了使用高阶函数,有时创建一次性小型函数(匿名函数)。
  2. 参数列表中最适合使用匿名函数。例如sorted中的key。
  3. Python中匿名函数的缺点

    Python简单的句法限制了lambda函数的定义体只能使用纯表达式。 即lambda函数里不能用while和try等逻辑语句,和不能赋值。
  4. 除了作为参数传给高阶函数,Python很少使用匿名函数,Python中的lambda表达式难读或者无法写出。
  5. 重构lambda函数的方法为注释,或者转换成def声明函数。
  6. lambda句法是语法糖:与def语句一样会创建函数对象。这是Python其中一种可调用对象。

例子1. 使用lambda函数翻转拼写,省掉reverse函数

fruits = ['strawberry', 'fig', 'cherry', 'apple', 'raspberry', 'banana']

print(fruits)
#反转后以首字母排序
print(sorted(fruits, key=lambda words: words[::-1]))

5. 可调用对象

  1. 判断对象能否调用,可以使用内置的callable()函数。
  2. Python数据模型文档给出了7中可调用对象:

    2.1 用户定义的函数(User-defined functions): def, lambda

    2.2 内置函数(Built-in functions):使用C语言(CPython)实现的函数,如len

    2.3 内置方法(Built-in methods):使用C语言实现的方法,如dict.get

    2.4 方法(Methods):在类的定义体中定义的函数

    2.5 类(Classes):特殊地,调用类时会运行__new__(),然后运行__init__(),最后将实例返回给调用者。因为Python没有new运算符,所以调用类相当于调用函数。(覆盖__new__()可能出现其他问题)

    2.6 类的实例(Class instances):如果类定义了__call__方法,那么它的实例可以作为函数调用

    2.7 生成器函数(Generator functions):使用yield关键字的函数或者方法。调用生成器函数返回的是生成器对象(生成器函数在很多方面与其他可调用对象不同,参见第14章。生成器函数还可以作为协程)

例子1. callable()结合列表推导式判断元素是否可调用

obj = [abs, str, 12]
for _ in obj:
print(type(_)) listcomps1 = [callable(x) for x in obj]
print(listcomps1)

6. 把类的实例变成可调用的对象--用户定义的可调用类型

  1. 不仅Python函数是对象,任何Python函数也可以变现得像函数。只需要实现__call__
  2. 实现__call__方法的类是创建函数类对象的简便方式,必须在内部维护一个状态,让它在调用之间可用,装饰器就是这样

例子1. BingoCage类的实例使用任何可迭代的对象构建,在内部存储一个随机顺序排列的列表。实现__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') #bingo.pick()的快捷方式是bingo()
def __call__(self):
return self.pick() bingo = BingoCage(range(5))
print('bingo.pick(): ', bingo.pick())
print('bingo(): ', bingo())
print('callable(bingo): ', callable(bingo))

7. 函数内省(Function Introspection)

用dir()可以知道一个函数具有什么属性

例子1. 列出常规对象没有而函数有的属性

class C: pass
obj = C()
def func(): pass diff = set(dir(func)) - set(dir(obj))
print(sorted(diff))

8.位置参数(Positional parameters)和关键字参数(Keyword parameters)

例子1. 函数参数中的*和**

"""
*args 是位置参数, 任意个参数会被*args捕获,存入一个元组;
**kw 是关键字参数,函数中没有明确制定名称的关键字参数会被**kw捕获,存入一个字典中。
""" def tag(*args, **kwargs):
print(type(args))
print(type(kwargs)) for _ in args:
print(_)
for key,value in kwargs.items():
print(key,value) t1 = tag('hello','world',name="allen",age=22)

调用函数时,用*和**展开可迭代对象,映射到单个参数。

例子2. tag函数用于生成HTML标签;使用名为cls的关键字参数传入'class'属性,这是一种变通方法,因为'class'是Python的关键字

def tag(name, *content, cls=None, **attrs):
"""生成一个或多个HTML标签"""
#class在Python3中为关键字,要用cls避开
if cls is not None:
attrs['class'] = cls
#有属性值就拼成HTML标签的属性值格式
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</%s>' %
(name, attr_str, c, name)
for c in content)
else:
return '<%s%s />' % (name, attr_str) print(tag('br'))
print(tag('h1', 'hello', 'world'))
print(tag('h2', 'h2', id=3))
print(tag('h3', 'h3', cls="class1", id=4))
print(tag('img', src='someURL')) my_tag = {
'name': 'img',
'title': 'Sunset',
'src': 'sunset.jpg',
'cls': 'framed',
} #在my_tag前面加上**,字典中的所有元素会被解包为单个参数传入,同名键会绑定到对应的具体参数上,余下的则被**attrs捕获。
print(tag(**my_tag))

例子3. 仅限keyword的参数,本例中只捕获关键字b,这叫仅限关键字参数

定义函数时若向指定仅限关键字参数(即固定位置参数,不捕获任意个位置参数),要把仅限关键字参数放在有*的参数后面。

def f(a, *, b):
return a, b print(f(1,b=2)) # f(1, c=2)会出错,仅限关键字b
print(f(1,2,b=2)) #错误,只能有1个位置参数
def f(a, *args, b):
return a, b print(f(1,b=2)) # f(1, c=2)会出错,仅限关键字b
print(f(1,2,b=2)) #正确,*和*args不同,*args可捕获多个位置参数,*则表示不捕获。

9. 函数内省,获取关于参数的信息

  1. 有点难,未搞定,先跳过。中文电子书P257
  2. HTTP微框架Bobo中有个使用函数内省的好例子。
import bobo
@bobo.query('/')
def hello(person):
return 'Hello %s!' % person
  1. 装饰器不是这个例子的关键。关键是,Bobo会内省hello函数,发现它需要一个名为person的参数,然后从请求中获取那个名称对应的参数,将其传给hello函数,因此程序员不用触碰请求对象。

    表面上,Bobo知道调用hello函数必须传入person参数,如果在请求中找不到同名参数,则看到的是"Missing form variable person".
  1. 函数对象的__defaults__属性,它的值是一个元组,里面保存着位置参数和关键字参数的默认值。__kwdefaults__属性中保存仅限关键字参数。参数的名称在__code__属性中,它的值是一个code对象引用,自身也有很多属性。
  1. 总结:在inspect模块的帮助下,Python数据模型把实参绑定给函数调用中的形参的机制,框架和IDE等工具可以使用这些信息验证代码。Python3的另外一个特性--函数注解增进了这些信息的用途。

10. 函数注解

  1. 跳过,难懂,中文电子书P263
  2. Python3提供了一种句法,用于为函数声明中的参数和返回值附加元数据。
  3. 函数注解可以让WEB框架自动处理请求。例如使用price:float注解的参数可以自动把查询字符串转换成函数期待的float类型;quantity:'int > 0'这样的字符串注解可以转换成对参数的验证。
  4. 函数注解的最大影响或许不是让WEB框架自动处理请求,而是为IDE和lint程序等工具中的静态类型检查功能提供额外的类型信息。

11. 支持函数式编程的包

  1. 得益于operator和functools等包的支持, Python可以使用函数式编程。
  2. 在函数式编程中,经常需要把算术运算符当做函数使用。例如,求和用sum()函数。例如作为sorted的key, reduce的第一个参数
  3. 可以用lambda function自己创建运算函数

例子1. 使用reduce函数和一个匿名函数计算阶乘

from functools import reduce

def fact(n):
return reduce(lambda a, b: a*b, range(1, n+1)) print(fact(5))

oeprator模块为多个算数运算符提供了对应的函数,从而避免编写匿名函数。

例子2. 使用reduce和operator.mul函数计算阶乘

from functools import reduce
from operator import mul def fact(n):
return reduce(mul, range(1, n+1)) print(fact(5))
  1. itemgetter(1)的作用与lambda fields: fields[1]一样。
  2. itemgetter()的常见用途:根据元组的某个字段给元组列表

    排序。
  3. 例如,按照国家代码(第二个字段)的顺序打印各个城市的信息。

例子3. itemgetter()创建一个函数,返回索引位上的元素。如果把多个值传给itemgetter,它构建的函数会提取的值构成的元组。

#元组列表
metro_data = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
] from operator import itemgetter
cc_name = itemgetter(1, 0) for city in metro_data:
print(cc_name(city)) print(cc_name([1,2]))

例子4. 使用itemgetter排序一个元组列表

#元组列表
metro_data = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
] #itemgetter(1)与lambda fields: fields[1]作用一样
from operator import itemgetter
for city in sorted(metro_data, key=itemgetter(1)):
print(city)

例子5. 使用attrgetter与itemgetter作用类似,它创建的函数根据名称提取对象的属性。

#元组列表
metro_data = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
] from collections import namedtuple
#1.使用namedtuple定义LatLong
LatLong = namedtuple('LatLong', 'lat long')
#2.使用namedtuple定义Metropolis. cc for city code, pop for population
Metropolis = namedtuple('Metropolis', 'name cc pop coord')
#3.构建Metropolis的列表。我们用元组拆包(tuple unpacking)提取(lat, long),然后用它们构建LatLong,作为Metropolis的coord属性。
metro_areas = [Metropolis(name, cc, pop, LatLong(lat,long))
for name, cc, pop, (lat, long) in metro_data]
print(metro_areas[0])
#4.获取metro_areas[0]的纬度。
print(metro_areas[0].coord.lat) from operator import attrgetter
#5.attrgetter()返回一个函数,获取name属性和嵌套的coord.lat属性。
name_lat = attrgetter('name', 'coord.lat')
#6.再次使用attrgetter(),按照纬度排序城市列表。
for city in sorted(metro_areas, key=attrgetter('coord.lat')):
#7.使用标号5中定义的attrgetter,只显示城市名和纬度。
print(name_lat(city))

例子6. 查看operator模块中定义的部分函数

省略以_开头的函数,因为它们基本上是实现细节

import operator

a = [name for name in dir(operator) if not name.startswith('_')]
print(a)

例子7. methodcaller创建的函数会在对象上调用参数指定的方法。

from operator import methodcaller

#第一个测试只是展示methodcaller的用法,用str.upper()替代。
s = 'The time has come'
upcase = methodcaller('upper')
print(upcase(s)) hyphenate = methodcaller('replace', ' ', '-')
print(hyphenate(s))

例子8. 使用functools.partial冻结参数(固定参数)

使用partial把一个两参数函数改编成需要单参数的可调用对象

把接受参数的函数改编成API

from operator import mul
from functools import partial triple = partial(mul, 3)
print(triple(7)) print(list(map(triple, range(0, 10))))

例子9. functools.partial结合04介绍的unicodedata.normalize函数

处理多国语言编写的文本,在比较或排序之前可能会想使用unicodedata.normalize('NFC', s)处理所有字符串s。如果经常这么做,可以定义个nfc函数。

import unicodedata
import functools nfc = functools.partial(unicodedata.normalize, 'NFC') str1 = 'café'
str2 = 'cafe\u0301'
print(str1, str2, str1 == str2, str1 is str2)
print(len(str1), len(str2)) str1 = nfc(str1)
str2 = nfc(str2)
print(str1, str2, str1 == str2, str1 is str2)
print(len(str1), len(str2))
print(nfc.args, nfc.func)

partial的第一个参数是一个可调用对象,后面跟着任意个要绑定的位置参数和关键字参数。

总结

  1. 本章探讨Python函数即对象的特性。That is, 我们可以把函数赋值给变量、作为其他函数的参数、存储在数据结构中以及访问函数的属性,供框架和一些工具使用。
  2. 高阶函数是函数式编程的重要组成部分,即使现在不像以前那样经常使用map、filter和reduce函数,但还是有列表推导式、生成器表达式以及sum、all、和any等内置的归约函数。
  3. Python中常用的高阶函数有内置函数sorted、min、max和functools.partial
  4. Python有7种callable对象, 从lambda表达式创建的简单函数到实现__call__方法的类实例。这些可调用对象都能通过内置的callable() 函数检测。
  5. 每一种可调用对象都支持使用相同的丰富句法声明形式参数,包括仅限关键字参数(keyword-only parameter)和注解,两者都是Python3的新特性。
  6. Python函数及其注解有丰富的属性,在inspect模块的帮助下,可以读取它们。例如, Signature.bind方法使用灵活的规则把实参绑定到形参上。
  7. 有了operator和functools这些函数,函数式编程就不太需要功能有限的lambda表达式了。

Fluent_Python_Part3函数即对象,05-1class-func,一等函数,函数即对象的更多相关文章

  1. javascript世界一等公民—函数

    简介 在很多传统语言(C/C++/Java/C#等)中,函数都是作为一个二等公民存在,你只能用语言的关键字声明一个函数然后调用它,如果需要把函数作为参数传给另一个函数,或是赋值给一个本地变量,又或是作 ...

  2. JavaScript世界的一等公民—— 函数

    简介 在很多传统语言(C/C++/Java/C#等)中,函数都是作为一个二等公民存在,你只能用语言的关键字声明一个函数然后调用它,如果需要把函数作为参数传给另一个函数,或是赋值给一个本地变量,又或是作 ...

  3. javascript语言中的一等公民-函数

    简介 在很多传统语言(C/C++/Java/C#等)中,函数都是作为一个二等公民存在,你只能用语言的关键字声明一个函数然后调用它,如果需要把函数作为参数传给另一个函数,或是赋值给一个本地变量,又或是作 ...

  4. python12--字符串的比较 函数的默认值的细节 三元表达式 函数对象 名称空间 作用域 列表与字典的推导式 四则运算 函数的嵌套

     复习   1.字符串的比较; 2.函数的参数; ******实参与形参的分类: 3.函数的嵌套调用:     # 字符串的比较#  -- 按照从左往右比较每一个字符,通过字符对应的ascii进行比较 ...

  5. python学习Day12 函数的默认值、三元表达式、函数对象(函数名)的应用场景、名称空间与作用域

    复习 1.字符串的比较: -- 按照从左往右比较每一个字符,通过字符对应的ascii进行比较 2. 函数的参数 : 1)实参与形参:       -- 形参:在函数定义时()中出现的参数       ...

  6. day_12函数默认值,数据类型的补充,函数对象名称空间与作用域,函数的嵌套定义

    复习, 昨天讲了字符串的比较,按照从左往右比较每一个字符,通过字符对应的ASCII码进行比较 函数的参数,‘ 实参与形参 形参:在函数定义时()中出现的参数 实参,在函数调用时()中出现的参数 实参的 ...

  7. JavaScript世界的一等公民 - 函数

    简介 在很多传统语言(C/C++/Java/C#等)中,函数都是作为一个二等公民存在,你只能用语言的关键字声明一个函数然后调用它,如果需要把函数作为参数传给另一个函数,或是赋值给一个本地变量,又或是作 ...

  8. 黑马oracle_day02:04.oracle对象&&05.oracle编程(a)

    01.oracle体系结构 02.oracle的基本操作 03.oracle的查询 04.oracle对象&&05.oracle编程(a) 05.oracle编程(b) 04.orac ...

  9. JS的数据类型判断函数、数组对象结构处理、日期转换函数,浏览器类型判断函数合集

    工具地址:https://github.com/BothEyes1993/bes-jstools bes-jstools 100多个基础常用JS函数和各种数据转换处理集合大全,此工具包是在 outil ...

随机推荐

  1. vue插槽的使用

    一.插槽的基本使用 二.具名插槽的使用 三.编译作用域的例子 四.作用域插槽 一.插槽的基本使用     1.插槽的基本使用<slot></slot>     2.插槽的默认值 ...

  2. java多线程--死锁

    1. Java中导致死锁的原因 Java中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2 ...

  3. HIT大作业——hello的一生

    hello的一生 关键词:计算机系统:功能:流程:P2P;O2O;hello                              目  录   第1章 概述- 4 - 1.1 Hello简介 - ...

  4. layui下拉框数据过万渲染渲染问题解决方案

    方案一:layui下拉框分页插件 https://fly.layui.com/jie/29002/ 此插件我用了下浏览器缓存有问题,而且当下拉框数据量过万后,会一直渲染不出来,期待后期作者优化 如图下 ...

  5. nginx知识学习

    设备: macbook 有用的命令行: sudo nginx -t  测试nginx是否正常 sudo nginx -s reload  平滑重启 配置目录: /usr/local/etc/nginx ...

  6. 【14】N的二进制中1的个数

    class Solution { public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> h = new ...

  7. python 序列 倒着取元素

    当要倒着取元素时,用s[-2]只能取一个, 如果取多个时用s[-9:-1],注意,最后一个-1是不取出来的. 此时要用s[-9:] 最后一个空着就可以取出来了.

  8. C语言数据结构——第三章 栈和队列

    三.栈和队列 栈和队列是两种重要的线性结构.从数据结构的角度来看,栈和队列也是线性表,它的特殊性在于栈和队列的基本操作是线性表操作的子集,它们的操作相对于线性表来说是受到限制的,因此,可以称其为限定性 ...

  9. luckyframe的一些坑

    建议使用idea运行 1.第一次运行访问http://localhost:8088/LuckyFrameServer 2.luckyframe提示“javax.net.ssl.SSLKeyExcept ...

  10. Bugku-CTF加密篇之进制转换(二进制、八进制、十进制、十六进制,你能分的清吗?)

    进制转换 二进制.八进制.十进制.十六进制,你能分的清吗?