在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 問(wèn)答/Python  網(wǎng)絡(luò)安全/ 循環(huán)輸出t為何值不變

循環(huán)輸出t為何值不變

class Count:
    def __init__(self, count=0):
        self.count = count

   
def increment(c, times):
    c.count += 1
    print('c:', c.count)
    times += 1
    print('t:', times)

def main():
    c = Count()
    times = 0
    for i in range(100):
        increment(c, times)

main()

clipboard.png

回答
編輯回答
北城荒

c是引用傳遞,times是值傳遞

class Count:
    def __init__(self, count=0):
        self.count = count


def increment(c, times):
    c.count += 1
    # print('c:', c.count)
    print('c:', id(c))
    times += 1
    # print('t:', times)
    print('t:', id(times))


if __name__ == '__main__':
    c = Count()
    times = 0
    print('times:', id(times))
    for i in range(100):
        increment(c, times)

clipboard.png
可以清晰的看到timesincrement里的times不是同一個(gè)

2017年9月11日 15:00
編輯回答
蟲(chóng)児飛

times 是“值傳遞”。

2017年11月15日 18:09
編輯回答
玄鳥(niǎo)

函數(shù)參數(shù)應(yīng)該會(huì)根據(jù)傳入?yún)?shù)類型是否是可變對(duì)象,或者說(shuō)是否是原始類型去決定是傳值還是傳引用,這點(diǎn)應(yīng)該和js挺像的

2017年7月17日 21:35
編輯回答
墨沫

同意 @dokelung 的答案,準(zhǔn)確來(lái)說(shuō)不是傳引用和傳值,是call by sharing(本質(zhì)上是傳值,不過(guò)這個(gè)值是引用的拷貝),所謂的傳值在這里不是發(fā)生在函數(shù)調(diào)用的時(shí)候,而是在遞增賦值,引用和內(nèi)存的變動(dòng)情況(使用id追蹤):

main:
c -> [一塊Count對(duì)象的內(nèi)存]                main.c -> c1 內(nèi)存
c.count -> [默認(rèn)值初始化為常量0的內(nèi)存]  main.c.count -> '0' 內(nèi)存
times -> [常量0的內(nèi)存]                main.times -> '0' 內(nèi)存 (你沒(méi)有看錯(cuò),指向同一塊內(nèi)存)

increment: 第1次調(diào)用
increment.c -> c1 內(nèi)存
increment.c.count -> '0' 內(nèi)存
increment.times -> '0' 內(nèi)存

increment.c.count += 1          increment.c.count -> '1' 的 內(nèi)存
increment.times += 1            increment.times -> '1' 的 內(nèi)存 (你沒(méi)看錯(cuò),還真是這樣的)

第一次調(diào)用結(jié)束:
increment.c -> c1 內(nèi)存           咱沒(méi)變,main.c -> c1 內(nèi)存 沒(méi)變
increment.c.count -> '1' 內(nèi)存    變了
increment.times -> '1' 內(nèi)存      變了,main.times -> '0' 內(nèi)存 沒(méi)變

-------第一次調(diào)用結(jié)束-------
mian: 咱知道 c1 內(nèi)存,知道 '0' 內(nèi)存,但是 '1' 內(nèi)存???不存在的,咱不知道。
c1內(nèi)存: 咱知道'1'內(nèi)存,不告訴 mian。'0' 內(nèi)存?剛丟了。

-------第n次調(diào)用---------
increment: 第n次調(diào)用
increment.c -> c1 內(nèi)存           main 給我的
increment.c.count -> 'n-1' 內(nèi)存    c1 給我的
increment.times -> '0' 內(nèi)存      main 給我的

increment.c.count += 1          increment.c.count -> 'n' 的 內(nèi)存
increment.times += 1            increment.times -> '1' 的 內(nèi)存 (哎,還是這個(gè))

基本類型(java)和字面常量(python、java) 是不可變的,這就是為什么只能重分配內(nèi)存,而不是在原內(nèi)存上修改。times,c.count 被重分配了內(nèi)存。c卻只是在原內(nèi)存上修改,c是對(duì)象而不是基本類型或者字面常量。

那么為什么 c.count 和 times 都指向過(guò)同一個(gè) '0' 和 '1' 內(nèi)存呢?因?yàn)橛?strong>常量池,小范圍數(shù)字(小于127???)和字面字符串會(huì)被常量池緩存,而不會(huì)每一次都申請(qǐng)一塊新的內(nèi)存。

2017年7月12日 06:51
編輯回答
葬憶

懶了幾天, 這次就詳細(xì)的談一下關(guān)於函數(shù)調(diào)用時(shí)的參數(shù)傳遞問(wèn)題, 我已經(jīng)預(yù)見(jiàn)這個(gè)回答會(huì)相當(dāng)長(zhǎng)了...


(1) 從 variable 跟 value 談起

這是一個(gè)很重要的概念, variable 與其 儲(chǔ)存(參考)的 value (object) 是兩個(gè)完全不同的東西:

variable 是一個(gè)抽象的概念
value 是一個(gè)實(shí)際存在的資料
variable 不是其對(duì)應(yīng)的 value
vairable 儲(chǔ)存(參考)其對(duì)應(yīng)的 value

舉了例子:

a = 5

我們說(shuō) a 是一個(gè) variable, 他參考到 5 這個(gè)整數(shù), a 不是 5, 5 不是 a, 那究竟 variable a 是什麼呢?

我們可以把他想成一個(gè) box (在 c/c++ 中這樣的想像比較妥當(dāng)), 他 儲(chǔ)存 著 value,又或者我們可以把他想像成一個(gè)標(biāo)籤(在 python 中可以這樣想像), 他 參考 到對(duì)應(yīng)的 value。

variable 跟 value 是兩回事, 但他們有 指涉關(guān)係

(2) 關(guān)於 buildin function id

id 常常會(huì)有直覺(jué)上的誤解, 因?yàn)槲覀兂3?huì)使用 variable 作為 id 的 argument:

>>> a = 5
>>> id(a)
140035495798656
>>> a = 6
>>> id(a)
140035495798688
>>> id(5)
140035495798656
>>> id(6)
140035495798688

但從上面的例子可以看出來(lái), 當(dāng)我們使用 id(a) 的時(shí)候, 我們?cè)儐?wèn)的不是 a 的 id, 而是 a 對(duì)應(yīng)到的 value 5 的 id, 這也是為什麼當(dāng) a = 6 的 assign 發(fā)生之後, id(a) 的結(jié)果不同了。

id 問(wèn)的是 value 的 id 不是 variable 的 id

(3) variable assignment 與 data manipulating

(3) 這個(gè)標(biāo)題中提到兩種不同的動(dòng)作(這是我自己亂叫的, 並非專有名詞, 慎之):

variable assignment 指的是變數(shù)的指派跟重新繫結(jié):

>>> a = []
>>> id(a)
140035460871496
>>> a = [1, 2, 3]
>>> id(a)
140035460871560

上面我們讓 a 這個(gè) variable 指涉到另外一個(gè) list, 原本的 empty list 並沒(méi)有被動(dòng)到, a 對(duì)應(yīng)的內(nèi)容之所以改變, 是因?yàn)槲覀冏屗麉⒖嫉搅藙e的 data。

data manipulating 指的是 data 本身進(jìn)行了一些操作, 例如:

>>> a = []
>>> id(a)
140035460871496
>>> a.extend([1, 2, 3])
>>> id(a)
140035460871496

這種操作會(huì)使得 value(data) 的內(nèi)容被改變, 但是 variable 還是指涉到同一個(gè) data。

P.S. 關(guān)於這兩種概念的討論和注意事項(xiàng)請(qǐng)見(jiàn) 評(píng)論 處我與 @chaneyzorn 的討論。

(4) 函數(shù)調(diào)用時(shí)的傳遞方式

其實(shí)整個(gè) function call 的 pass way 只是 求值策略 Evaluation strateg 的一部分, 但我們今天重點(diǎn)就放在 function call 上面。

最常見(jiàn)的傳遞方式有三種:

  1. pass by value
  2. pass by reference
  3. pass by sharing (call by object reference)

我們一種一種來(lái)分析:

pass by value

此種傳遞方式會(huì)在函數(shù)內(nèi)另起一個(gè)新的 variable, 並將 caller 傳入的 argument value 複製一份傳入, 此新 variable 將會(huì)指涉到這個(gè)複製的 value, 這個(gè)行為牽涉到兩個(gè)不同的 variable, 而行為上是傳遞 value 本身, 所以叫做 pass by value。

想像: 在被呼叫的函數(shù)中製造一個(gè)新的 box, 並將 caller box 中的內(nèi)容 copy 一份存到新的 box 中

舉個(gè) c++ 的例子:

void foo(int a) {
    a = 6;
    return;
}

int main() {
    int a = 1;
    foo(a);
    return 0;
}

此處, main 中的 afoo 中的 a 是兩個(gè)完全不同的 variable, 所以當(dāng)我們進(jìn)行 variable assignment (a = b) 的時(shí)候, 外面的 a 完全不會(huì)受到影響。同樣地, 因?yàn)?value 也是複製的, 如果我們進(jìn)行 data manipulating 也完全不會(huì)影響到函數(shù)以外的世界。

對(duì)兩種操作的影響: 不論在 function 中進(jìn)行 variable assignment 或是 data manipulating 都不會(huì)影響外界。

pass by reference

此種傳遞方式並不一定會(huì)在函數(shù)內(nèi)另起一個(gè)新 variable (要看語(yǔ)言), 但是 caller
中的 argument value 一定不會(huì)被複製。這種傳遞方式通常是被 特殊的語(yǔ)法 或是 模擬方法 所實(shí)現(xiàn)。

比如說(shuō), 在 c++ 中, 預(yù)設(shè)的傳遞方法是 call by value, 但他提供了特殊的參照語(yǔ)法讓我們得以實(shí)現(xiàn) pass by referece, 讓我們看一個(gè)例子:

void foo(int& a) {
    a = 6;
    return;
}

int main() {
    int a = 1;
    foo(a);
    return 0;
}

在這個(gè)例子中, foo 中的 amain 中的 a 是同一個(gè) variable, 如果這邊是一個(gè) object 而不是整數(shù), 我們也能看見(jiàn)這個(gè) a 指涉到一樣的 value (這是當(dāng)然的, 因?yàn)檫@邊根本就只有一個(gè) variable a)。

另外像是 c, 預(yù)設(shè)的傳遞方法也是 call by value, 我們得利用指標(biāo)來(lái)模擬 pass by referece, 見(jiàn)下例 (出自 wiki):

void modify(int p, int* q, int* r) {
    p = 27;  // passed by value: only the local parameter is modified
    *q = 27; // passed by value or reference, check call site to determine which
    *r = 27; // passed by value or reference, check call site to determine which
}

int main() {
    int a = 1;
    int b = 1;
    int x = 1;
    int* c = &x;
    
    // a is passed by value
    // b is passed by reference by creating a pointer
    // c is a pointer passed by value
    // b and x are changed
    modify(a, &b, c); 
    
    return 0;
}

這個(gè)例子展示了 c 中完整的 pass 方法, a 是個(gè)整數(shù), 使用 call by value 的方式傳遞, p 是一個(gè)全新的 variable, 於是我們對(duì)此 variable 重新指派不影響外面的 a 指涉的值。b 的傳遞透過(guò)指標(biāo)來(lái)進(jìn)行,此處的 q 雖然是全新的 variable, 但是 *q 卻是指涉到原本的值且對(duì) *q 的重指派等若對(duì)於 b 的重指派, 若此處 b 為一個(gè) object, 我們?cè)?modify 中對(duì) *q 指涉到的 object 作 data manipulating 一樣會(huì)影響到外面 b 指涉到的 object (因?yàn)樗麄兪峭粋€(gè))。xc 的部分就請(qǐng)大家自行參考, 不再多言。

這種藉由 pointer (address) 來(lái)模擬 pass by reference 的方法也被稱為 pass by address。

想像: 將 caller box 和其中的內(nèi)容直接傳遞到 function
對(duì)兩種操作的影響: 不論在 function 中進(jìn)行 variable assignment 或是 data manipulating 都會(huì)影響外界。

pass by sharing

有人把這種傳遞方式稱作 pass by object reference, 但在 wiki 中是被叫做 call by sharing, 此方式由 Barbara Liskov 命名 ,並被 Python、Java(物件類型)、JavaScript、Scheme、OCaml 等語(yǔ)言使用。

此方式會(huì)在函數(shù)中製造一個(gè)新的 variable, 但是傳入的 value 卻不會(huì)複製, 而是讓裡面的新 variable 指涉到一樣的 value (data)。

想像: 在被呼叫的函數(shù)中製造一個(gè)新的 box, 但不複製 caller box 中的內(nèi)容, 而是裝載同一份內(nèi)容

請(qǐng)看以下例子:

def foo(lst):
    lst.append(1)
    lst = [2]
    
m = []
foo(m)
print(m)

你覺(jué)得 print(m) 的結(jié)果是什麼呢? 答案是 [1], 原因很簡(jiǎn)單, foo 中的 lst 一開(kāi)始指涉到外面?zhèn)魅氲?empty list, 所以當(dāng)我們 lst.appedn(1) 時(shí), lst 參考到的 list 跟外面的 m 參考到的 list 都被更動(dòng)為 [1], 但 lst = [2] 卻不會(huì)影響外界, 因?yàn)?lstm 是兩個(gè)不同的 variable, 我們只是將 lst 重新指派到一個(gè)新的 list 而已。

對(duì)兩種操作的影響: 在 function 中進(jìn)行 variable assignment 的時(shí)候不會(huì)影響外界, 但是作 data manipulating 會(huì)影響外界。

(5) 回頭看本題

所以 python 並不會(huì)因?yàn)?data 是可變還是不可變的因素去決定是傳值還是傳參, 因?yàn)樗疾皇? 他自有其特殊的傳遞方式:

class Count:
    def __init__(self, count=0):
        self.count = count

def increment(c, times):
    c.count += 1
    # print('c:', c.count)
    print('c:', id(c))
    times += 1
    # print('t:', times)
    print('t:', id(times))

if __name__ == '__main__':
    c = Count()
    times = 0
    print('times:', id(times))
    for i in range(100):
        increment(c, times)

之所以 c.count 改變而 times 不變, 並非因?yàn)?c 是傳參, times 傳值, 而是對(duì)於 pass by sharing 來(lái)說(shuō), 以 argument c 的角度而言, c.count += 1 是一個(gè) data manipulating 的動(dòng)作, 我們操作了與外界同一份 data 所以影響了外面的 c, 而 times += 1 以 argument times 的角度而言 是一個(gè) variable assignment 的動(dòng)作, 我們操作了與外界不同一個(gè)的 variable, 所以不會(huì)影響外面。

我們來(lái)看一下上面的實(shí)驗(yàn), 在外面的 id(times) 問(wèn)的是 0 的 id, 而在 increment 中問(wèn)的都是 1 的 id, 所以才會(huì)第一個(gè) id(times) 與剩下來(lái)的 id(times) 不同, 但剩下來(lái)的 id(times) 皆相同。卻不是因?yàn)橥饷娴?times 跟裡面的 times 是不一樣的 variable 所以才會(huì)這樣的, 這裡的 times 沒(méi)有傳值, 而是共享值。

要怎麼突破這個(gè)盲點(diǎn)呢? 可以加上一行來(lái)測(cè)試:

def increment(c, times):
    print('t begin:', id(times))
    c.count += 1
    ...

結(jié)果:

times: 140303106782944
t begin: 140303106782944
c: 140303062368832
t: 140303106782976
t begin: 140303106782944
c: 140303062368832
t: 140303106782976
t begin: 140303106782944
c: 140303062368832
t: 140303106782976
t begin: 140303106782944

你會(huì)發(fā)現(xiàn) timest begin 一樣, 值沒(méi)有被複製, 至於 t 為何最後變了, 那是因?yàn)?times += 1times 指到別人了。

(6) 結(jié)論

所以, 別再說(shuō) python 是 pass by value 或是 pass by reference 了, 都不是!!
python 是完完全全的 call by sharing!

(7) 參考資料


我回答過(guò)的問(wèn)題: Python-QA

2017年5月27日 02:20