【Python】引用和对象的理解

版权声明:本文为博主原创文章,转载请注明出处,谢谢! https://blog.csdn.net/Jkwwwwwwwwww/article/details/72934647

Overview

在Python中使用变量进行值修改、参数传递、以及复制变量等等的过程中,往往会出现一些我们意想不到的“错误”。
但实际上产生这些“错误”的原因,大多是因为没有深入地理解Python内部的对象引用机制。
针对于此,笔者大致整理了10个例子,用以循序渐进地帮助大家加深对于Python引用和对象的理解。
Here we go.

Example 1

a = 3

这是一个简单的赋值语句,整数 3 为一个对象,a 是一个引用,利用赋值语句,引用a指向了对象3。
形象比喻一下:这个过程就相当于“放风筝”,变量a就是你手里面的“线”,python就跟那根“线”一样,通过引用来接触和拴住天空中的风筝——对象。
引用[3]
你可以通过python的内置函数 id() 来查看对象的身份(identity),这个所谓的身份其实就是 对象 的内存地址。

Example 2

利用上面的 id()函数:

>>> a = 1
>>> id(a)
24834392

>>> a = 'banana'
>>> id(a)
139990659655312

第一个语句中, 2是储存在内存中的一个整数对象,通过赋值 引用a 指向了 对象 1
第二个语句中,内存中建立了一个字符串对象‘banana’,通过赋值 将 引用a 指向了 ‘banana’,同时,对象1不在有引用指向它,它会被python的内存处理机制给当我垃圾回收,释放内存。

Example 3

>>> a = 3
>>> b = 3

>>> id(a)
10289448
>>> id(b)
10289448

在这里可以看到 这俩个引用 指向了同一个 对象,这是为什么呢? 这个跟python的内存机制有关系,因为对于语言来说,频繁的进行对象的销毁和建立,特别浪费性能。所以在Python中,整数和短小的字符,Python都会缓存这些对象,以便重复使用。

Example 4

>>> a = 4
>>> b = a
>>> id(a)
36151568
>>> id(b)
36151568

>>> a = a + 2
>>> id(a)
36151520
>>> id(b)
36151568

可以看到 a 的引用改变了,但是 b 的引用未发生改变;a,b指向不同的对象; 第3句对 a 进行了重新赋值,让它指向了新的 对象6;即使是多个引用指向同一个对象,如果一个引用值发生变化,那么实际上是让这个引用指向一个新的引用,并不影响其他的引用的指向。从效果上看,就是各个引用各自独立,互不影响。

Example 5

>>> L1 = [1,2,3]
>>> L2 = L1
>>> id(L1)
139643051219496
>>> id(L2)
139643051219496

>>> L1[0] = 10
>>> id(L1)
139643051219496
>>> id(L2)
139643051219496
>>> L2
[10, 2, 3]

同样的跟Example 4那样,修改了其中一个对象的值,但是可以发现 结果 并不与 Example 4相同, 在本次实验中,L1 和 L2 的引用没有发生任何变化,但是 列表对象[1,2,3] 的值 变成了 [10,2,3](列表对象改变了)

在该情况下,我们不再对L1这一引用赋值,而是对L1所指向的表的元素赋值。结果是,L2也同时发生变化。
原因何在呢?因为L1,L2的指向没有发生变化,依然指向那个表。表实际上是包含了多个引用的对象(每个引用是一个元素,比如L1[0],L1[1]…, 每个引用指向一个对象,比如1,2,3), 。而L1[0] = 10这一赋值操作,并不是改变L1的指向,而是对L1[0], 也就是表对象的一部份(一个元素),进行操作,所以所有指向该对象的引用都受到影响。
(与之形成对比的是,我们之前的赋值操作都没有对对象自身发生作用,只是改变引用指向。)

列表可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可变数据对象(mutable object),词典也是这样的数据类型。
而像之前的数字和字符串,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。对于元组(tuple),尽管可以调用引用元素,但不可以赋值,因此不能改变对象自身,所以也算是immutable object.

Example 6

>>> a = 4 # id(a) = 36151568
>>> b = a # id(b) = 36151568
>>> a is b 
True

>>> a = a + 2 # id(a) = 36151520
>>> a is b # id(b) = 36151568
False

如果我们想知道两个引用是否指向同一个对象,可以使用 python的 is 关键词,is即是用于判断两个引用所指的对象是否相同。

Example 7

def add_list(p):
    p = p + [1]
p1 = [1, 2, 3]
add_list(p1)
print p1
>>> [1, 2, 3]

def add_list(p):
    p += [1]
p2 = [1, 2, 3]
proc2(p2)
print p2
>>>[1, 2, 3, 1]

这主要是由于“=”操作符会新建一个新的变量保存赋值结果,然后再把引用名指向“=”左边,即修改了原来的p引用,使p成为指向新赋值变量的引用。而+=不会,直接修改了原来p引用的内容,事实上+=和=在python内部使用了不同的实现函数。

Example 8

a = []
b = {'num': 0, 'sqrt': 0}
resurse = [1, 2, 3]
for i in resurse:
    b['num'] = i
    b['sqrt'] = i * i
    a.append(b)
print a
>>> [{'num': 3, 'sqrt': 9}, {'num': 3, 'sqrt': 9}, {'num': 3, 'sqrt': 9}]

但我们实际想要的结果是这样的:

>>> [{'num': 1, 'sqrt': 1}, {'num': 2, 'sqrt': 4}, {'num': 3, 'sqrt': 9}]

这是由于a中的元素就是b的引用。可以修改为:

a = []
resurse = [1, 2, 3]
for i in resurse:
    a.append({"num": i, "sqrt": i * i})

Example 9

定义一个家族谱词典:value为key的parent. 要写一个函数,输入人名,给出这个人的所有祖先名字。
开始的做法:

ada_family = { 'Judith Blunt-Lytton': ['Anne Isabella Blunt', 'Wilfrid Scawen Blunt'],
              'Ada King-Milbanke': ['Ralph King-Milbanke', 'Fanny Heriot'],
              'Ralph King-Milbanke': ['Augusta Ada King', 'William King-Noel'],
              'Anne Isabella Blunt': ['Augusta Ada King', 'William King-Noel'],
              'Byron King-Noel': ['Augusta Ada King', 'William King-Noel'],
              'Augusta Ada King': ['Anne Isabella Milbanke', 'George Gordon Byron'],
              'George Gordon Byron': ['Catherine Gordon', 'Captain John Byron'],
              'John Byron': ['Vice-Admiral John Byron', 'Sophia Trevannion'] }


def ancestors(genealogy, person):
    if person in genealogy:
        parents = genealogy[person]
        result = parents
        for parent in parents:
            result += ancestors(genealogy, parent)
        return result
    return []

print ancestors2(ada_family, 'Judith Blunt-Lytton')
print ada_family


>>> ['Anne Isabella Blunt', 'Wilfrid Scawen Blunt', 'Augusta Ada King',
    'William King-Noel', 'Anne Isabella Milbanke', 'George Gordon Byron',
    'Catherine Gordon', 'Captain John Byron', 'Catherine Gordon',
    'Captain John Byron', 'Anne Isabella Milbanke', 'George Gordon Byron',
    'Catherine Gordon', 'Captain John Byron', 'Catherine Gordon',
    'Captain John Byron', 'Catherine Gordon', 'Captain John Byron',
    'Catherine Gordon', 'Captain John Byron']
>>> {'Ralph King-Milbanke': ['Augusta Ada King', 'William King-Noel'],
    'Ada King-Milbanke': ['Ralph King-Milbanke', 'Fanny Heriot'],
    'Anne Isabella Blunt': ['Augusta Ada King', 'William King-Noel', 'Anne Isabella Milbanke', 'George Gordon Byron', 'Catherine Gordon', 'Captain John Byron', 'Catherine Gordon', 'Captain John Byron'],
    'Augusta Ada King': ['Anne Isabella Milbanke', 'George Gordon Byron', 'Catherine Gordon', 'Captain John Byron', 'Catherine Gordon', 'Captain John Byron'],
    'Judith Blunt-Lytton': ['Anne Isabella Blunt', 'Wilfrid Scawen Blunt', 'Augusta Ada King', 'William King-Noel', 'Anne Isabella Milbanke', 'George Gordon Byron', 'Catherine Gordon', 'Captain John Byron', 'Catherine Gordon', 'Captain John Byron', 'Anne Isabella Milbanke', 'George Gordon Byron', 'Catherine Gordon', 'Captain John Byron', 'Catherine Gordon', 'Captain John Byron', 'Catherine Gordon', 'Captain John Byron', 'Catherine Gordon', 'Captain John Byron'],
    'Byron King-Noel': ['Augusta Ada King', 'William King-Noel'],
    'George Gordon Byron': ['Catherine Gordon', 'Captain John Byron'],
    'John Byron': ['Vice-Admiral John Byron', 'Sophia Trevannion']}

由于我们使用的result实际就是词典中的value列表的引用,改动了result,就也改动了ada_family词典,从而导致结果不正确(有很多重复项)。
修改为如下写法即可:

def ancestors(genealogy, person):
    if person in genealogy:
        parents = genealogy[person]
        result = parents
        for parent in parents:
            result = result + ancestors2(genealogy, parent)
        return result
    return []

print ancestors2(ada_family, 'Judith Blunt-Lytton')
print ada_family


>>> ['Anne Isabella Blunt', 'Wilfrid Scawen Blunt', 'Augusta Ada King', 
    'William King-Noel', 'Anne Isabella Milbanke', 'George Gordon Byron', 
    'Catherine Gordon', 'Captain John Byron']
>>> {'Ralph King-Milbanke': ['Augusta Ada King', 'William King-Noel'], 
    'Ada King-Milbanke': ['Ralph King-Milbanke', 'Fanny Heriot'], 
    'Anne Isabella Blunt': ['Augusta Ada King', 'William King-Noel'], 
    'Augusta Ada King': ['Anne Isabella Milbanke', 'George Gordon Byron'], 
    'Judith Blunt-Lytton': ['Anne Isabella Blunt', 'Wilfrid Scawen Blunt'], 
    'Byron King-Noel': ['Augusta Ada King', 'William King-Noel'], 
    'George Gordon Byron': ['Catherine Gordon', 'Captain John Byron'], 
    'John Byron': ['Vice-Admiral John Byron', 'Sophia Trevannion']}

此时就不会修改原词典内容,得到的也是正确结果。
当然,我们也可以简单的使用以下方法实现,就避免了引用带来的麻烦:

def ancestors(genealogy, person):
     if person in genealogy:
         parents = genealogy[person]
         return parents + ancestors(genealogy,parents[0]) + ancestors(genealogy,parents[1])
     return []

Example 10

>>> values = [0, 1, 2]
>>> values[1] = values
>>> values
[0, [...], 2]

预想应当是:

[0, [0, 1, 2], 2]

但结果却为何要赋值无限次?
Python 做的事情是把 values 这个标签所引用的列表对象的第二个元素指向 values 所引用的列表对象本身。执行完毕后,values 标签还是指向原来那个对象,只不过那个对象的结构发生了变化,从之前的列表 [0, 1, 2] 变成了 [0, ?, 2],而这个 ? 则是指向那个对象本身的一个引用。
可以说 Python 没有赋值,只有引用。上述操作相当于创建了一个引用自身的结构,所以导致了无限循环。
value[3]
要达到你所需要的效果,即得到 [0, [0, 1, 2], 2] 这个对象,你不能直接将 values[1] 指向 values 引用的对象本身,而是需要吧 [0, 1, 2] 这个对象「复制」一遍,得到一个新对象,再将 values[1] 指向这个复制后的对象:

values[1] = values[:]

当然这里也仅仅是浅层复制,适用于单层结构,而深层复制则需要用到copy.deepcopy()


References

[1] python 引用和对象理解 —— ShaunChen
[2] 关于Python中的引用 —— 东去春来
[3] python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域 —— xrzs

希望能够都对大家有所帮助~

猜你喜欢

转载自blog.csdn.net/Jkwwwwwwwwww/article/details/72934647
今日推荐