zoukankan      html  css  js  c++  java
  • 你相信吗??Python把数字也当做对象!@@@对象,名称绑定,引用计数

    本文学习自:http://blog.csdn.net/yockie/article/details/8474408

    1.对象

    Python中, 万物皆对象,包括12345等int常量。不信吗??用dir()命令看一看就知道

    当然了,既然他们都叫做对象。那么肯定有共同点了!所有的对象都有下面的三个特征

    a = 1
    b = 1
    print(id(a))
    print(id(b))
    print(id(1))
    
    # 结果:
    # 1634064176
    # 1634064176
    # 1634064176
    View Code

    有感觉了,int类似于string一样,是一个不可变的对象,内部可能有一个常量池的东西?

    * 都有唯一的标识码 id()

    * 都有确定的类型

    * 有内容(或称为值)

    一旦对象被创建,标识码就不能更改,对象类型也是不可更改的,内容可以改变(实际上不是内容改变而是把名字取给另一个不可变对象)

    一个名称只能对应一个对象,一个对象可能有0或1或n个名称  这个0,1,n叫引用计数

    (可变对象如dict、list 。恒定对象如int、string)

    而一个对象有可能:

    * 肯定有属性

    * 有0个或者n个方法

    * 有0个或者n个名字(引用计数为0,或者为n)

    2.名字

    我悄悄的认为,名字就是引用。不知道对不对

    对象自己不知道有多少名字,叫什么,只有名字本身知道它所指向的是个什么对象。

    Python将赋值语句认为是一个命名操作(或名称绑定)

     一个对象的引用计数可以为0或者为n,要访问对象必须通过名字(引用),Python中赋值操作就是一个命名操作(或名字绑定)。

    名字在一定的名字空间内有效。而且唯一,就是说一个名字只能对应一个对象,(在同一个名字空间内)而一个对象却可以有多个名字。

    a = 1 在Python中的含义:

    * 创建一个值为1的对象

    * a是指向该对象的名字

    3.绑定

    绑定就是用引用指向对象,会增加该对象的引用计数。

    a = a + 1  在Python中的含义:

    *  创建一个新的对象,值为 a + 1

    * a 这个名字指向新对象,新对象的引用计数 + 1 ,而a以前指向的对象引用计数 - 1

    * a以前指向的对象值没有变

    什么操作导致引用计数的变化?

    * 赋值

    * 在一个容器(list、 dict、seq)中包含该对象

                    ——将增加对象的引用计数

    * 离开当前的名字空间(该名字空间中的本地名字都会被销毁)

    * 对象的一个名字被绑定到另外一个对象

    * 对象被从包含它的容器中删除

    * 用del()方法

                    ——将减少对象的引用计数

    区别

    a = 1

    b = a

    a = 2

    print(b)  # 1  恒定对象

    ----------

    a = [1, 2, 3]

    b = a

    a[0] = 2

    print (b)  # [2,2,3]   可变对象

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

    为什么修改字典d的值不用global关键字先声明呢?

    s = 'foo'
    d = {'a':1}
    def f():
        s = 'bar'
        d['b'] = 2
    f()
    print s  # foo
    print d  # {'a': 1, 'b': 2}

    s = 'bar'这句话可以认为是
    创建新对象'bar'绑定到f函数的名字空间认为是新名称s,或者是解绑外层s的引用,绑定到新对象'bar'就产生了歧义~~~Python默认是执行第一种,
    d['b'] = 2这句话是修改可变对象,不存在创建新对象的问题,没有歧义。
    python里面一个方法内是一个名称空间,循环里面不是。旧名字->新对象,会被认为是使用新名称空间,修改可变对象如上例就是被认为是修改了老对象(新对象也没有啊)


    -----------------------------------再看下面的代码-------------
    list_a = []
    def a():
        list_a = [1]      ## 语句1
    a()
    print list_a    # []
    
    print "======================"
    
    list_b = []
    def b():
        list_b.append(1)    ## 语句2
    b()
    print list_b    # [1]

    大家可以看到为什么 语句1 不能改变 list_a 的值,而 语句2 却可以?他们的差别在哪呢?

    因为 = 创建了局部变量,而 .append() 或者 .extend() 重用了全局变量。

     

    4.函数的传参问题

     函数的参数传递也是一个名字与对象绑定的过程。(传参即增加了该对象的引用计数)而且是绑定到另外一个名字空间(即函数内部的名字空间)。

    Python所有参数传递都是引用传递,也就是传址。函数内部修改可变对象的值会影响外部

    因此在Python中,我们应该抛开传递参数这种概念,时刻牢记函数的调用参数是将对象用另外一个名字空间的名字绑定。在函数中不过是用了另外一个名字,但还是对同一个对象进行操作,。

    -------------缺省参数的问题---------

    Python的缺省参数暗藏玄机。看下面的代码:

    >>> def foo(par=[]):
    ... par.append(0)
    ... print par
    ... 
    >>> foo()                       # 第一次调用
    [0]
    >>> foo()                       # 第二次调用
    [0, 0]

    par在执行结束就销毁,两次调用结果不是应该一样吗?为什么会出现这种结果????

    问题就出在没有搞清楚缺省参数的生存周期。。。

    这都是“对象,名字,绑定”这些思想惹的祸,“万物皆对象”,这里函数foo当然也是一个对象,可以称为函数对象(与一般对象没有什么不同),先看看它的属性

    python2.x中
    >>> dir(foo) ['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__',

    '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',

    'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
    python 3.x中
    ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__',

    '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__',
    '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__',
    '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

    defaults可能与缺省参数有关:看看它的值

    foo()                       
    print(foo.__defaults__)  # 第一次调用 ([0],)
    foo()                       
    print(foo.__defaults__)  # 第二次调用([0, 0],)

    验证一下:

    def foo1(par=[] , st = "s", a = 1):
        par.append(0)
        print (par, st, a)
    
    foo1()
    print(foo1.__defaults__)  # 第一次调用 ([0], 's', 1)
    foo1()
    print(foo1.__defaults__)  # 第二次调用(([0, 0], 's', 1)

    可以看出,这个函数对象的属性__defaults__中存放了这个函数的所有缺省参数。

    在函数定义中有几个缺省参数,__defaults__中就会包括几个对象,暂且称之为缺省参数对象(如上列中的[]、“s”和1)。

    这些缺省参数对象的生命周期与函数对象相同,从函数使用def定义开始,直到其消亡(如用del)。所以即便是在这些函数没有被调用的时候,但只要定义了,缺省参数对象就会一直存在。(☆☆☆☆☆)

      前面讲过,函数调用的过程就是对象在另外一个名字空间的绑定过程。当在每次函数调用时,如果没有传递任何参数给这个缺省参数,那么这个缺省参数的名字就会绑定到在func_defaults中一个对应的缺省参数对象上。

    函数foo1内的对象par就会绑定到__defaults__中的第[0]个名称,st绑定到第[1]个,a则是第[2]个。
    所以我们看到在函数foo中出现的累加现象,就是由于par绑定到缺省参数对象上,而且它是一个可变对象(列表),par.append(0)就会每次改变这个缺省参数对象的内容。

      将函数foo改进一下,可能会更容易帮助理解:

    >>> def foo(par=[]):
    ... print id(par)                  # 查看该对象的标识码
    ... par.append(0)
    ... print par
    ...
    >>> foo.func_defaults                  # 缺省参数对象的初始值
    ([],)
    >>> id(foo.func_defaults[0])           # 查看第一个缺省参数对象的标识码
    11279792                               # 你的结果可能会不同
    >>> foo()                                
    11279792                               # 证明par绑定的对象就是第一个缺省参数对象
    [0]
    >>> foo()
    11279792                               # 依旧绑定到第一个缺省参数对象
    [0, 0]                                 # 该对象的值发生了变化
    >>> b=[1]
    >>> id(b)
    11279952
    >>> foo(b)                             # 不使用缺省参数
    11279952                               # 名字par所绑定的对象与外部名字b所绑定的是同一个对象
    [1, 0]
    >>> foo.func_defaults
    ([0, 0],)                              # 缺省参数对象还在那里,而且值并没有发生变化
    >>> foo()                    
    11279792                               # 名字par又绑定到缺省参数对象上
    ([0, 0, 0],)

    为了预防此类“问题”的发生,python建议采用下列方法:

    >>> def foo(par = None):
    ... if par is None:
    ...  par = []
    ... par.append(0)
    ... print par
    
    或者:
    
    >>> def foo(par = []):
    ... if len(args) <= 0:
    ...  par = []
    ... par.append(0)
    ... print par
    
    或者:
    
    >>> def foo(par = []):
    ... if not par:
    ...  par = []
    ... par.append(0)
    ... print par

      永远不要使用可变的默认参数,可以使用None作为哨兵,以判断是否有参数传入,如果没有,就新创建一个新的列表对象,而不是绑定到缺省
    参数对象上。

    6.总结
      * python是一种纯粹的面向对象语言。
      * 赋值语句是名字和对象的绑定过程。
      * 函数的传参是对象到不同名字空间的绑定。

  • 相关阅读:
    04:布尔表达式
    python中的operator模块
    python习题-4
    北大OJ1001
    [工具]toolbox_graph_laplacian
    [工具]toolbox_graph_normal_displayment
    [工具]toolbox_graph_isomap
    [工具]toolbox_graph_建立欧式距离邻接矩阵
    [工具]toolbox_graph_Floyd算法
    [工具]toolbox_graph_dijkstra算法
  • 原文地址:https://www.cnblogs.com/revo/p/7224287.html
Copyright © 2011-2022 走看看