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是一种纯粹的面向对象语言。
      * 赋值语句是名字和对象的绑定过程。
      * 函数的传参是对象到不同名字空间的绑定。

  • 相关阅读:
    Codeforces Round #344 (Div. 2) C. Report 其他
    Codeforces Round #344 (Div. 2) B. Print Check 水题
    Codeforces Round #344 (Div. 2) A. Interview 水题
    8VC Venture Cup 2016
    CDOJ 1280 772002画马尾 每周一题 div1 矩阵快速幂 中二版
    CDOJ 1280 772002画马尾 每周一题 div1 矩阵快速幂
    CDOJ 1279 班委选举 每周一题 div2 暴力
    每周算法讲堂 快速幂
    8VC Venture Cup 2016
    Educational Codeforces Round 9 F. Magic Matrix 最小生成树
  • 原文地址:https://www.cnblogs.com/revo/p/7224287.html
Copyright © 2011-2022 走看看