Python二次元世界-Lisp的帝国斜阳 lambda与closure

Python二次元世界-函数式编程

Function , lambdaclosure

 

本章讲述Python语言自Lisp语言演变而来的一些高级函数编程技巧 如闭包(closure)

匿名函数(lambda 生成器(yield) 嵌套作用域(nested scope)

好的
Python程序员必须熟练掌握这几种特性 能快速地将代码移植到类Lisp语言 scheme Emacs-Lisp JavaScript Java8

特别提示∶在您浏览本教程时,不要强行记忆。记住一点∶在使用中学习。

1.         Function

Python的函数的本质是对象方法和模块属性
声明/定义一个函数 def foo(): print “bar”
函数作用于对象 object/reference foo
函数的调用 foo() or obj.foo()
如果没有return语句 Python函数默认返回值是None

>>> def hello():

... print 'hello world'

>>> 

>>> res = hello()

hello world

>>> res

>>> print res

None

>>> type(res)

<type 'None'>

不同其他编程语言 Python不可以拥有多个返回值 但是你可以返回一个类型为list的对象单元或是元组 等同于一次返回多个值 典型的有 os.walk() return (root,path,???)

def foo():

return ['xyz', 1000000, -98.6]

def bar():

return 'abc', [42, 'python'], "Guido"


为保证返回值的不可变 可以使用元组 os.walk()

def foo():

return 'xyz', 1000000, -98.6

>>> rs = (x,y,z) = foo()

>>> rs

('xyz', 1000000, -98.6)

>>> x

‘xyz’

>>> x = 1

>>> x

1

>>> rs = 1

TypeError: 'tuple' object does not support item assignment

#元组不可以被修改

 

参数列表():

func(*lt, **kw)

带有一个*号的参数是一个元组tuple,两个星号为字典dict

任何带有关键字参数的函数 都可以接受不固定的参数列表 A=’B’的赋值被保存到**kw关键字典 使用kw[‘xxx’]进行引用 直接使用‘A’参数的赋值被保存到 *lt 关键字列表 引用方式一样

#########领略函数式编程的乐趣##########

第一课 example-1 makeFun.py

#!c:/python27/python.exe

#coding: utf-8

 

__version__=0.1

__author__ ="[email protected]"

 

import random as ri

 

def guessguess():

    '''猜一个100以内的随机整数'''

    print '欢迎来到猜数字 输入1-100以内的数字 [quit]退出'

    guess   = 0

    num     = ri.randint(1,100)

    while guess != num:

        guess = input("请输入一个数字: ")

 

        if guess > num:

            print 'too high'

        elif guess < num:

            print 'too low'

        else :

            print '''Binggo! the number is %d''' % num

    return 1

 

def gamble():

    print '''*********欢迎来到赌大小*********

输入[1]代表大 [0]代表小 [quit]退出\n'''

   

    yourchoice = ''

    done    = False

    while not done:

        dice = lambda : ri.randint(1,6)

        x,y,z = dice(),dice(),dice()

        yourchoice = input('买入大小盘[1/0]?')

        if yourchoice in [0,1]:

            rs = 1 and (x+y+z)>=9 or 0

            print '''Binggo! 骰子A[%d],骰子B[%d],骰子C[%d],买盘结果%d

注意读作(tou)骰子 ''' % (x,y,z,x+y+z)

            tmp = "大" if yourchoice else "小"

            print '您买的是[',tmp,']'

            if yourchoice == rs:

                print '赢!'

            else :

                print '输!'

        goahead = raw_input( '是否继续? [y]')

        done = False if goahead == 'y' else True

    else:

        print "欢迎再来...\n退出游戏"

        return 1

 

def bada_quit(direcs):

    for i in answers[3:]:

        if direcs in i :

            return True

        else :

            continue

 

answers = [1,2,3,'quit','exit']

 

def main():

    choice  = ''

 

    while choice not in answers :

        choice = raw_input("""

欢迎来到作死v1.0,你可以选择

1.锻炼智力

2.碰碰运气

3.退出. [Q/quit]

""")

        if choice == str(answers[0]): #Python没有switch

            guessguess()

        elif choice == str(answers[1]):

            gamble()

        elif choice == str(answers[2]):

            print '退出...'

            break

        elif bada_quit(choice):

            print '退出...'

            break

        else :

            print "输出12进入游戏,输入quit/Q退出..."

            continue

 

if __name__=='__main__':

    main()

 


欢迎来到作死v1.0,你可以选择
1.作死
2.继续作死
3.退出. [Q/quit]
2
*********欢迎来到赌大小*********
输入[1]代表大 [0]代表小 [quit]退出

买入大小盘[1/0]?0
Binggo! 骰子A[4],骰子B[4],骰子C[3],买盘结果11
注意读作(tou)骰子
您买的是[ 小 ]
输!
是否继续? [y]y
买入大小盘[1/0]?1
Binggo! 骰子A[4],骰子B[6],骰子C[6],买盘结果16
注意读作(tou)骰子
您买的是[ 大 ]
赢!
是否继续? [y]quit
欢迎再来...
退出游戏

欢迎来到作死v1.0,你可以选择
1.作死


根据二分查找策略 最多需要lg(n)+1次机会可获得答案数字 猜数字游戏 在后面可以进行升级 限制玩家的答题次数

 

写完程序makeFun.py函数式编程的入门差不多结束了 开始下一个话题之前 请准备铁观音或者咖啡3

 

2.         Lambda

 

Python 保留了Haskell语言的匿名函数功能 使用lambda关键字定义匿名功能块 区别于def关键字 lambda中的所有指针全部失效 python解释器将不再创建属于该函数的stack区间保留临时变量 它完全就是一个表达式 类似于C语言中的宏 . 但可以外部引用lambda指针 可以接收参数 也可以递归调用

>>> true   = lambda : True

>>> false = lambda : False

#改写关键字TrueC Java程序员更习惯的 true

平方根函数

>>> sqrt = lambda x : x**0.5

>>> sqrt(3)

1.7320508075688772

次方函数

>>> pow = lambda x,y=2:x**y

>>> pow(3)

9

>>> pow(3,5)

243

lambda一般都是极客使用 为了写出积极复杂但精简的代码 lambda通常同一些回调函数一起使用 主要有 apply reduce map filter

 

结合列表解析与回调函数 代码将变得很复杂

Lambda-用例1.1 筛偶数

[x for x in range(100) if x%2]

[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41,

43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81,

83, 85, 87, 89, 91, 93, 95, 97, 99]

来看刚才的例子

Lambda-用例1.2 切偶数

map(lambda x:x if x%2 else None,[x for x in range(100)])[1::2]


[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41,

43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81,

83, 85, 87, 89, 91, 93, 95, 97, 99]

map, filter, apply, reduce 类似PHP中的array walk() 接收一个函数指针用于数组的回调 返回本身或者None(直接作用于数组) apply filter 已经在2.4版本中被废弃 建议使用map reduce完成同样的功能 python三元操作符 x = sth if a else b 或者 x = sth and a or b


一些更有趣的例子 

Lambda 用例2 阶乘

 

#!/usr/bin/env python #-*- encoding: utf-8 -*-

__author__='vikid'

__version__=1.0

 
#C新人

def factorial_v1(n=5):

    if n == 0:

        return 1

    else:

        return factorial_v1(n-1)*n


#菜鸟

def factorial_v2(n=5,rs=1):

    for i in xrange(2,n+1):

        rs *= i

    return rs


#大虾

def factorial_v3(n=5):

    return n and factorial_v2(n-1)*n or 1

 

#极客

def factorial_v4(n=5):

    f = lambda n: reduce(lambda a,b:a*b ,xrange(2,n+1))

    return f(n)

   

#测试

from random import choice

funcs,op = {

    "1":factorial_v1,

    "2":factorial_v2,

    "3":factorial_v3,

    "4":factorial_v4

},"1234"

 

for i in range(2,51):

    which       =       choice(op)

    rs          =       str(funcs[which](i))

    showrs      =       rs.rjust(60) if i <=25 else rs.ljust(30)

    print """v%s[%s] = %s """ % (which,str(i).center(4),showrs)  

 

v2[ 2 ] = 2 v3[ 3 ] = 6 v2[ 4 ] = 24 v4[ 5 ] = 120 v4[ 6 ] = 720 v4[ 7 ] = 5040 v4[ 8 ] = 40320 v2[ 9 ] = 362880 v2[ 10 ] = 3628800 v3[ 11 ] = 39916800 v2[ 12 ] = 479001600 v2[ 13 ] = 6227020800 v4[ 14 ] = 87178291200 v4[ 15 ] = 1307674368000 v3[ 16 ] = 20922789888000 v4[ 17 ] = 355687428096000 v3[ 18 ] = 6402373705728000 v1[ 19 ] = 121645100408832000 v3[ 20 ] = 2432902008176640000 v4[ 21 ] = 51090942171709440000 v2[ 22 ] = 1124000727777607680000 v4[ 23 ] = 25852016738884976640000 v2[ 24 ] = 620448401733239439360000 v3[ 25 ] = 15511210043330985984000000 v2[ 26 ] = 403291461126605635584000000 v1[ 27 ] = 10888869450418352160768000000 v1[ 28 ] = 304888344611713860501504000000 v1[ 29 ] = 8841761993739701954543616000000 v2[ 30 ] = 265252859812191058636308480000000 v2[ 31 ] = 8222838654177922817725562880000000 v2[ 32 ] = 263130836933693530167218012160000000 v4[ 33 ] = 8683317618811886495518194401280000000 v1[ 34 ] = 295232799039604140847618609643520000000 v2[ 35 ] = 10333147966386144929666651337523200000000 v2[ 36 ] = 371993326789901217467999448150835200000000 v3[ 37 ] = 13763753091226345046315979581580902400000000 v4[ 38 ] = 523022617466601111760007224100074291200000000 v1[ 39 ] = 20397882081197443358640281739902897356800000000 v4[ 40 ] = 815915283247897734345611269596115894272000000000 v1[ 41 ] = 33452526613163807108170062053440751665152000000000 v2[ 42 ] = 1405006117752879898543142606244511569936384000000000 v3[ 43 ] = 60415263063373835637355132068513997507264512000000000 v2[ 44 ] = 2658271574788448768043625811014615890319638528000000000 v4[ 45 ] = 119622220865480194561963161495657715064383733760000000000 v2[ 46 ] = 5502622159812088949850305428800254892961651752960000000000 v3[ 47 ] = 258623241511168180642964355153611979969197632389120000000000 v3[ 48 ] = 12413915592536072670862289047373375038521486354677760000000000 v1[ 49 ] = 608281864034267560872252163321295376887552831379210240000000000 v2[ 50 ] = 30414093201713378043612608166064768844377641568960512000000000000


lambda代码绝大多数人看不懂 甚至有时自己也看不懂 但这正是lambda的意义 人人都有极客的潜质 当你还是一个菜鸟的时候根本无法写出火星人代码 但是当你习惯了这样的书写方式之后 你会发现全世界90%的代码都丑陋至极 尤其作为一个Python程序员 牢记 有且只有一种最简单的办法 虽然有时候你得为这种简单牺牲一些性能作为代价

 

>>> import this

The Zen of Python, by Tim Peters

 

There should be one-- and preferably only one --obvious way to do it.

Beautiful is better than ugly –long live the Lambda!

>>> 

 

reduce函数是python2.4之后的一个里程碑函数 它取代了丑陋和不必要的apply filter 甚至是map 它的工作方式是这样的:

reduce(1..50)->re((((((12)3)4)5)6)..49)

一次接受2个参数进行运算 用一个占位符保存中间结果 直到表达式迭代完毕 返回占位符中的保存值 


值得一提的是 根据我的经验 判断一个python程序员的水平最直观的方式就是看他/她能不能使用回调函数与嵌套作用域来书写lambda代码:

Lambda-用例3.1 斐波那契数列

return map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1: f(x,f),range(100))

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

 

必须指出的是 Python3000之后 不再鼓励使用回调函数 事实上 即使不使用reduce map filter 一样能写出精简的lambda表达式

解决办法就是: 列表解析 […]

Lambda-用例3.1素数

primes = lambda n:[x for x in range(1,n) if not [y for y in range(2,int(x**0.5 +1)) if x % y == 0]]

print primes(100)

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]


列表解析 if 可以不添加: 切通常位于表达式后半部

让我们来为lambda添加注释

Lambda-用例3.2素数

# [y for y ..]之后的列表解析 用于计算当前数n 的所有分解因子 例如 9的分解

# 因子为[3] 3*3=9,10的分解因子为[2,5] 2*5=105*2=10

# 有了新的素数算法 -- 没有分解因子的数n,即只能被1和他自身分解的数 ->素数

# Pseudo code

# [x for x in range(n) #is Prime~# if [#分解因子切片#]==null ]

Primes = lambda n:[x for x in range(1,n) if not [y for y in \
range (2,int(x**0.5 +1)) if x % y == 0]]

当然了 楼上的代码其实还可以写的更复杂 但我觉得已经没有现实意义了 纯属火星人

primes = lambda n:[x for x in range(1,n) if not \
[y for y in range(2,map(lambda z: int(z**0.5)+1,[x])[0]) if x % y == 0]]

print primes(20)


3.         *Lambda尾声 PFA 截断函数

 

Python2.5以后反而引入了一个相当有趣 晦涩的特性 functool 模块

该模块下面有一个方法特别有意思 引入了PFA的概念 我不知道如何翻译 姑且叫做截断函数

打开python doc->Library Refercens->9.8 functools.partial

官方有一个很实用的列子  

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18


我们拓展一下例子 制作一个函数 用来计算16进制数的整数值

只需要改一行代码

>>> from functools import partial
>>> hex2human = partial(int, base=16)
>>> basetwo('0x168')
360



Partial函数接收一个函数指针 2个关键字参数 然后传递这个函数的调用 这个特性被广泛使用与Python OOP特性 操作符重载 和方法重载 

<Python核心编程>一书中有一个关于partial方法非常经典的用例

Lambda-用例4.1使用截断函数进行方法重载

#!/usr/bin/env python
#-*- encoding: utf-8 -*-

from functools import partial
import Tkinter
root = Tkinter.Tk()
MyButton = partial(Tkinter.Button, root,fg='white', bg='blue')
# 关键字参数被自动复制到MyButton构造方法中 每一个实例不需要复制编码参数
b1 = MyButton(text='更加优雅的写法')
b2 = MyButton(text='两个按钮具有相同的构造参数')
qb = MyButton(text='退出', bg='red',command=root.quit)
b1.pack()
b2.pack()
qb.pack(fill=Tkinter.X, expand=True)
root.title('PFAs!')
root.mainloop()


Python二次元世界-Lisp的帝国斜阳 lambda与closure

4.         Closure

 

自版本2.1之后 Python全面支持Lisp的闭包特性 全局作用于 需要在单独的行内声明 任何时候不要使用2.1之前的python解释器 可能会产生兼容问题

x = “nesting world”
def foo():
      y = “\b\n”
      Global x
      x = “hello world”
      print x+y
foo()
#Hello world

 

习惯写 schemeHaskell程序的人 往往会对python中的闭包(closure)特性感到十分亲切 传统的函数式编程没有面向对象的诸多功能  比如类与封装 继承与多重继承  但是有了closure 即使不使用class 关键字 也可以模拟面向对象的功能

这十分狂妄地彰显了Lisp语言的极客精神

更现实的意义在于 GUI程序设计和DB事务设计中 闭包被广泛使用 来限制变量的越权读写 这得益于closure对作用域的限制

 

Closure -用例1.1使用closure模拟面向对象

#/usr/bin/env python

#-*- coding: utf-8 -*-

import sys

__author__='vikid'

__version__=1.0

__doc__= '''Simple tool for making class-like objects using nested scopes and closures in Python 2.x'''

 

 

class Instance:

   pass

def classify(local_dict=None):

    o = Instance()

    if local_dict is None:

        local_dict = sys._getframe(1).f_locals

    vars(o).update(local_dict)

    return o

 

#########################

###  Simple example  ####

#########################

def Animal(name):

    'Animal-like class'

    def speak():

        print('I am', name)

    def set_name(newname):

        global name

        name = newname

    def __getitem__(key):

        return '%s is %sing' % (name, key.title())

    return classify()

d = Animal('哈士奇')

print(d.name)

d.speak()

d.set_name('小马哥')

d.speak()

print(d['fetch'])

 

Closure -用例1.2 [2.py]使用closure模拟面向对象

def person(name="peter"):

    name = name

    def say_hello():

        return "my name is " + name

    return say_hello()


print person()

print person("Lita")

>>>2.py

My name is peter

My name is Lita

 

关于作用域最简单的一句话总结就是 闭包函数可以自由操作上层函数的变量 最高到达全局global lambda闭包同函数 反过来 上层函数不能修改闭包函数值 因为闭包是在上层函数创建之后创建的 当遇到内外冲突的变量 closure将忽略上层变量保留内部变量 这一个特性非常重要 很多Python JavaScript面试题考究这点

 

Closure -用例1.3 closure结合包装器实现函数测试(from core python) 思考 函数1为何明显快于23

#filename: log4py.py 

#!c:/python272/python.exe
#-*- encoding: utf-8 -*-

'''
        usage:

         \

         |--   from log4py import logtest

         |--   @logtest()

         |--   def foo(): #do sthing

         |--   foo() #auto make it

'''

from time import time

import sys

 

#-------------------------闭包函数------------------------------

def logtest():

    "测试函数的执行用时 打印参数表"

    def show_args(f,*args,**kargs):

        print '''

funcs name  : %s

params      : %r

key params  : %r

..          : %s''' % (f.__name__,args,kargs,'###'*10)

 

    def logfuncs(f):

        def closure_log(*args,**kargs):

            now = time()

            try:

                return f(*args,**kargs)

            finally :

                show_args(f,*args,**kargs)

                print "Call timedelta: %s" % (time() - now)

        return closure_log

    try:   

        return logfuncs

#return {”foo”:foo,”bar”:bar}[para_father]

    except KeyError ,e :

        raise ValueError(e) ,'None'

 

----------------------------------------------------------------------------

#filename: log4py.py

 

#!/usr/bin/env python

#-*- encoding: utf-8 -*-

 

"""

输入30以内的斐波那契数字的不同算法以及测试代码

1,$s/22/30:w

"""

from log4py import logtest as log

 

@log()

def fibonacci_lambda(n):

    return map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1: f(x,f),range(n))

 

@log()

def fibonacci_recursion(n):

    def closures_fibonacci_recursion(p):

        """closure function 2 calc current fibo num"""

        return int(1 and p in [0,1] or closures_fibonacci_recursion(p-1) + closures_fibonacci_recursion(p-2))

    d = []

    for i in xrange(n):

        d.append(closures_fibonacci_recursion(i))

    return d

 

@log()

def fibonacci_pythonic(n):

    '''

Lift is short and i pythonic!

'''

    a,b,list = 0,1,[]

    for i in xrange(n):

        #get current fibonacci number and push it into list[]

        list.append(b)

        a , b = b , a + b

    return list

 

print fibonacci_pythonic(30)

print fibonacci_lambda(30)

print fibonacci_recursion(30)


funcs name  : fibonacci_pythonic
params      : (30,)
key params  : {}
..          : ##############################
Call timedelta: 0.00799989700317
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181
, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 83204
0]

funcs name  : fibonacci_lambda
params      : (30,)
key params  : {}
..          : ##############################
Call timedelta: 2.08600020409
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181
, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 83204
0]

funcs name  : fibonacci_recursion
params      : (30,)
key params  : {}
..          : ##############################
Call timedelta: 5.23500013351
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181
, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 83204
0]


包装器是2.5以后的属性 用于处理静态类编写工作 logtest() 本身不传递被包装的函数指针fibonacci_xxx 而是他的闭包函数的第一个参数f接收函数指针 注意是一个指针而不是字符串(不同于PHP) 闭包内调用return f()来执行函数 #return {”foo”:foo,”bar”:bar}[para_father]你可以使用这样的方式来指定返回哪一个闭包函数 尽管他们只有细微的差别 例如是否打印执行时间

 

希望下个月黄金能跌破1500 ~

你可能感兴趣的:(python,素数,阶乘,闭包,lambda,调试,列表解析,作用域嵌套,斐波那契数)