理解Python的函数

Python的函数就概念上来说,和其他编程语言如C/C++,JAVA并没有什么区别,都是为了减少代码冗余,最大化代码重用以及进行结构化的程序设计,但在实现以及使用上Python要比静态编译型语言灵活的多,下面逐渐介绍Python的函数以及Python特有的属性。

(例子代码基于Python3.0,在Python 3.3下测试通过)

一、函数定义分析

def funcname(arg1,arg2):
     <statements>
     return result

1. def是可执行语句

执行后会创建一个变量名(函数名),创建函数对象(实际的执行代码对象),以及在他们之间建立引用链接,让函数名执行该函数对象,这和普通的赋值语句比如x=5并没有什么区别,因此,函数定义是动态执行的,不可在def语句前先行引用函数,但可以把函数定义放在函数内甚至条件判断if/else语句中。
>>> someCondition = False
>>> if someCondition:
     def func():
          print('OK')
else:
     def func():
          print('Not Support')

          
>>> func()
Not Support

是不是很像C语言的宏编译选项呢,只不过在C中是静态的,而Python是动态的。

2. 函数名也仅仅是指向函数对象的一个变量

函数名就类似于C的函数指针,所以我们完全可以动态进行赋值,哪怕是删除。
>>> f = add #创建变量f指向变量add指向的函数对象
>>> f(3, 5)
8
>>> del add 
#删除最初的add对象,所以以下add(3, 5)调用会出错,但仍可以通过f进行调用,因为f还在引用函数对象,真正的函数对象不会被删除
>>> add(3, 5)

3. 函数的参数是多态的

函数的参数及返回值没有类型约束,这也符合面向对象中的“针对接口编程,而不是特定对象”
#定义一个函数add用于a,b两个对象相加,因为a,b没有类型约束,所以只要a,b支持+运算就可运行
>>> def add(a, b)
return a + b

>>> add(1, 2) #对于数字对象,是值相加
3

>>> add('hello ', 'python') #对于字符串对象,是字符串相连接
'hello python'

>>> add([1, 2], ['a', 'b']) #对于list对象,是list对象是集合合并
[1, 2, 'a', 'b']

 4.函数返回值也是泛类型的

使用元组对象可以模拟返回多个返回值
>>> def func(a, b):
return a+b, a-b

>>> m, n = func(10, 5)
>>> m, n
(15, 5)

5.函数是对象,可以有额外的属性甚至函数

>>> def func():
print('func...')
 
>>> func.val = 100
>>> def subfunc():
print('sub func')
>>> func.subf = subfunc

>>> f = func
>>> f.val #属性是属于函数对象而不是变量名
100
>>> func.subf()
sub func
是不是和类对象没啥区别,虽然实际中很少这样使用,但Python确实是支持,这也从另一方面也印证了Python里的一切都是对象。

二、函数的作用域LEGB法则

  1. 函数引用变量的LEGB法则

当函数引用一变量时,Python会依次搜索4个作用域来搜索该变量,在第一处找到该变量名处返回,如找不到返回错误。

1)本地作用域(L)

类似于C的局部变量,这个很好理解
>>> X = 100
>>> def func():
x = 'in func'
print(x) #因为首先搜索本地作用域,所以此处引用的是函数func定义的x

当同一个函数发生嵌套调用时,每次函数调用的局部作用域也不相同
>>> def func(x):
     print('x = ', x, '; id = ', id(x)) #id可获取变量的ID,类似于C的变量的内存地址
     if x <= 0:
          return
     func(x//10)

    
>>> func(123)
x =  123 ; id =  505912816 #由id可以看出,每次函数调用的局部变量x都是不同的。
x =  12 ; id =  505911040
x =  1 ; id =  505910864
x =  0 ; id =  505910848
>>> X = 100

2)嵌套作用域(E)

当在函数内部定义函数时,Python会逐级搜索函数的外层函数定义,例如:
>>>X = 100
>>> def func():
X = 10
def sub(a):
        print(X * a) #此处X为外层函数func内定义的X=10
sub(5)
    
>>> func()
50

3)全局作用域(G)

以上例子把func中变量X的赋值注释掉,sub引用的即为全局作用域中X的定义
>>> X = 100
>>> def func():
#X = 10
def sub(a):
        print(X * a)
sub(5)
    
>>> func()
500

4)Python内置作用域(B)

内置做用户仅仅是一个名为__buildin__的内置标准模块,根据LEGB作用域法则,Python最后才搜索该模块。
所以如果我们定义了和内置模块重名的变量名或函数名,就会覆盖内置模块的同名变量/函数
>>> s = 'hello python'
>>> len(s)
12
>>> def len(obj):
print('in my len()...')
    
>>> len(s)
in my len()...

2. 当在函数中给一个变量名赋值时,Python总是创建或改变本地作用域的变量名

除非它已经在函数中声明为global,nonlocal
>>> X = 100
>>> def func1():
X = 'hello'
print('In func1(), X = ', X) #X为局部变量'hello'
    
>>> func1()
In func1(), X =  hello
>>> X
100

>>> def func2():
global X
X = 'hello'
print('In func2(), X = ', X) #X为模块全局变量100
    
>>> func2()
In func2(), X =  hello
>>> X
'hello'

global与nonlocal
global使得变量的作用域从嵌套的模块的作用域开始查找,函数内部可以修改全局作用域变量;nonlocal限制作用域只查找外层的def定义的函数,不包括局部作用域也不包括模块的全局作用域。
>>> def func():
count = 0
def sub():
nonlocal count
count += 1 #修改外层函数的局部变量count
print('in sub(), count = ', count)
return sub

>>> f = func()
>>> f()
in sub(), count =  1
>>> f()
in sub(), count =  2

三、函数参数传递方式

参数的传递是通过自动将对象(实参)赋值给函数本地做变量名(形参)来实现的,函数内部对参数重新赋值时不会影响调用者的。

1.函数是通过变量名之间进行赋值调用的

但函数调用传递的参数(实参)和函数的接受参数(形参)其实都指向同一个对象。
>>> x = 5
>>> y = [1, 2]
>>> print('x=', x, '; id(x)=', id(x))  #id取得对象x的id,类似于内存地址
x= 5 ; id(x)= 505910928
>>> print('y=', y, '; id(y)=', id(y))
y= [1, 2] ; id(y)= 40115096

>>> def func(obj):
print('obj=', obj, '; id(ojb)=', id(obj))

>>> func(x)
obj= 5 ; id(ojb)= 505910928
>>> func(y)
obj= [1, 2] ; id(ojb)= 40115096

可以看出调用func时,参数obj指向的对象和x,y相同

2.如函数内部对参数进行重新赋值,不会影响调用者

无论对于不可修改对象(数字,字符串,元组等)还是原处可修改对象(列表,字典等)都不会影响调用者,因为Python对变量进行赋值元素按会创建一新的局部变量。
>>> def func2(x, y):
x = 10
y = ['a', 'b']
print('x=', x, '; id(x)=', id(x))
print('y=', y, '; id(y)=', id(y))
     
>>> func2(x, y)
id: 505911008  =  10
id: 40113616  =  ['a', 'b']

可以看出x,y均指向了不同的对象

3.对于列表,字典等可修改对象,原处修改会影响调用者

对于不可修改对象,无法直接修改函数参数指向的对象,任何修改都会创建新的对象,对于列表,字典等可修改对象,原处修改会影响调用者
>>> def func3(obj):
obj.append('a')
print('obj=', obj, '; id(ojb)=', id(obj))
    
>>> func3(y)
obj= [1, 2, 'a'] ; id(ojb)= 40115096
>>> y
[1, 2, 'a']

可以看出y受到了影响,如不想函数调用修改对象y,可明确传递y的一个拷贝func3(y[:])或将y转变为元组进行调用func3(tuple(y))

由此可以看出,Python相比C/C++等,有以下2个特点:
  1. 函数定义是动态的,是需要执行才能创造一个函数对象
  2. 一切都是对象






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值