python函數(shù)運(yùn)行機(jī)制,python運(yùn)行方法

Python函數(shù)的參數(shù)類型

Python函數(shù)的參數(shù)類型主要包括必選參數(shù)、可選參數(shù)、可變參數(shù)、位置參數(shù)和關(guān)鍵字參數(shù),本文介紹一下他們的定義以及可變數(shù)據(jù)類型參數(shù)傳遞需要注意的地方。

我們提供的服務(wù)有:成都網(wǎng)站設(shè)計、做網(wǎng)站、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、子洲ssl等。為近1000家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的子洲網(wǎng)站制作公司

必選參數(shù)(Required arguments)是必須輸入的參數(shù),比如下面的代碼,必須輸入2個參數(shù),否則就會報錯:

其實上面例子中的參數(shù) num1和num2也屬于關(guān)鍵字參數(shù),比如可以通過如下方式調(diào)用:

執(zhí)行結(jié)果:

可選參數(shù)(Optional arguments)可以不用傳入函數(shù),有一個默認(rèn)值,如果沒有傳入會使用默認(rèn)值,不會報錯。

位置參數(shù)(positional arguments)根據(jù)其在函數(shù)定義中的位置調(diào)用,下面是pow()函數(shù)的幫助信息:

x,y,z三個參數(shù)的的順序是固定的,并且不能使用關(guān)鍵字:

輸出:

在上面的pow()函數(shù)幫助信息中可以看到位置參數(shù)后面加了一個反斜杠 / ,這是python內(nèi)置函數(shù)的語法定義,Python開發(fā)人員不能在python3.8版本之前的代碼中使用此語法。但python3.0到3.7版本可以使用如下方式定義位置參數(shù):

星號前面的參數(shù)為位置參數(shù)或者關(guān)鍵字參數(shù),星號后面是強(qiáng)制關(guān)鍵字參數(shù),具體介紹見強(qiáng)制關(guān)鍵字參數(shù)。

python3.8版本引入了強(qiáng)制位置參數(shù)(Positional-Only Parameters),也就是我們可以使用反斜杠 / 語法來定義位置參數(shù)了,可以寫成如下形式:

來看下面的例子:

python3.8運(yùn)行:

不能使用關(guān)鍵字參數(shù)形式賦值了。

可變參數(shù) (varargs argument) 就是傳入的參數(shù)個數(shù)是可變的,可以是0-n個,使用星號( * )將輸入?yún)?shù)自動組裝為一個元組(tuple):

執(zhí)行結(jié)果:

關(guān)鍵字參數(shù)(keyword argument)允許將任意個含參數(shù)名的參數(shù)導(dǎo)入到python函數(shù)中,使用雙星號( ** ),在函數(shù)內(nèi)部自動組裝為一個字典。

執(zhí)行結(jié)果:

上面介紹的參數(shù)可以混合使用:

結(jié)果:

注意:由于傳入的參數(shù)個數(shù)不定,所以當(dāng)與普通參數(shù)一同使用時,必須把帶星號的參數(shù)放在最后。

強(qiáng)制關(guān)鍵字參數(shù)(Keyword-Only Arguments)是python3引入的特性,可參考:。 使用一個星號隔開:

在位置參數(shù)一節(jié)介紹過星號前面的參數(shù)可以是位置參數(shù)和關(guān)鍵字參數(shù)。星號后面的參數(shù)都是強(qiáng)制關(guān)鍵字參數(shù),必須以指定參數(shù)名的方式傳參,如果強(qiáng)制關(guān)鍵字參數(shù)沒有設(shè)置默認(rèn)參數(shù),調(diào)用函數(shù)時必須傳參。

執(zhí)行結(jié)果:

也可以在可變參數(shù)后面命名關(guān)鍵字參數(shù),這樣就不需要星號分隔符了:

執(zhí)行結(jié)果:

在Python對象及內(nèi)存管理機(jī)制中介紹了python中的參數(shù)傳遞屬于對象的 引用傳遞 (pass by object reference),在編寫函數(shù)的時候需要特別注意。

先來看個例子:

執(zhí)行結(jié)果:

l1 和 l2指向相同的地址,由于列表可變,l1改變時,l2也跟著變了。

接著看下面的例子:

結(jié)果:

l1沒有變化!為什么不是[1, 2, 3, 4]呢?

l = l + [4]表示創(chuàng)建一個“末尾加入元素 4“的新列表,并讓 l 指向這個新的對象,l1沒有進(jìn)行任何操作,因此 l1 的值不變。如果要改變l1的值,需要加一個返回值:

結(jié)果:

下面的代碼執(zhí)行結(jié)果又是什么呢?

執(zhí)行結(jié)果:

和第一個例子一樣,l1 和 l2指向相同的地址,所以會一起改變。這個問題怎么解決呢?

可以使用下面的方式:

也可以使用淺拷貝或者深度拷貝,具體使用方法可參考Python對象及內(nèi)存管理機(jī)制。這個問題在Python編程時需要特別注意。

本文主要介紹了python函數(shù)的幾種參數(shù)類型:必選參數(shù)、可選參數(shù)、可變參數(shù)、位置參數(shù)、強(qiáng)制位置參數(shù)、關(guān)鍵字參數(shù)、強(qiáng)制關(guān)鍵字參數(shù),注意他們不是完全獨(dú)立的,比如必選參數(shù)、可選參數(shù)也可以是關(guān)鍵字參數(shù),位置參數(shù)可以是必選參數(shù)或者可選參數(shù)。

另外,python中的參數(shù)傳遞屬于對象的 引用傳遞 ,在對可變數(shù)據(jù)類型進(jìn)行參數(shù)傳遞時需要特別注意,如有必要,使用python的拷貝方法。

參考文檔:

--THE END--

Python 里為什么函數(shù)可以返回一個函數(shù)內(nèi)部定義的函數(shù)

“在Python中,函數(shù)本身也是對象”

這一本質(zhì)。那不妨慢慢來,從最基本的概念開始,討論一下這個問題:

1. Python中一切皆對象

這恐怕是學(xué)習(xí)Python最有用的一句話。想必你已經(jīng)知道Python中的list, tuple, dict等內(nèi)置數(shù)據(jù)結(jié)構(gòu),當(dāng)你執(zhí)行:

alist = [1, 2, 3]

時,你就創(chuàng)建了一個列表對象,并且用alist這個變量引用它:

當(dāng)然你也可以自己定義一個類:

class House(object):

def __init__(self, area, city):

self.area = area

self.city = city

def sell(self, price):

[...] #other code

return price

然后創(chuàng)建一個類的對象:

house = House(200, 'Shanghai')

OK,你立馬就在上海有了一套200平米的房子,它有一些屬性(area, city),和一些方法(__init__, self):

2. 函數(shù)是第一類對象

和list, tuple, dict以及用House創(chuàng)建的對象一樣,當(dāng)你定義一個函數(shù)時,函數(shù)也是對象:

def func(a, b):

return a+b

在全局域,函數(shù)對象被函數(shù)名引用著,它接收兩個參數(shù)a和b,計算這兩個參數(shù)的和作為返回值。

所謂第一類對象,意思是可以用標(biāo)識符給對象命名,并且對象可以被當(dāng)作數(shù)據(jù)處理,例如賦值、作為參數(shù)傳遞給函數(shù),或者作為返回值return 等

因此,你完全可以用其他變量名引用這個函數(shù)對象:

add = func

這樣,你就可以像調(diào)用func(1, 2)一樣,通過新的引用調(diào)用函數(shù)了:

print func(1, 2)

print add(1, 2) #the same as func(1, 2)

或者將函數(shù)對象作為參數(shù),傳遞給另一個函數(shù):

def caller_func(f):

return f(1, 2)

if __name__ == "__main__":

print caller_func(func)

可以看到,

函數(shù)對象func作為參數(shù)傳遞給caller_func函數(shù),傳參過程類似于一個賦值操作f=func;

于是func函數(shù)對象,被caller_func函數(shù)作用域中的局部變量f引用,f實際指向了函數(shù)func;cc

當(dāng)執(zhí)行return f(1, 2)的時候,相當(dāng)于執(zhí)行了return func(1, 2);

因此輸出結(jié)果為3。

3. 函數(shù)對象 vs 函數(shù)調(diào)用

無論是把函數(shù)賦值給新的標(biāo)識符,還是作為參數(shù)傳遞給新的函數(shù),針對的都是函數(shù)對象本身,而不是函數(shù)的調(diào)用。

用一個更加簡單,但從外觀上看,更容易產(chǎn)生混淆的例子來說明這個問題。例如定義了下面這個函數(shù):

def func():

return "hello,world"

然后分別執(zhí)行兩次賦值:

ref1 = func #將函數(shù)對象賦值給ref1

ref2 = func() #調(diào)用函數(shù),將函數(shù)的返回值("hello,world"字符串)賦值給ref2

很多初學(xué)者會混淆這兩種賦值,通過Python內(nèi)建的type函數(shù),可以查看一下這兩次賦值的結(jié)果:

In [4]: type(ref1)

Out[4]: function

In [5]: type(ref2)

Out[5]: str

可以看到,ref1引用了函數(shù)對象本身,而ref2則引用了函數(shù)的返回值。通過內(nèi)建的callable函數(shù),可以進(jìn)一步驗證ref1是可調(diào)用的,而ref2是不可調(diào)用的:

In [9]: callable(ref1)

Out[9]: True

In [10]: callable(ref2)

Out[10]: False

傳參的效果與之類似。

4. 閉包LEGB法則

所謂閉包,就是將組成函數(shù)的語句和這些語句的執(zhí)行環(huán)境打包在一起時,得到的對象

聽上去的確有些復(fù)雜,還是用一個栗子來幫助理解一下。假設(shè)我們在foo.py模塊中做了如下定義:

#foo.py

filename = "foo.py"

def call_func(f):

return f() #如前面介紹的,f引用一個函數(shù)對象,然后調(diào)用它

在另一個func.py模塊中,寫下了這樣的代碼:

#func.py

import foo #導(dǎo)入foo.py

filename = "func.py"

def show_filename():

return "filename: %s" % filename

if __name__ == "__main__":

print foo.call_func(show_filename) #注意:實際發(fā)生調(diào)用的位置,是在foo.call_func函數(shù)中

當(dāng)我們用python func.py命令執(zhí)行func.py時輸出結(jié)果為:

chiyu@chiyu-PC:~$ python func.py

filename:func.py

很顯然show_filename()函數(shù)使用的filename變量的值,是在與它相同環(huán)境(func.py模塊)中定義的那個。盡管foo.py模塊中也定義了同名的filename變量,而且實際調(diào)用show_filename的位置也是在foo.py的call_func內(nèi)部。

而對于嵌套函數(shù),這一機(jī)制則會表現(xiàn)的更加明顯:閉包將會捕捉內(nèi)層函數(shù)執(zhí)行所需的整個環(huán)境:

#enclosed.py

import foo

def wrapper():

filename = "enclosed.py"

def show_filename():

return "filename: %s" % filename

print foo.call_func(show_filename) #輸出:filename: enclosed.py

實際上,每一個函數(shù)對象,都有一個指向了該函數(shù)定義時所在全局名稱空間的__globals__屬性:

#show_filename inside wrapper

#show_filename.__globals__

{

'__builtins__': module '__builtin__' (built-in), #內(nèi)建作用域環(huán)境

'__file__': 'enclosed.py',

'wrapper': function wrapper at 0x7f84768b6578, #直接外圍環(huán)境

'__package__': None,

'__name__': '__main__',

'foo': module 'foo' from '/home/chiyu/foo.pyc', #全局環(huán)境

'__doc__': None

}

當(dāng)代碼執(zhí)行到show_filename中的return "filename: %s" % filename語句時,解析器按照下面的順序查找filename變量:

Local - 本地函數(shù)(show_filename)內(nèi)部,通過任何方式賦值的,而且沒有被global關(guān)鍵字聲明為全局變量的filename變量;

Enclosing - 直接外圍空間(上層函數(shù)wrapper)的本地作用域,查找filename變量(如果有多層嵌套,則由內(nèi)而外逐層查找,直至最外層的函數(shù));

Global - 全局空間(模塊enclosed.py),在模塊頂層賦值的filename變量;

Builtin - 內(nèi)置模塊(__builtin__)中預(yù)定義的變量名中查找filename變量;

在任何一層先找到了符合要求的filename變量,則不再向更外層查找。如果直到Builtin層仍然沒有找到符合要求的變量,則拋出NameError異常。這就是變量名解析的:LEGB法則。

總結(jié):

閉包最重要的使用價值在于:封存函數(shù)執(zhí)行的上下文環(huán)境;

閉包在其捕捉的執(zhí)行環(huán)境(def語句塊所在上下文)中,也遵循LEGB規(guī)則逐層查找,直至找到符合要求的變量,或者拋出異常。

5. 裝飾器語法糖(syntax sugar)

那么閉包和裝飾器又有什么關(guān)系呢?

上文提到閉包的重要特性:封存上下文,這一特性可以巧妙的被用于現(xiàn)有函數(shù)的包裝,從而為現(xiàn)有函數(shù)更加功能。而這就是裝飾器。

還是舉個例子,代碼如下:

#alist = [1, 2, 3, ..., 100] -- 1+2+3+...+100 = 5050

def lazy_sum():

return reduce(lambda x, y: x+y, alist)

我們定義了一個函數(shù)lazy_sum,作用是對alist中的所有元素求和后返回。alist假設(shè)為1到100的整數(shù)列表:

alist = range(1, 101)

但是出于某種原因,我并不想馬上返回計算結(jié)果,而是在之后的某個地方,通過顯示的調(diào)用輸出結(jié)果。于是我用一個wrapper函數(shù)對其進(jìn)行包裝:

def wrapper():

alist = range(1, 101)

def lazy_sum():

return reduce(lambda x, y: x+y, alist)

return lazy_sum

lazy_sum = wrapper() #wrapper() 返回的是lazy_sum函數(shù)對象

if __name__ == "__main__":

lazy_sum() #5050

這是一個典型的Lazy Evaluation的例子。我們知道,一般情況下,局部變量在函數(shù)返回時,就會被垃圾回收器回收,而不能再被使用。但是這里的alist卻沒有,它隨著lazy_sum函數(shù)對象的返回被一并返回了(這個說法不準(zhǔn)確,實際是包含在了lazy_sum的執(zhí)行環(huán)境中,通過__globals__),從而延長了生命周期。

當(dāng)在if語句塊中調(diào)用lazy_sum()的時候,解析器會從上下文中(這里是Enclosing層的wrapper函數(shù)的局部作用域中)找到alist列表,計算結(jié)果,返回5050。

當(dāng)你需要動態(tài)的給已定義的函數(shù)增加功能時,比如:參數(shù)檢查,類似的原理就變得很有用:

def add(a, b):

return a+b

這是很簡單的一個函數(shù):計算a+b的和返回,但我們知道Python是 動態(tài)類型+強(qiáng)類型 的語言,你并不能保證用戶傳入的參數(shù)a和b一定是兩個整型,他有可能傳入了一個整型和一個字符串類型的值:

In [2]: add(1, 2)

Out[2]: 3

In [3]: add(1.2, 3.45)

Out[3]: 4.65

In [4]: add(5, 'hello')

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

TypeError Traceback (most recent call last)

/home/chiyu/ipython-input-4-f2f9e8aa5eae in module()

---- 1 add(5, 'hello')

/home/chiyu/ipython-input-1-02b3d3d6caec in add(a, b)

1 def add(a, b):

---- 2 return a+b

TypeError: unsupported operand type(s) for +: 'int' and 'str'

于是,解析器無情的拋出了一個TypeError異常。

動態(tài)類型:在運(yùn)行期間確定變量的類型,python確定一個變量的類型是在你第一次給他賦值的時候;

強(qiáng)類型:有強(qiáng)制的類型定義,你有一個整數(shù),除非顯示的類型轉(zhuǎn)換,否則絕不能將它當(dāng)作一個字符串(例如直接嘗試將一個整型和一個字符串做+運(yùn)算);

因此,為了更加優(yōu)雅的使用add函數(shù),我們需要在執(zhí)行+運(yùn)算前,對a和b進(jìn)行參數(shù)檢查。這時候裝飾器就顯得非常有用:

import logging

logging.basicConfig(level = logging.INFO)

def add(a, b):

return a + b

def checkParams(fn):

def wrapper(a, b):

if isinstance(a, (int, float)) and isinstance(b, (int, float)): #檢查參數(shù)a和b是否都為整型或浮點(diǎn)型

return fn(a, b) #是則調(diào)用fn(a, b)返回計算結(jié)果

#否則通過logging記錄錯誤信息,并友好退出

logging.warning("variable 'a' and 'b' cannot be added")

return

return wrapper #fn引用add,被封存在閉包的執(zhí)行環(huán)境中返回

if __name__ == "__main__":

#將add函數(shù)對象傳入,fn指向add

#等號左側(cè)的add,指向checkParams的返回值wrapper

add = checkParams(add)

add(3, 'hello') #經(jīng)過類型檢查,不會計算結(jié)果,而是記錄日志并退出

注意checkParams函數(shù):

首先看參數(shù)fn,當(dāng)我們調(diào)用checkParams(add)的時候,它將成為函數(shù)對象add的一個本地(Local)引用;

在checkParams內(nèi)部,我們定義了一個wrapper函數(shù),添加了參數(shù)類型檢查的功能,然后調(diào)用了fn(a, b),根據(jù)LEGB法則,解釋器將搜索幾個作用域,并最終在(Enclosing層)checkParams函數(shù)的本地作用域中找到fn;

注意最后的return wrapper,這將創(chuàng)建一個閉包,fn變量(add函數(shù)對象的一個引用)將會封存在閉包的執(zhí)行環(huán)境中,不會隨著checkParams的返回而被回收;

當(dāng)調(diào)用add = checkParams(add)時,add指向了新的wrapper對象,它添加了參數(shù)檢查和記錄日志的功能,同時又能夠通過封存的fn,繼續(xù)調(diào)用原始的add進(jìn)行+運(yùn)算。

因此調(diào)用add(3, 'hello')將不會返回計算結(jié)果,而是打印出日志:

chiyu@chiyu-PC:~$ python func.py

WARNING:root:variable 'a' and 'b' cannot be added

有人覺得add = checkParams(add)這樣的寫法未免太過麻煩,于是python提供了一種更優(yōu)雅的寫法,被稱為語法糖:

@checkParams

def add(a, b):

return a + b

這只是一種寫法上的優(yōu)化,解釋器仍然會將它轉(zhuǎn)化為add = checkParams(add)來執(zhí)行。

6. 回歸問題

def addspam(fn):

def new(*args):

print "spam,spam,spam"

return fn(*args)

return new

@addspam

def useful(a,b):

print a**2+b**2

首先看第二段代碼:

@addspam裝飾器,相當(dāng)于執(zhí)行了useful = addspam(useful)。在這里題主有一個理解誤區(qū):傳遞給addspam的參數(shù),是useful這個函數(shù)對象本身,而不是它的一個調(diào)用結(jié)果;

再回到addspam函數(shù)體:

return new 返回一個閉包,fn被封存在閉包的執(zhí)行環(huán)境中,不會隨著addspam函數(shù)的返回被回收;

而fn此時是useful的一個引用,當(dāng)執(zhí)行return fn(*args)時,實際相當(dāng)于執(zhí)行了return useful(*args);

最后附上一張代碼執(zhí)行過程中的引用關(guān)系圖,希望能幫助你理解:

python的內(nèi)存管理機(jī)制

論壇

活動

招聘

專題

打開CSDN APP

Copyright ? 1999-2020, CSDN.NET, All Rights Reserved

登錄

XCCS_澍

關(guān)注

Python 的內(nèi)存管理機(jī)制及調(diào)優(yōu)手段? 原創(chuàng)

2018-08-05 06:50:53

XCCS_澍

碼齡7年

關(guān)注

內(nèi)存管理機(jī)制:引用計數(shù)、垃圾回收、內(nèi)存池。

一、引用計數(shù):

引用計數(shù)是一種非常高效的內(nèi)存管理手段, 當(dāng)一個 Python 對象被引用時其引用計數(shù)增加 1, 當(dāng)其不再被一個變量引用時則計數(shù)減 1. 當(dāng)引用計數(shù)等于 0 時對象被刪除。

二、垃圾回收 :

1. 引用計數(shù)

引用計數(shù)也是一種垃圾收集機(jī)制,而且也是一種最直觀,最簡單的垃圾收集技術(shù)。當(dāng) Python 的某個對象的引用計數(shù)降為 0 時,說明沒有任何引用指向該對象,該對象就成為要被回收的垃圾了。比如某個新建對象,它被分配給某個引用,對象的引用計數(shù)變?yōu)?1。如果引用被刪除,對象的引用計數(shù)為 0,那么該對象就可以被垃圾回收。不過如果出現(xiàn)循環(huán)引用的話,引用計數(shù)機(jī)制就不再起有效的作用了

2. 標(biāo)記清除

如果兩個對象的引用計數(shù)都為 1,但是僅僅存在他們之間的循環(huán)引用,那么這兩個對象都是需要被回收的,也就是說,它們的引用計數(shù)雖然表現(xiàn)為非 0,但實際上有效的引用計數(shù)為 0。所以先將循環(huán)引用摘掉,就會得出這兩個對象的有效計數(shù)。

3. 分代回收

從前面“標(biāo)記-清除”這樣的垃圾收集機(jī)制來看,這種垃圾收集機(jī)制所帶來的額外操作實際上與系統(tǒng)中總的內(nèi)存塊的數(shù)量是相關(guān)的,當(dāng)需要回收的內(nèi)存塊越多時,垃圾檢測帶來的額外操作就越多,而垃圾回收帶來的額外操作就越少;反之,當(dāng)需回收的內(nèi)存塊越少時,垃圾檢測就將比垃圾回收帶來更少的額外操作。

python程序打包后源程序的運(yùn)行機(jī)制?

如果有main就是從main入口進(jìn)入執(zhí)行

沒有的話,我猜想是順序執(zhí)行

因為exe其實也就是把你的py文件需要用的包和環(huán)境打包,然后再其他設(shè)備上,即使沒有安裝python的也可以正常運(yùn)行,其運(yùn)行效果和你在本地調(diào)試運(yùn)行應(yīng)沒有分別

名稱欄目:python函數(shù)運(yùn)行機(jī)制,python運(yùn)行方法
當(dāng)前網(wǎng)址:http://www.muchs.cn/article24/hcphje.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動網(wǎng)站建設(shè)、網(wǎng)站策劃、ChatGPT、企業(yè)網(wǎng)站制作、App設(shè)計、面包屑導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)