当前位置 博文首页 > zy010101博客:python风格——动态类型

    zy010101博客:python风格——动态类型

    作者:[db:作者] 时间:2021-06-11 18:06

    python风格

    想要写出python风格的代码,就得理解python的特点,合理的应用python所带来的东西。
    python是一门动态类型的语言,这是由python的设计思想所决定的。在python中,我们编写对象接口而不是类型。我们关心的是一个对象能做什么,而不是关心它是什么。它是什么并不重要,重要的是它能做什么?我们希望代码能自动的适应非常多的类型,任何具有兼容性的接口对象能够正常工作。实际上这就是多态(多态:指为不同数据类型的实体提供统一的接口),这也是使用python的核心思想之一。

    动态语言

    既然我们只关心只它能做什么,那么它是什么就没有那么的重要了。因此将python设计为一门动态语言就非常合理。
    动态语言程序运行时,允许改变程序结构(例如引进新函数、删除旧函数)或变量类型。动态语言中的变量本身是没有类型的,但是变量所绑定的值是有类型的,但是这个类型检查是发生在运行期的。
    在python中,是没有类型声明的,直接给变量绑定值即可。例如:
    s = "123"
    一个有编写过静态类型语言经验的程序员,可能会想着如何在python判断数据类型是什么?但是这样的思路在使用python的时候是不合适的,在代码中检测数据类型,实际上会破坏python的灵活性。虽然python中的type()函数提供了类型判断的功能,但是不建议使用。下面看一个例子,它就体现了python的灵活性。

    s = "123"
    num = 123
    
    print(type(s))
    print(type(num))
    
    s = 123
    num = "123"
    print(type(s))
    print(type(num))
    

    程序执行结果如下:

    <class 'str'>
    <class 'int'>
    <class 'int'>
    <class 'str'>
    

    变量,对象和表达式

    作为一名有经验的C程序员,在编写python程序的时候,要习惯python中变量不一样的地方。

    1. 变量在第一次赋值的时候会被创建出来;
    2. 变量在使用之前必须被赋值(必须存在这个变量);
    3. 变量引用的是对象;
    4. 变量没有数据类型,有数据类型的是对象。
    5. 变量在表达式中出现的时候,它会被其所引用的对象的值所取代。

    总结来说,python里的变量实际上就是一个void *指针(通用类型指针),这个指针指向的是对象。只不过我们在使用的时候不需要解引用。而且这个指针指向的对象还可以改变。(这和C++的引用是完全不同的)
    对象知道自己的类型,每个对象都包含一个头部信息,其中的类型标志符标记了这个对象的类型,其中的引用计数器决定何时回收这个对象。

    垃圾回收

    在python中,每当一个变量名被赋予一个新对象,如果原来的对象没有被其他变量或者对象引用,那么之前的那个对象所占用的内存空间就会被回收。这种自动回收对象空间的技术叫作垃圾回收。得益于垃圾回收机制,python程序员无需手动管理内存,这使得python程序员的工作得到了很大的简化。
    在python的实现中,每个对象的头部都有一个引用计数器,它记录了该对象的引用数目,一旦计数器被设为0,那么这个对象的内存空间就会被回收。
    在python这里有个问题是集合这样的数据类型本身是一个容器,他可以容纳任何数据类型。所有就会有下面这样的代码出现。

    >>> a = [1,2,3]
    >>> a.append(a)
    

    这个时候,循环引用就出现了,这样的对象的引用计数器不会为0,必须进行特别处理。
    不过这点不用我们担心,python默认是可以回收循环引用的对象。

    共享引用

    共享引用在python里是指多个变量引用了同一个对象。在看例子之前,我们首先介绍一个函数id()。
    id()返回的是对象的“身份证号”,唯一且不变。一个对象的id值在CPython解释器里就代表它在内存中的地址。
    下面,我们来看一个例子:

    >>> a = 1
    >>> b = 1
    >>> id(a)
    9788992
    >>> id(b)
    9788992
    

    这个例子中,变量a和变量b所引用的对象的id是一致的,这就是共享引用。注意,a和b本身并没有什么关联。当然了,在python里变量之间是不可能有引用关系的。下面改变b变量所引用的对象,效果如下:

    >>> b = '123'
    >>> id(a)
    9788992
    >>> id(b)
    140352439347888
    

    这就是python的特殊之处,变量本身不是某个内存地址的标签,变量本身应当是类似void *类型的指针。给变量赋新值,就是改变变量所指向的对象,而不是改变原来对象。事实上,刚才的b指向的对象3是整形,而整形是不可变类型,你根本没有办法改变它。
    需要注意的是对可变对象的共享引用,这可能会造成你预期之外的结果。例如:

    >>> list1 = [1,2,3]
    >>> list2 = list1
    >>> id(list1)
    140352439347392
    >>> id(list2)
    140352439347392
    >>> list2[0] = 0
    >>> list1
    [0, 2, 3]
    >>> list2
    [0, 2, 3]
    >>> id(list2)
    140352439347392
    >>> id(list1)
    140352439347392
    

    可以看到,list1和list2共享引用,但是由于列表是可变对象,可以改变其中的值。但是这并没有改变它们是共享引用。所以,list1和list2的结果都发生了改变。这种效果对于习惯了C/C++编程的人而言,一开始是不太习惯的,经历过几次这样的错误就好了。如果你真的想让list1和list2指向的对象不同,那么你可以使用复制对象来解决这个问题。例如:

    >>> list1 = [1,2,3]
    >>> list2 = list1.copy()
    >>> id(list2)
    140352438351616
    >>> id(list1)
    140352439347392
    

    这样做了对象复制之后,list1和list2就会指向不同的对象。复制一个对象还可以将原来的对象传入相应的构造函数中,例如:

    >>> list1 = [1,2,3]
    >>> list2 = list(list1)
    >>> id(list1)
    140352438324288
    >>> id(list2)
    140352438560704
    

    字典和列表也可以使用copy或者构造函数方式来复制原来的对象。复制一个对象方法有很多,这不是重点,重点是python的可变对象的共享引用是较为特殊,尤其是对习惯了C/C++的人而言。

    共享引用和相等

    首先,python解释器有时候并不会马上回收一个不再使用的对象,python会缓存并复用小的整数和小的字符串,缓存机制并不会影响代码。但是大多数的对象都是在不被引用的时候立即回收的。
    基于python的引用模型,python中有两种方法去检测是否相等。例如:

    >>> list1 = [1,2,3]
    >>> list2 = list1
    >>> list1 == list2    #比较值是否相等
    True
    >>> list1 is list2    #检测是否是同一个对象
    True
    

    在这里,由于list1和list2引用的是同一个对象,所以值是相同的。下面的另一个例子则说明了不同之处。

    >>> list1 = [1,2,3]
    >>> list2 = [1,2,3]
    >>> list1 == list2    #比较值是否相等
    True
    >>> list1 is list2    #检测是否是同一个对象
    False
    

    这个例子中,list1和list2引用的对象是不同的(因为python只会缓存并复用小的整数和小的字符串,列表并不会被缓存),但是两个对象的值相同的。最后,我们再来一个说明缓存效果会带来的不同之处。

    >>> a = 1
    >>> b = 1
    >>> a == b
    True
    >>> a is b
    True
    
    • ==:测试两个被引用的对象是否具有相同的值
    • is:测试两个变量是否引用的是同一个对象

    因此,is可以作为测试共享应用的一种方式。
    python中sys模块中的getrefcount()方法可以返回对象的引用次数,以数字对象1为例,其返回的引用次数高达199次。

    >>> import sys
    >>> sys.getrefcount(1)
    199
    

    这种方式也是python为了其执行速度而采用的众多优化方式中的一种。

    python的这个引用,赋值模型是唯一的,它具有良好的一致性。作为比较对象的C++语言,它的语法一致性奇差。