python函数补充

一 作用域

作用域介绍 

python中的作用域分4种情况:
L:local,局部作用域,即函数中定义的变量;
E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局          部作用域,但不是全局的;
G:globa,全局变量,就是模块级别定义的变量;
B:built-in,系统固定模块里面的变量,比如int, bytearray等。 搜索变量的优          先级顺序依次是:作用域局部>外层作用域>当前模块中的全局>python内置        作用域,也就是LEGB。

x = int(2.9)  
#
int built-in
g_count = 0  
# global
def outer():
    o_count = 1  
# enclosing
    def inner():
        i_count = 2  
# local
        print(o_count)
    
#
print(i_count) 找不到
    inner()
outer()

#
print(o_count)
#找不到

当然,local和enclosing是相对的,enclosing变量相对上层来说也是local。
 

作用域产生 
在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如if、try、for等)是不会引入新的作用域的,如下代码:

if 2>1:
    x = 1
print(x)  
# 1

 

这个是没有问题的,if并没有引入一个新的作用域,x仍处在当前作用域中,后
面代码可以使用。

def test():
    x = 2
print(x) 
# NameError: name 'x2' is not defined

def、class、lambda是可以引入新作用域的。
 

变量的修改 

x=6
def f2():
    print(x)
    x=5
f2()
  

# 错误的原因在于print(x)时,解释器会在局部作用域找,会找到x=5(函数已经加载到内存),但x使用在声明前了,所以报错:

# local variable 'x' referenced before assignment.如何证明找到了x=5呢?简单:注释掉x=5,x=6

# 报错为:name 'x' is not defined
#同理
x=6
def f2():
    x+=1 #local variable 'x' referenced before assignment.
f2()

 

global关键字 

当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了,当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下,代码如下:

count = 10
def outer():
    global count
    print(count)
    count = 100
    print(count)
outer()
#10
#100

 

nonlocal关键字 

global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量怎么办呢,这时就需要nonlocal关键字了

def outer():
    count = 10
    def inner():
        nonlocal count
        count = 20
        print(count)
    inner()
    print(count)
outer()
#20
#20

 

小结 

(1)变量查找顺序:LEGB,作用域局部>外层作用域>当前模块中的全局                 >python内置作用域;
(2)只有模块、类、及函数才能引入新作用域;
(3)对于一个变量,内部作用域先声明就会覆盖外部变量,不声明直接使用,           就会使用外部作用域的变量;
(4)内部作用域要修改外部作用域变量的值时,全局变量要使用global关键               字,嵌套作用域变量要使用nonlocal关键字。nonlocal是python3新增的关           键字,有了这个 关键字,就能完美的实现闭包了。
 

二 递归函数

定义:在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
实例1(阶乘)

def factorial(n):
    result=n
    for i in range(1,n):
        result*=i
    return result
print(factorial(4))

#**********递归*********
def factorial_new(n):
if n==1:
return 1
return n*factorial_new(n-1)
print(factorial_new(3))

 
实例2(斐波那契数列)

def fibo(n):
    before=0
    after=1
    for i in range(n-1):
        ret=before+after
        before=after
        after=ret
    return ret
print(fibo(3))

#**************递归*********************
def fibo_new(n):#n可以为零,数列有[0]
if n <= 1:
return n
return(fibo_new(n-1) + fibo_new(n-2))
print(fibo_new(3))
print(fibo_new(30000))#maximum recursion depth exceeded in comparison

 

递归函数的优点:    是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
递归特性:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈    (stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈           帧,每当函数返     回,栈就会减一层栈帧。由于栈的大小不是无限的,所         以,递归调用的次数过多,会导致栈溢出。)
 
三 内置函数(Py3.6)
python函数补充
重要的内置函数:
filter(function, sequence)

str = ['a', 'b','c', 'd']
def fun1(s):
    if s != 'a':
        return s
ret = filter(fun1, str)
print(list(ret))
# ret是一个迭代器对象

对sequence中的item依次执行function(item),将执行结果为True的item做成一个filter object的迭代器返回。可以看作是过滤函数。
 

map(function, sequence) 

str = [1, 2,'a', 'b']
def fun2(s):
    return s + "alvin"
ret = map(fun2, str)
print(ret)      
#  map object的迭代器
print(list(ret))
#  ['aalvin', 'balvin', 'calvin', 'dalvin']

对sequence中的item依次执行function(item),将执行结果组成一个map object迭代器返回.
map也支持多个sequence,这就要求function也支持相应数量的参数输入:

ef add(x,y):
    return x+y
print (list(map(add, range(10), range(10))))##[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

 

reduce(function, sequence, starting_value)

from functools import reduce
def add1(x,y):
    return x + y
print (reduce(add1, range(1, 101)))#
# 4950 (注:1+2+...+99)
print (reduce(add1, range(1, 101), 20))#
# 4970 (注:1+2+...+99+20)

对sequence中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用.
 

lambda
普通函数与匿名函数的对比:

#普通函数
def add(a,b):
    return a + b
print add(2,3)

#匿名函数
add = lambda a,b : a + b
print add(2,3)
#========输出===========
5
5

匿名函数的命名规则,用lamdba 关键字标识,冒号(:)左侧表示函数接收的参数(a,b) ,冒号(:)右侧表示函数的返回值(a+b)。

因为lamdba在创建时不需要命名,所以,叫匿名函数

四 函数式编程 

学会了上面几个重要的函数后,我们就可以来聊一聊函数式编程到底是个什么鬼

概念(函数式编程)

函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,常见的面向对象编程是也是一种命令式编程。
命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列。
而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式。函数式编程关心数据的映射,命令式编程关心解决问题的步骤,这也是为什么“函数式编程”叫做“函数式编程”。

实例

#计算数组中正整数的平均值

number =[2, –5, 9, –7, 2, 5, 4, –1, 0, –3, 8]
count = 0
sum = 0
for i in range(len(number)):
if number[i]>0:
count += 1
sum += number[i]
print sum,count
if count>0:
average = sum/count
print average
#========输出===========
30 6
5

 

首先循环列表中的值,累计次数,并对大于0的数进行累加,最后求取平均值。 这就是命令式编程——你要做什么事情,你得把达到目的的步骤详细的描述出来,然后交给机器去运行。
这也正是命令式编程的理论模型——图灵机的特点。一条写满数据的纸带,一条根据纸带内容运动的机器,机器每动一步都需要纸带上写着如何达到。
那么,不用这种方式如何做到呢?

number =[2, -5, 9, -7, 2, 5, 4, -1, 0, -3, 8]
positive = filter(lambda x: x>0, number)
average = reduce(lambda x,y: x+y, positive)/len(positive)
print average
#========输出===========
5

 

这段代码最终达到的目的同样是求取正数平均值,但是它得到结果的方式和 之前有着本质的差别:通过描述一个列表->正数平均值 的映射,而不是描述“从列表得到正数平均值应该怎样做”来达到目的。
 
函数式编程有什么好处呢?
1)代码简洁,易懂。
2)无副作用
由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是引用透明(Referential transparency)的和没有副作用(No Side Effect)。
 


发表评论