2017/10/14
函數的傳參,嵌套,作用域變量
1.可變/不可變類型 的實參的參數傳遞的差別
可變:
清單list,集合set,字典dict
不可變:
frozenset,tuple(隻可以進行索引查找,不可以進行索引指派),str,float,int...
思考:函數隻能通過傳回值傳回資料嗎?
執行個體1(可變類型資料):
def fn(x):
x.append(10)
l = []
fn(l)
print(l)
>>>[10]
#改變了 l 的清單,但實際上沒有return語句,def内部也沒有print語句!!l是如何在定義函數fn外拿到資料的呢?
解答:清單,集合,字典是可變的資料類型,可以直接從定義函數中傳回已經變化的語句,而不需要進行傳回return處理
執行個體2(可變類型資料):
def fn(x):
x['name'] = 'tarena'
d = {}
fn(d)
print(d)
>>>{'name': 'tarena'}
#原理同上
執行個體3(不可變類型資料):
def fn(x):
x[1] = 2.2
t = (1,2,3)
print(t)
fn(t) #元組内部資料不可變,不可以進行索引指派,隻可以進行索引查找
print(t)
>>>TypeError: 'tuple' object does not support item assignment
執行個體4:
def fn(x):
x=(4,5,6)
t = (1,2,3)
fn(t)
print(t)
>>>(1, 2, 3) #元組本身資料就更改不了,元組可查不可變
執行個體5:
def fn(x): #剛開始,x的确綁定的是清單[1,2,3]
x = [4,5,6] #到這裡,x改變了綁定關系,這句話的意思的實質是建立清單[4,5,6]并進行綁定,而不是對原來的裂變進行變值處理
l = [1,2,3]
fn(l)
print(l)
>>>[1, 2, 3] #調用fn時,系統建立一個單獨的空間
執行個體6:
def fn(x):
x.append(4) #這裡的x依舊綁定的是清單[1,2,3],是以可以綁定原來的清單值
l = [1,2,3]
fn(l)
print(l)
>>>[1, 2, 3, 4]
執行個體7:
def fn(x):
x = [1,2,3] #這裡x建立并綁定的清單[1,2,3]與l建立并綁定的清單[1,2,3]是兩個完全無關的清單,分别位于兩個不同的記憶體空間
x.append(4) #在x綁定的清單中改變資料,不會影響l綁定的清單資料
l = [1,2,3]
fn(l)
print(l)
>>>[1, 2, 3]
總結:
對于以上定義函數,如果了解為将l替換x是錯誤的(記得新常提的綁定關系,而不是指派關系),實際上x 和 l綁定的是某個空間中的某個清單,可能是同空間的同一個清單,也可能是不同空間的清單,資料修改互不相關
差別:
不可變類型的資料作為函數參數傳入時,函數内部不會改變變量的原資料值,是安全的;
可變資料類型的資料作為參數傳入時,函數内部可以改變原資料,多用來傳回更多資料。
2.函數嵌套:
一個函數語句裡用def語句來建立其他函數的情況
函數變量:
函數名是變量,它在建立函數時綁定一個函數
執行個體:
def fn():
print('hello xiao')
f1 = fn
f1() #等同于調用函數fn()
執行個體8-函數嵌套:
def fn_outer(): #外部函數
print('外部函數被調用')
def fn_inner():
print('fn_inner被調用')
fn_inner()
fn_inner()
print('外部函數調用結束')
fn_outer()
>>>
外部函數被調用
fn_inner被調用
fn_inner被調用
外部函數調用結束
執行個體9-命名空間的作用:
def fn_outer(): #外部函數
print('外部函數被調用')
def fn_inner():
print('fn_inner被調用')
fn_inner()
fn_inner()
print('外部函數調用結束')
fn_outer()
fn_inner() #錯誤,内嵌函數隻存在于函數内部
>>>NameError: name 'fn_inner' is not defined
3.函數作為函數的傳回值:
執行個體10:
def getfn():
def print_hello():
print('hello')
return print_hello #函數的名稱不加括号,不加括号表示一個變量,不執行語句塊,把整個語句丢過去,但是不執行;如果加括号,表示執行語句塊,調用了函數,并傳回函數執行的結果
fn = getfn() #加括号?????????????
fn()
print(getfn()) #列印函數
print(fn)
4.函數作為函數的參數傳遞:
執行個體11:
def tab(x,y):
return '|' + x.center(13) + '|' + y.center(13)+ '|'
def string(x,y):
return '姓名:' + x + '年齡:' + y
def myprint(fx,x,y):
s = fx(x,y)
print(s)
myprint(tab,'tarena','15') #這裡的tab作為函數進行傳參,先調用myprint函數
myprint(string,'xiaoyu','23')
>>>
| tarena | 15 |
姓名:xiaoyu年齡:23
執行個體12:
def goodbye(L):
for x in L:
print("再見",x)
def hello(L):
for x in L:
print("你好",x)
def operator(fn,L):
fn(L)
operator(goodbye,['xiaoyu','bantian'])
>>>
再見 xiaoyu
再見 bantian
你好 xiaoyu
你好 bantian
5.全局變量和局部變量
全局變量:定義在函數外部,子產品内部的變量
局部變量:定義在函數内部的變量(包括函數參數)
執行個體13:
v = 100 #此處為全局變量,變量全局作用域
def fn():
v = 200 #此處為局部變量,變量林臨時使用,隻有在調用函數的時候才使用此變量
print(v)
fn() #200
print(v) #100
>>>
200
100
6.python作用域
作用域,也叫名字空間,是變量通路的時候查找變量名的範圍空間
執行個體14:
v = 100
def fn():
print(v)
fn()
>>>100
總結:變量查找空間的順序是先局部作用域,後全局作用域
7.python四個作用域:
局部作用域(函數内) Local L
外部嵌套函數作用域 Enclosing function locals E
函數定義在子產品(檔案)的作用域 Global(module) G(全局作用域)
python内置子產品的作用域 Builtin(Python) B
變量名的查找規則:
在通路變量時,先查找本地變量,然後是包裹此函數的外部函數的函數内部的變量,之後是全局變量,最後是内置變量
L -> E -> G -> B
在預設情況下,變量名指派會建立或者修改本地變量
執行個體15:
v = 100 #全局作用域G
def fun1():
v = 200 #外部嵌套作用域E
print('fun1.v=',v)
def fun2():
v = 300 #局部作用域L
print('fun2.v=',v)
fun2()
fun1()
print('v=',v)
>>>
fun1.v= 200
fun2.v= 300
v= 100
曾經出現的bug:
v = 100 #全局作用域G
def fun1():
v = 200 #外部嵌套作用域E
print('fun1.v=',v)
def fun2():
v = 300 #局部作用域L
print('fun2.v=',v)
fun2
fun1
print('v=',v)
>>>v=100
分析:fun2 fun1 應該寫為fun2() ,fun1(),沒有括号僅僅表示函數名變量,綁定了一批代碼,不做任何處理和運作
執行個體16:
v = 100 #全局作用域G
def fun1():
v = 200 #外部嵌套作用域E
print('fun1.v=',v)
def fun2():
#v = 300 #局部作用域L
print('fun2.v=',v)
fun2()
fun1()
print('v=',v)
>>>
fun1.v= 200
fun2.v= 200
v= 100
global語句:
作用:
告訴解釋執行器,global語句聲明的一個或多個變量,這些變量的作用域為子產品級别的作用域,也稱作全局變量
對全局聲明(global)的變量指派,将映射到子產品檔案的内部作用域
文法:
global 變量名1,變量名2,...
執行個體17:
v = 100
def fn():
global v #聲明以下的變量是全局的
v = 200 #建立了全局變量v = 200,解除以往的v綁定的值
fn()
print(v)
執行個體18:
def fn():
global v #聲明以下的變量是全局的
v = 200
fn()
print(v)
global說明:
1.全局變量如果要在函數内部被指派,則必須經過全局聲明,否則被認為是局部變量
2.全局變量在函數内部不經過聲明就可以直接通路(前提是變量已經存在)
3.不能先聲明局部變量,再用global聲明為全局變量,此做法不符合文法規則
4.global變量清單裡的變量不能出現在此作用域的參數清單裡,for循環控制目标,類定義,函數定義及import導入名字中
執行個體19-bug:
v = 100
def fn():
v = 200 #建立了一個局部變量v,在聲明為全局是不可以的
global v #後聲明全局
fn()
print(v)
執行個體20-bug:
def fn(): #定義了一個局部變量v 後又聲明為全局,這是錯誤的
global v
v = 200
fn(111) #原因在此,已經建立了局部變量
print(v)
nonlocal語句:
作用:
1.告訴解釋執行器,nonlocal聲明的變量不是局部變量,也不是全局變量,而是外部嵌套函數内的變量
文法:
nonlocal 變量名1,變量2,.....
執行個體21-bug:
var = 100 #全局
def outter():
var = 200 #外層嵌套
def inner():
var += 1 #此行出錯
print(var)
inner()
print('outter.var',var)
outter()
執行個體22-bug:
var = 100 #全局
def outter():
var = 200
def inner():
nonlocal var #指定var為外層嵌套函數作用域,表示是inner()之外的,而且是嵌套的
var += 1 #此行出錯
print('inner.var=',var)
inner()
print('outter.var=',var)
outter()
執行個體23-bug:
var = 100 #全局
def outter():
var = 200 #外
def inner():
#var += 1 #此行出錯
print(var)
inner()
print('outter.var',var)
outter()
說明:
1.nonlocal語句隻能在被嵌套函數的内部進行使用
2.通路nonlocal變量将對外部嵌套函數的作用域内的變量進行操作
3.當有兩層或兩層以上函數嵌套時,通路nonloal變量隻對最近一層的變量進行操作
4.nonlocal語句的變量清單裡的變量名,不能出現在此作用域的參數清單中
執行個體24-bug:
var = 100
def fn():
nonlocal var #不是外部嵌套函數
var = 200
fn()
執行個體-bug:
var = 100
def fn():
global var #不是外部嵌套函數,層數不對(原因是嵌套層數太少)
var = 200
fn()
執行個體25:
def f1():
v = 200
def f2():
v = 300
def f3():
nonlocal v #對上一層的變量進行重新處理
v = 400
f3()
print('f2.v=',v)
f2()
print('f1.v=',v)
f1()
>>>
f2.v= 400
f1.v= 200
------------------------------------------------------------------------------------------------------------
練習:
1.寫一個函數,在函數内部讀取學生姓名,并存入清單中,通過兩種方式傳回學生姓名資料并列印出來
方式1:通過傳回值傳回資料
方式2:通過參數傳回資料
解:
方式1(傳回值傳回資料):
def read_names():
L = []
while 1:
n = input('please enter students\' names:')
if n != '0':
L.append(n)
else :
break #逃離最近的循環
return L
print(read_names())
方式2(參數傳回資料):
def read_names(l):
while 1:
n = input('please enter students\' names:')
if n != '0':
l.append(n)
else :
break
L = []
read_names(L)
print(L)
新的解法:
def get_names(x=[]):
L = []
while True:
name = input('please enter students\' names:')
if not name: #@@@@@@@@@@@@@@@@@@@@ 很巧妙的結束循環的方法 ,判斷字元是否為空
break #@@@@@@@@@@@@@@@@@@@@ 跳出最近的循環體
L.append(name)
x[:] = L #切片指派 #也可以用x.extend(L) 追加清單
return L
#方式1
print(get_names()) #傳回值傳遞資料,定義函數中傳回一個值L,這個L清單儲存了所有輸入學生的姓名,實參省略的原因(形參賦予預設值,實參就可以不寫了)
#方式2
n = []
get_names(n) #參數傳遞資料,n與x綁定了同一個空值清單
print(n)
2.寫一個函數,此函數有一個參數op,如下:
def get_op(op):
...
此函數在傳入字元串'加',傳回加操作的函數
def myadd(x,y):return x+y
此函數在傳入字元串'乘',傳回加操作的函數
def myatt(x,y):return x*y
在主程式中程式如下:
a = int(input('輸入第一個數:'))
b = int(input('輸入第一個數:'))
operator = input('請輸入操作方式:')
fn = get_op(operator)
print('結果是:'fn(a,b))
測試用例:
3
2
加<回車>
結果是:5
3
2
乘<回車>
結果是:6
解:
def get_op(op):
def myadd(x,y):
return x+y
def myatt(x,y):
return x*y
if op == '加':
return myadd
if op == '乘':
return myatt
a = int(input('輸入第一個數:'))
b = int(input('輸入第一個數:'))
operator = input('請輸入操作方式:')
fn = get_op(operator)
print('結果是:',fn(a,b))
總結:
a = function() #将函數function運作的結果傳回并指派給a
b = function(x,y) #将函數function運作的結果傳回并指派給b,形參為a和b
c = function #函數名function是個變量,它将函數體内的語句塊全部傳給c,此時c和function是綁定同一堆函數語句的函數名變量
3.給出一個數n,寫一個函數來計算1+2+3+...+n的和,要求用函數來做。
如:print(mysum(100)) #5050
4.給出一個數n,寫一個函數來計算n!(n的階乘)
如:print(myfac(5)) #120
5.給出一個數n,寫一個函數來計算1+2**2+3**3+...n**n的和,注意n給個小點的數。
6.寫函數列印楊輝三角,用清單做,元素逐漸增多。(隻列印6層)
行數n 元素個數
1 1 1
1 1 2 2
1 2 1 3 3
1 3 3 1 4 4
1 4 6 4 1
1 5 10 10 5 1
解第3題:
def mysum(n):
s = 0
for i in range(1,n+1):
s = s + i
return s
a = int(input('please enter a num:'))
print(mysum(a))
解第4題:
def myfc(n):
s = 1
for i in range(1,n+1):
s = s * i
return s
a = int(input('please enter a num:'))
print(myfc(a))
解第5題:
def mytt(n):
s = 0
for i in range(1,n+1):
s = s + i**i
return s
a = int(input('please enter a num:'))
print(mytt(a))
解第6題:
def tri(n):
L = []
i = 1
s = 0
while i <= n:
for a in range(1,n+1):
s = s +s
L.append(s)
return L
a = int(input('please enter a num>=3:'))
print(tri(a))
新解法:
思路:定義函數,計算下一行,輸出本行的資料
def local_line(L,space = 0):
print(' ' * space,end ='')
for x in L:
print(x,end=' ')
print()
def next_line(L):
out_L = []
if L:
out_L.append(L[0])
if len(L)>=2:
for j in range(len(L)-1):
out_L.append(L[j]+L[j+1])
if L:
out_L.append(L[-1])
return out_L
L = [1]
for i in range(6):
local_line(L,6-i-1) #輸出目前行
next_line(L) #計算下一行