记录博客 ZH-BLOG

Python 函数

时间:2018-08-06 18:00:00分类:python

在 Python 中, 函数是一等对象

1).在运行时创建

2).能赋值给变量或数据结构中的元素

3).能作为参数传给函数

4).能作为函数的返回结果

>>> def factorial(n):
	"""返回 n 的阶层"""
	return 1 if n < 2 else n * factorial(n-1)

>>> factorial(4)
24
>>> factorial.__doc__
'返回 n 的阶层'
>>> type(factorial) # function 类实例

>>> help(factorial) # __doc__ 属性用于生成对象的帮助文档
Help on function factorial in module __main__:

factorial(n)
    返回 n 的阶层

>>> fact = factorial # 赋值给变量
>>> fact

>>> fact(5) # 通过变量名调用
120
>>> map(fact, range(11)) # 可以传递给其它函数作为参数

>>> list(map(fact, range(11)))
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

高阶函数

接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higherorder function)。内置函数 map 和 sorted 都是高阶函数。

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

>>> sorted(fruits, key=reverse)
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

map、 filter 和 reduce

>>> list(map(fact, range(6)))
[1, 1, 2, 6, 24, 120]
>>> [fact(n) for n in range(6)] # 列表推导替代 map
[1, 1, 2, 6, 24, 120]
>>> list(map(fact, filter(lambda n : n % 2, range(6))))
[1, 6, 120]
>>> [fact(n) for n in range(6) if n % 2] # 列表推导替代 filter
[1, 6, 120]
>>> from functools import reduce
>>> from operator import add
>>> reduce(add, range(100))
4950
>>> sum(range(100)) # 内置函数 sum 替代 reduce
4950

内置的归约函数 all 和 any

all(iterable):

如果 iterable 的每个元素都是真值, 返回 True; all([]) 返回 True。

any(iterable):

只要 iterable 中有元素是真值, 就返回 True; any([]) 返回 False。

>>> all(range(2))
False
>>> any(range(2))
True

匿名函数

lambda 关键字在 Python 表达式内创建匿名函数。正常除了作为参数传给高阶函数之外, Python 很少使用匿名函数。

>>> fruits
['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=lambda word : word[::-1])
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

Python 数据模型文档列出了 7 种可调用对象

1). 用户定义的函数

使用 def 语句或 lambda 表达式创建。

2). 内置函数

使用 C 语言(CPython) 实现的函数,如 len 或 time.strftime。

3). 内置方法

使用 C 语言实现的方法, 如 dict.get。

4). 方法

在类的定义体中定义的函数。

5). 类

调用类时会运行类的 __new__ 方法创建一个实例,然后运行 __init__ 方法,初始化实例,最后把实例返回给调用方。因为 Python没有 new 运算符,所以调用类相当于调用函数。

6). 类的实例

如果类定义了 __call__ 方法,那么它的实例可以作为函数调用。

7). 生成器函数

使用 yield 关键字的函数或方法。调用生成器函数返回的是生成器对象。

import random

class BingoCage:

    def __init__(self, items):
        self._items = list(items)
        random.shuffle(self._items)


    def pick(self):
        try:
            return self._items.pop() # 列表为空抛出 IndexError
        except IndexError:
            raise LookupError('pick from empty BingoCage')


    def __call__(self):
        return self.pick()
>>> import os
>>> os.chdir(r'C:\Users\Administrator\Desktop')
>>> from call import BingoCage
>>> bingo = BingoCage(range(3))
>>> bingo.pick()
1
>>> bingo()
2
>>> callable(bingo)
True

函数内省

函数对象有很多属性。使用 dir 函数可以查看。

>>> dir(fact)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

其中大多数属性是 Python 对象共有的。

函数有而常规对象没有的属性:

>>> class C: pass

>>> obj = C()
>>> def func(): pass

>>> sorted(set(dir(func)) - set(dir(obj)))
['__annotations__', '__call__', '__closure__', '__code__', '__defaults__', '__get__', '__globals__', '__kwdefaults__', '__name__', '__qualname__']

__annotations__: dict 类型,参数和返回值的注解

__call__: methodwrapper 类型, 实现 () 运算符;即可调用对象协议

__closure__: tuple 类型,函数闭包,即自由变量的绑定(通常是 None)

__code__: code 类型,编译成字节码的函数元数据和函数定义体

__defaults__: tuple 类型,形式参数的默认值

__get__: methodwrapper 类型,实现只读描述符协议

__globals__: dict 类型,函数所在模块中的全局变量

__kwdefaults__: dict 类型,仅限关键字形式参数的默认值

__name__: str 类型,函数名称

__qualname__: str 类型,函数的限定名称, 如 Random.choice

参数处理

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

# name必填,可以第一个传入或者具名参数传入
# *content, * 号表示任意个参数传入,不具名 (可迭代对象)
# cls=None 表示只能具名 cls 传入
# **attrs, ** 表示可多个具名参数传入,(看成字典)
def tag(name,*content,cls=None,**attrs):
if cls:
attrs['class']=cls
if attrs:
attrs_str=''.join(' %s=\'%s\''% (key,val) for key,val in attrs.items())
else:
attrs_str=''

if content:
return ''.join('<%s%s>%s</%s>'% (name,attrs_str,con,name) for con in content)
else:

return '<%s%s/>'% (name,attrs_str)


>>> tag('br')
'<br />'
>>> tag('p', 'hello')
'<p>hello</p>'
>>> print(tag('p', 'hello', 'world'))
<p>hello</p>
<p>world</p>>>> tag('p', 'hello', id=33)
'<p id="33">hello</p>'
>>> print(tag('p', 'hello', 'world', cls='sidebar'))
<p class="sidebar">hello</p>
<p class="sidebar">world</p>
>>> tag(content='testing', name="img")
'<img content="testing" />'
>>> my_tag = {'name': 'img', 'title': 'Sunset Boulevard',
... 'src': 'sunset.jpg', 'cls': 'framed'}
>>> tag(**my_tag)
'<img class="framed" src="sunset.jpg" title="Sunset Boulevard" />'

内省方法

函数对象有个 __defaults__ 属性, 它的值是一个元组,里面保存着定位参数和关键字参数的默认值。仅限关键字参数的默认值在__kwdefaults__ 属性中。然而,参数的名称在 __code__ 属性中,它的值是一个 code 对象引用,自身也有很多属性。

def clip(text, max_len=80):
    """在max_len前面或后面的第一个空格处截断文本"""
    end = None
    if len(text) > max_len:
        space_before = text.rfind(' ', 0, max_len)
        if space_before >= 0:
            end = space_before
        else:
            space_after = text.rfind(' ', max_len)
            if space_after >= 0:
                end = space_after

if end is None: # 没找到空格
    end = len(text)
return text[:end].rstrip()

>>> clip.__defaults__
(80,)
>>> clip.__code__
code object clip at 0x0000000002E3BA50, file "C:\Users\Administrator\Desktop\clip.py", line 2
>>> clip.__code__.co_varnames
('text', 'max_len', 'end', 'space_before', 'space_after')
>>> clip.__code__.co_argcount
2

这种组织信息的方式并不方便,而且不能判断出默认值是属于 text 还是 max_len。

inspect 模块

>> > from clip import clip
>> > from inspect import signature
>> > sig = signature(clip)
>> > sig
< Signature(text, max_len=80) >
>> > str(sig)
'(text, max_len=80)'
>> > for name, param in sig.parameters.items():
print(param.kind, ':', name, '=', param.default)

POSITIONAL_OR_KEYWORD: text = <class 'inspect._empty'>
POSITIONAL_OR_KEYWORD: max_len = 80


inspect.signature 函数返回一个 inspect.Signature 对象,它有一个 parameters 属性,这是一个有序映射,把参数名和 inspect.Parameter 对象对应起来。各个Parameter 属性也有自己的属性, 例如 name、 default 和 kind。特殊的 inspect._empty 值表示

没有默认值。


signature.bind()

>>> sig = signature(tag)
>>> tag_params = {'name': 'img', 'title': 'you', 'src': '6.jpg', 'cls': 'image'}
>>> bound_args = sig.bind(**tag_params)
>>> bound_args

>>> for name, value in bound_args.arguments.items():
	print(name, '=', value)

	
name = img
cls = image
attrs = {'title': 'you', 'src': '6.jpg'}
>>> del tag_params['name']
>>> bound_args = sig.bind(**tag_params)
Traceback (most recent call last):
  ...
TypeError: missing a required argument: 'name'

函数注解

def clip(text:str, max_len:'int > 0'=80) -> str: """在max_len前面或后面的第一个空格处截断文本
"""
end = None
if len(text) > max_len:
space_before = text.rfind(' ', 0, max_len)
if space_before >= 0:
end = space_before
else:
space_after = text.rfind(' ', max_len)
if space_after >= 0:
end = space_after
if end is None: # 没找到空格
end = len(text)
return text[:end].rstrip()

Python 对注解所做的唯一的事情是,把它们存储在函数的 __annotations__ 属性里。

>>> from clip_annot import clip
>>> clip.__annotations__
{'text': , 'max_len': 'int > 0', 'return': }

>>> from clip_annot import clip
>>> from inspect import signature
>>> sig = signature(clip)
>>> sig.return_annotation
<class 'str'>
>>> for param in sig.parameters.values():
... note = repr(param.annotation).ljust(13)
... print(note, ':', param.name, '=', param.default)
<class 'str'> : text = <class 'inspect._empty'>
'int > 0' : max_len = 80


函数式编程

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

>>> from operator import mul
>>> def fact(n):
	return reduce(mul, range(1, n+1))

operator.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
>>> for city in sorted(metro_data, key=itemgetter(1)):
	print(city)

	
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833))
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))
('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
('Mexico City', 'MX', 20.142, (19.433333, -99.133333))
('New York-Newark', 'US', 20.104, (40.808611, -74.020386))
>>> cc_name = itemgetter(1, 0)
>>> for city in metro_data:
	print(cc_name(city))

	
('JP', 'Tokyo')
('IN', 'Delhi NCR')
('MX', 'Mexico City')
('US', 'New York-Newark')
('BR', 'Sao Paulo')

operator.attrgetter

>>> from collections import namedtuple
>>> LatLong = namedtuple('LatLong', 'lat long')
>>> Metropolis = namedtuple('Metropolis', 'name cc pop coord')
>>> metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long)) for name, cc, pop, (lat, long) in metro_data]
>>> metro_areas[0]
Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLong(lat=35.689722, long=139.691667))
>>> metro_areas[0].coord.lat
35.689722
>>> from operator import attrgetter
>>> name_lat = attrgetter('name', 'coord.lat')
>>> for city in sorted(metro_areas, key=attrgetter('coord.lat')):
	print(name_lat(city))

	
('Sao Paulo', -23.547778)
('Mexico City', 19.433333)
('Delhi NCR', 28.613889)
('Tokyo', 35.689722)
('New York-Newark', 40.808611)

operator.methodcaller

>>> from operator import methodcaller
>>> s = 'The time has come'
>>> upcase = methodcaller('upper')
>>> upcase(s)
'THE TIME HAS COME'
>>> rep = methodcaller('replace', ' ', '-')
>>> rep(s)
'The-time-has-come'

functools.partial 冻结参数

>>> from operator import mul
>>> from functools import partial
>>> triple = partial(mul, 3)
>>> triple(7)
21
>>> list(map(triple, range(1, 10)))
[3, 6, 9, 12, 15, 18, 21, 24, 27]
>>> tag
<function tag at 0x00000000030FA488>
>>> picture = partial(tag, 'img', cls='image')
>>> picture(src='6.jpg')
'<img class="image" src="6.jpg" />'
>>> picture
functools.partial(<function tag at 0x00000000030FA488>, 'img', cls='image')
>>> picture.func
<function tag at 0x00000000030FA488>
>>> picture.args
('img',)
>>> picture.keywords
{'cls': 'image'}