pythoncookbook 第7章 函数

[toc]

第7章 函数

7.1 任意参数

*args **kwargs 使用

7.2 只能接受关键字参数 (py3)

  • 关键字 参数只能放在 位置参数的后面
def recv(maxsize, *, block):
'Receives a message'
    pass
recv(1024, True) # TypeError
recv(1024, block=True) # Ok 在*后面的关键字的位置参数,只能作为关键字参数才能起作用
# 相当于强制启用关键字参数,使recv()的调用更清晰

7.3 函数添加注解

-不推荐使用,用3引号的就可以了

def add(x:int, y:int) -> int:
return x + y

存储于add.__annotations__

7.4 返回多个的函数

  • 函数返回多个值时候,其实际返回的是一个元组
  • ",,"以创建元组

7.5 默认参数

  • 1 默认参为不可变对象
def apam (a,b=42):
    return a,b
  • 2 默认参为可变对象
    默认参数是一个可修改的容器比如一个列表、集合或字典,可以使用 None作为默认值
def spam(a, b=None):
    if b is None:
        b = []

原因看下面代码,python的变量名称为一个对象的指针,对象为可变对象的话,内部是可以变化的。导致看上去你的默认参数在变。

def func(x,y=[]):
    y.append(x)
    return x,y

print func(1)
print func(2)

object()返还一个对象,None是一个对象。

_no_value = object()
def spam(a, b=_no_value):
    if b is _no_value:
        print('No b value supplied')
spam(123,None)
spam(123)

几点tips:

  • 默认参数的值仅仅在函数定义的时候赋值一次。
  • 默认参数不可,使用可变对象(原因看上文)
  • 长度为 0 的字符串、列表、元组、字典等都会被当做 False。
def spam(a, b=None):
    if not b: # NO! Use 'b is None' instead
        b = []
>>>spam(1)
>>>x = []
>>>spam(1,x) # Silent error. x value overwritten b
>>>spam(1,0) # Silent error. 0 ignored
>>>spam(1,'') # Silent error. '' ignored

object 是 python 中所有类的基类。
你可以创建 object 类的实例,但是这些实例没什么实际用处,因为它并没有任何有用
的方法,也没有哦任何实例数据 (因为它没有任何的实例字典,你甚至都不能设置任何
属性值)。你唯一能做的就是测试同一性。这个刚好符合我的要求,因为我在函数中就
只是需要一个同一性的测试而已。

7.6 匿名函数lambda

  • 常用于排序时的表达式
  • 惰性调用,存在
  • 让某个对象变成能callable

7.7 匿名函数捕获变量值

x =  10
a = lambda y: x+y
x = 20
b = lambda y: x+y
a(10) #30
b(10) #30

这其中的奥妙在于 lambda 表达式中的 x 是一个自由变量,在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。

注意不正确的使用列表推倒

funcs = [lambda x: x+n for n in range(5)]
for f in funcs:
print f(0)

正确的写成

funcs = [lambda x, n=n: x+n for n in range(5)]

原因解析:lambda 函数运行时绑定内部参数

funcs=[<lambda object0>,<lambda object1>,<lambda object2>...<lambda object4>]

7.8 减少可调用对象的参数个数

functools.partial() 函数允许你给一个或多个参数设置固定的值,减少接下来被调用时的参数个数。

def spam(a, b, c, d):
    print(a, b, c, d)
from functools import partial
s1 = partial(spam, 1) # a = 1
s1(2, 3, 4)
>>>1 2 3 4 

partial() 固定某些参数并返回一个新的 callable 对象。这个新的 callable接受未赋值的参数,然后跟之前已经赋值过的参数合并起来,最后将所有参数传递给原始函数。
更进一步,partial() 通常被用来微调其他库函数所使用的回调函数的参数。

def output_result(result, log=None):
    if log is not None:
        log.debug('Got: %r', result)


# A sample function
def add(x, y):
    return x + y


if __name__ == '__main__':
    import logging
    from multiprocessing import Pool
    from functools import partial
    logging.basicConfig(level=logging.DEBUG)
    log = logging.getLogger("__file__")
    p = Pool()
    p.apply_async(add, (3, 4), callback=partial(output_result, log=log))
    p.close()  # 调用join之前,先调用close函数,否则会出错。
    p.join() # 执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束

partial在类实例化的使用

class EchoHandler(StreamRequestHandler):
# ack is added keyword-only argument. *args, **kwargs are
# any normal parameters supplied (which are passed on)
    def __init__(self, *args, ack, **kwargs):
        self.ack = ack
        super(EchoHandler,self).__init__(*args, **kwargs)
    def handle(self):
        for line in self.rfile:
            self.wfile.write(self.ack + line)

from functools import partial
serv = TCPServer(('', 15000), partial(EchoHandler, ack=b'RECEIVED:'))

7.9 两种保留函数外部环境变量的方法

  • 1 为函数的闭包
def urltemplate(template):
    def opener(**kwargs):
        return urlopen(template.format_map(kwargs))
return opener
yahoo = urltemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}')
  • 2 为类的实现
from urllib.request import urlopen
class UrlTemplate:
    def __init__(self, template):
        self.template = template
    def open(self, **kwargs):
        return urlopen(self.template.format_map(kwargs))

$这其实也是两类装饰器的实现原理$

7.10 带额外状态信息的回调函数(*)

一般回调函数,载回调后,内部的变量,会被内存回收。有时候,需要回调函数状态变量。
用人话:如有一个“内部”变量保存回调函数的调用次数

我们先定义如下一个需要调用回调函数的函数:

def apply_async(func, args, *, callback):
    # Compute the result
    result = func(*args)
    # Invoke the callback with the result
    callback(result)

使用这个函数

def print_result(result):
    print('Got:', result)

def add(x, y):
    return x + y
apply_async(add, (2, 3), callback=print_result) # Got: 5
apply_async(add, ('hello', 'world'), callback=print_result) # Got: helloworld

注意到 print result() 函数仅仅只接受一个参数 result 。不能再传入其他信息。
而当你想让回调函数访问其他变量或者特定环境的变量值的时候就会遇到麻烦。

  • 1 类方法的实现
class ResultHandler:
    def __init__(self):
        self.sequence = 0
    def handler(self, result):
        self.sequence += 1
        print('[{}] Got: {}'.format(self.sequence, result))

如何使用这个方法

r = ResultHandler()
apply_async(add, (2, 3), callback=r.handler) 
#[1] Got: 5
apply_async(add, ('hello', 'world'), callback=r.handler)
#[2] Got: helloworld
  • 2 闭包捕获状态值
def make_handler():
    OneClass = type(repr('OneClass'), (object,), {"squence": 0}) #或者用字典
    def handler(result):
        #nonlocal sequence 这个nonlocal方法py2 没有,用类属性来传递变量
        OneClass.squence += 1
        print('[{}] Got: {}'.format(sequence, result))
    return handler

如何使用这个方法

handler = make_handler()
apply_async(add, (2, 3), callback=handler) 
# [1] Got: 5
apply_async(add, ('hello', 'world'), callback=handler)
# [2] Got: helloworld

总结:函数内部的变量,不会随函数结束,被回收。用两种方法被外部函数所记录。其实和7.9节差不多

  • 3 还有个更高级的办法,携程
def make_handler():
    sequence = 0
    while True:
        result = yield
        sequence += 1
        print('[{}] Got: {}'.format(sequence, result))

如何使用这个方法

handler = make_handler() # 产生一个生成器
handler.next() # 或者 handler.send(None) 运行里面的代码,遇到yield停下
apply_async(add, (2, 3), callback=handler.send) 
# [1] Got: 5
apply_async(add, ('hello', 'world'), callback=handler.send)
# [2] Got: helloworld

7.11 内联回调函数(*)

def apply_async(func, args, callback):
    # Compute the result
    result = func(*args)
    # Invoke the callback with the result
    callback(result)

from Queue import Queue
from functools import wraps



def inlined_async(func):
    @wraps(func)
    def wrapper(*args):
        f = func(*args)
        result_queue = Queue()
        result_queue.put(None)
        while True:
            result = result_queue.get()
            try:
                a = f.send(result)
                apply_async(a.func, a.args, callback=result_queue.put)
            except StopIteration:
                break
    return wrapper


def add(x, y):
    return x + y


@inlined_async
def test():
    r = yield Async(add, (2, 3))
    print r
    r = yield Async(add, ('hello', 'world'))
    print r
    for n in range(10):
        r = yield Async(add, (n, n))
        print r
    print "Goodbye"


class Async:
    def __init__(self, func, args):
        self.func = func
        self.args = args


if __name__ == '__main__':
    import multiprocessing
    pool = multiprocessing.Pool()
    apply_async = pool.apply_async
    # Run the test function
    test()

只能明白基础的流程,同一个回调函数,一系列的任务函数。

7.12 访问闭包中定义的变量

你想要扩展函数中的某个闭包,允许它能访问和修改函数的内部变量。

def sample():
    N = type(repr('OneClass'), (object,), {"value": 0}) #用类属性传递值
    #N = {"value":0}
 
    # Closure function
    def func():
        print'n=', N.value  #N["value"]
    # Accessor methods for n
    def get_n():
        return N.value
    def set_n(x):
        N.value = x
    # Attach as function attributes
    func.get_n = get_n
    func.set_n = set_n
    return func
 
f = sample()
f()
f.set_n(10)
f()
f.get_n()

在闭包的函数中定义,定义一些函数,并将这些函数变为内部函数的属性,来查看设置闭包函数的变量

同样,也可以用来的实现,内部的函数.

import sys
class ClosureInstance:
    def __init__(self, locals=None):
        if locals is None:
            locals = sys._getframe(1).f_locals
        # Update instance dictionary with callables
        self.__dict__.update((key,value) for key, value in locals.items()
                                    if callable(value) )
    # Redirect special methods
    def __len__(self):
        return self.__dict__['__len__']()
 
# Example use
def Stack():
    items = []
    def push(item):
        items.append(item)
    def pop():
        return items.pop()
    def __len__():
        return len(items)
return ClosureInstance()

-用闭包函数的外层函数,记住内部函数的变量,避免被回收.添加将定义的方法添加为属性.用来在全局中使用.查看或者改变闭包内的变量.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,511评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,011评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,489评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,872评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,741评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,525评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,914评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,542评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,833评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,858评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,674评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,479评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,923评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,099评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,400评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,923评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,141评论 2 339

推荐阅读更多精彩内容