美文网首页
我眼中一个好的Pythoneer应该具备的品质(一)

我眼中一个好的Pythoneer应该具备的品质(一)

作者: 王大吉 | 来源:发表于2020-02-03 23:46 被阅读0次

    知道python最常见的解释器有哪些。

    • CPython
      当从Python官方网站下载并安装好Python2.7后,就直接获得了一个官方版本的解释器:Cpython,这个解释器是用C语言开发的,所以叫CPython,在命名行下运行python,就是启动CPython解释器,CPython是使用最广的Python解释器。
    • IPython
      IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的,好比很多国产浏览器虽然外观不同,但内核其实是调用了IE。
    • PyPy
      PyPy是另一个Python解释器,它的目标是执行速度,PyPy采用JIT技术,对Python代码进行动态编译,所以可以显著提高Python代码的执行速度。JythonJython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。
    • IronPython
      IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

    在Python的解释器中,使用广泛的是CPython,对于Python的编译,除了可以采用以上解释器进行编译外,技术高超的开发者还可以按照自己的需求自行编写Python解释器来执行Python代码,十分的方便!

    知道python中的函数式编程

    在 Python 中,函数是「头等公民」(first-class)。也就是说,函数与其他数据类型(如 int)处于平等地位。因而,我们可以将函数赋值给变量,也可以将其作为参数传入其他函数,将它们存储在其他数据结构(如 dicts)中,并将它们作为其他函数的返回值。

    知道GIL的限制以及与多线程的关系。

    在Python多线程下,每个线程的执行方式:
    1.获取GIL
    2.执行代码直到sleep或者是python虚拟机将其挂起。
    3.释放GIL

    可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。

    在python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100(ticks可以看作是python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过 sys.setcheckinterval 来调整),进行释放。而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。

    那么是不是python的多线程就完全没用了呢?在这里我们进行分类讨论:
    1、CPU密集型代码(各种循环处理、计数等等),在这种情况下,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
    2、IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。

    而在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。

    多核多线程比单核多线程更差,原因是单核下多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低

    知道python的命名空间查找规则(LEGB)。

    LEGB含义解释:
    L-Local(function);函数内的名字空间
    E-Enclosing function locals;外部嵌套函数的名字空间(例如closure)
    G-Global(module);函数定义所在模块(文件)的名字空间
    B-Builtin(Python);Python内置模块的名字空间

    知道python多继承的查找规则(MRO)。

    查看MRO
    新式类
    ClassA.mro()(py3 使用) /ClassA.mro(py2 使用)
    经典类
    Inspect.getmro(A)

    知道python 2.x和3.x的主要差异。

    1. print
      在进行程序调试时用得最多的语句可能就是 print,在 Python 2 中,print 是一条语句,而 Python3 中作为函数存在。有人可能就有疑问了,我在 Python2 中明明也看到当函数使用:

      # py2
      print("hello")  # 等价 print  ("hello")
      
      #py3
      print("hello")
      

      然而,你看到的只是表象,那么上面两个表达式有什么区别?从输出结果来看是一样的,但本质上,前者是把 ("hello")当作一个整体,而后者 print()是个函数,接收字符串作为参数。# py2

      >>> print("hello", "world")
      ('hello', 'world')
      
      # py3 
      >>> print("hello", "world")
      hello world
      

      这个例子更明显了,在 py2 中,print语句后面接的是一个元组对象,而在 py3 中,print 函数可以接收多个位置参数。
      如果希望在 Python2 中 把 print 当函数使用,那么可以导入 future 模块 中的 print_function

      # py2
      >>> print("hello", "world")
      ('hello', 'world') 
      >>> 
      >>> from __future__ import print_function
      >>> print("hello", "world")  
      hello world
      
    2. 编码
      Python2 的默认编码是 asscii,这也是导致 Python2 中经常遇到编码问题的原因之一,至于是为什么会使用 asscii 作为默认编码,原因在于 Python这门语言诞生的时候还没出现 Unicode。
      Python 3 默认采用了 UTF-8 作为默认编码,因此你不再需要在文件顶部写 # coding=utf-8 了。

      # py2
      >>> sys.getdefaultencoding()
      'ascii'
      # py3
      >>> sys.getdefaultencoding()
      'utf-8'
      

      网上不少文章说通过修改默认编码格式来解决 Python2 的编码问题,其实这是个大坑,不要这么干。

    3. 字符串
      字符串是最大的变化之一,这个变化使得编码问题降到了最低可能。在 Python2 中,字符串有两个类型,一个是 unicode,一个是 str,前者表示文本字符串,后者表示字节序列,不过两者并没有明显的界限,开发者也感觉很混乱,不明白编码错误的原因,不过在 Python3 中两者做了严格区分,分别用 str 表示字符串,byte 表示字节序列,任何需要写入文本或者网络传输的数据都只接收字节序列,这就从源头上阻止了编码错误的问题。

    4. True False
      True 和 False 在 Python2 中是两个全局变量(名字),在数值上分别对应 1 和 0,既然是变量,那么他们就可以指向其它对象,例如:

      >>> True = False
      >>> True
      False
      >>> True is False
      True
      >>> False = "x"
      >>> False
      'x'
      >>> if False:
      ...     print("?")
      ... 
      

      ?
      显然,上面的代码违背了 Python 的设计哲学 Explicit is better than implicit.。
      而 Python3 修正了这个缺陷,True 和 False 变为两个关键字,永远指向两个固定的对象,不允许再被重新赋值。

      >>> True = 1
        File "<stdin>", line 1
      SyntaxError: can't assign to keyword
      
    5. 迭代器
      在 Python2 中很多返回列表对象的内置函数和方法在 Python 3 都改成了返回类似于迭代器的对象,因为迭代器的惰性加载特性使得操作大数据更有效率。Python2 中的 range 和 xrange 函数合并成了 range,如果同时兼容2和3,可以这样:

      try:
          range = xrange
      except:
          pass
      

      另外,字典对象的 dict.keys()、dict.values() 方法都不再返回列表,而是以一个类似迭代器的 "view" 对象返回。高阶函数 map、filter、zip 返回的也都不是列表对象了。Python2的迭代器必须实现 next 方法,而 Python3 改成了 __next__

    1. nonlocal
      我们都知道在Python2中可以在函数里面可以用关键字 global 声明某个变量为全局变量,但是在嵌套函数中,想要给一个变量声明为非局部变量是没法实现的,在Pyhon3,新增了关键字 nonlcoal,使得非局部变量成为可能。

    知道property的含义以及其描述器实现。

    一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法

    # ############### 定义 ###############
    class Foo:
        def func(self):
            pass
    
        # 定义property属性
        @property
        def prop(self):
            pass
    
    # ############### 调用 ###############
    foo_obj = Foo()
    foo_obj.func()  # 调用实例方法
    foo_obj.prop  # 调用property属性
    

    property属性的定义和调用要注意一下几点:

    • 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
    • 调用时,无需括号

    对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据这个分页的功能包括:根据用户请求的当前页和总数据条数计算出 m 和 n根据m 和 n 去数据库中请求数据

    # ############### 定义 ###############
    class Pager:
        def __init__(self, current_page):
            # 用户当前请求的页码(第一页、第二页...)
            self.current_page = current_page
            # 每页默认显示10条数据
            self.per_items = 10 
    
        @property
        def start(self):
            val = (self.current_page - 1) * self.per_items
            return val
    
        @property
        def end(self):
            val = self.current_page * self.per_items
            return val
    
    # ############### 调用 ###############
    p = Pager(1)
    p.start  # 就是起始值,即:m
    p.end  # 就是结束值,即:n
    

    从上述可见,Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。
    由此可见,property的作用就是 将一个属性的操作方法封装为一个属性,用户用起来就和操作普通属性完全一致,非常简单。

    property属性的有两种方式

    • 装饰器 即:在方法上应用装饰器

    • 类属性 即:在类中定义值为property对象的类属性

    装饰器方式

    在类的实例方法上应用@property装饰器

    Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富。( 如果类继object,那么该类是新式类 ,Python3中默认所有类为新式类)

    • 经典类,具有一种@property装饰器
    class Goods:
        @property
        def price(self):
            return "laowang"
    # ############### 调用 ###############
    obj = Goods()
    result = obj.price  # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
    print(result)
    
    • 新式类,具有三种@property装饰器
    # ############### 定义 ###############
    class Goods:
        """python3中默认继承object类
            以python2、3执行此程序的结果不同,因为只有在python3中才有@xxx.setter  @xxx.deleter
        """
        @property
        def price(self):
            print('@property')
    
        @price.setter
        def price(self, value):
            print('@price.setter')
    
        @price.deleter
        def price(self):
            print('@price.deleter')
    
    # ############### 调用 ###############
    obj = Goods()
    obj.price          # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
    obj.price = 123    # 自动执行 @price.setter 修饰的 price 方法,并将  123 赋值给方法的参数
    del obj.price      # 自动执行 @price.deleter 修饰的 price 方法
    

    注意
    经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

    class Goods(object):
    
        def __init__(self):
            # 原价
            self.original_price = 100
            # 折扣
            self.discount = 0.8
    
        @property
        def price(self):
            # 实际价格 = 原价 * 折扣
            new_price = self.original_price * self.discount
            return new_price
    
        @price.setter
        def price(self, value):
            self.original_price = value
    
        @price.deleter
        def price(self):
            del self.original_price
    
    obj = Goods()
    obj.price         # 获取商品价格
    obj.price = 200   # 修改商品原价
    del obj.price     # 删除商品原价
    

    类属性方式,创建值为property对象的类属性

    当使用类属性的方式创建property属性时,经典类和新式类无区别

    class Foo:
        def get_bar(self):
            return 'laotie'
    
        BAR = property(get_bar)
    
    obj = Foo()
    reuslt = obj.BAR  # 自动调用get_bar方法,并获取方法的返回值
    print(reuslt)
    

    property方法中有个四个参数

    • 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
    • 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
    • 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息
    #coding=utf-8
    class Foo(object):
        def get_bar(self):
            print("getter...")
            return 'laowang'
    
        def set_bar(self, value): 
            """必须两个参数"""
            print("setter...")
            return 'set value' + value
    
        def del_bar(self):
            print("deleter...")
            return 'laowang'
    
        BAR = property(get_bar, set_bar, del_bar, "description...")
    
    obj = Foo()
    
    obj.BAR  # 自动调用第一个参数中定义的方法:get_bar
    obj.BAR = "alex"  # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
    desc = Foo.BAR.__doc__  # 自动获取第四个参数中设置的值:description...
    print(desc)
    del obj.BAR  # 自动调用第三个参数中定义的方法:del_bar方法
    

    由于类属性方式创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

    class Goods(object):
    
        def __init__(self):
            # 原价
            self.original_price = 100
            # 折扣
            self.discount = 0.8
    
        def get_price(self):
            # 实际价格 = 原价 * 折扣
            new_price = self.original_price * self.discount
            return new_price
    
        def set_price(self, value):
            self.original_price = value
    
        def del_price(self):
            del self.original_price
    
        PRICE = property(get_price, set_price, del_price, '价格属性描述...')
    
    obj = Goods()
    obj.PRICE         # 获取商品价格
    obj.PRICE = 200   # 修改商品原价
    del obj.PRICE     # 删除商品原价
    

    总结

    • 定义property属性共有两种方式,分别是【装饰器】和【类属性】,而【装饰器】方式针对经- - 典类和新式类又有所不同。
    • 通过使用property属性,能够简化调用者在获取数据的流程
      x = property(get_x, set_x, del_x, "doc")

    知道python中dict的底层实现。

    python2.7之前字典是一个hash映射

    # 给字典添加一个值,key为hello,value为word
    my_dict['hello'] = 'word'
    
    # 假设是一个空列表,hash表初始如下
    enteies = [
        ['--', '--', '--'],
        ['--', '--', '--'],
        ['--', '--', '--'],
        ['--', '--', '--'],
        ['--', '--', '--'],
    ]hash_value = hash('hello')  # 假设值为 12343543 注:以下计算值不等于实际值,仅为演示使用
    index = hash_value & ( len(enteies) - 1)  # 假设index值计算后等于3,具体的hash算法本文不做介绍
    
    # 下面会将值存在enteies中
    enteies = [
        ['--', '--', '--'],
        ['--', '--', '--'],
        ['--', '--', '--'],
        [12343543, 'hello', 'word'],  # index=3
        ['--', '--', '--'],
    ]
    
    # 我们继续向字典中添加值
    my_dict['color'] = 'green'
    
    hash_value = hash('color')  # 假设值为 同样为12343543
    index = hash_value & ( len(enteies) - 1)  # 假设index值计算后同样等于3
    
    # 下面会将值存在enteies中
    enteies = [
        ['--', '--', '--'],
        ['--', '--', '--'],
        ['--', '--', '--'],
        [12343543, 'hello', 'word'],  # 由于index=3的位置已经被占用,且key不一样,所以判定为hash冲突,继续向下寻找
        [12343543, 'color', 'green'],  # 找到空余位置,则保存
    ]
    

    python2.7之后字典实现里新增加了一个index列表用来维护插入顺序。

    # 给字典添加一个值,key为hello,value为word
    my_dict['hello'] = 'word'
    
    # 假设是一个空列表,hash表初始如下
    indices = [None, None, None, None, None, None]
    enteies = []
    
    hash_value = hash('hello')  # 假设值为 12343543
    index = hash_value & ( len(indices) - 1)  # 假设index值计算后等于3,具体的hash算法本文不做介绍
    
    # 会找到indices的index为3的位置,并插入enteies的长度
    indices = [None, None, None, 0, None, None]
    # 此时enteies会插入第一个元素
    enteies = [
        [12343543, 'hello', 'word']
    ]
    
    # 我们继续向字典中添加值
    my_dict['haimeimei'] = 'lihua'
    
    hash_value = hash('haimeimei')  # 假设值为 34323545
    index = hash_value & ( len(indices) - 1)  # 假设index值计算后同样等于 0
    
    # 会找到indices的index为0的位置,并插入enteies的长度
    indices = [1, None, None, 0, None, None]
    # 此时enteies会插入第一个元素
    enteies = [
        [12343543, 'hello', 'word'],
        [34323545, 'haimeimei', 'lihua']
    ]
    

    知道__slots__的含义以及使用场景。

    正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法。但是,如果我们想要限制实例的属性怎么办?为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:

    >>> class Student:
    ...     __slots__ = ('name', 'age')
    ...
    >>> s = Student()
    >>> s.name = 'digg'
    >>> s.age = '19'
    >>> s.score = 99
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute 'score'
    

    由于’score’没有被放到__dict__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。
    使用__dict__要注意,__dict__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

    >>> class GraduateStudent(Student):
    ...     pass
    ...
    >>> g = GraduateStudent()
    >>> g.score = 9999
    

    除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
    __slots__ 存在的真正原因是用于优化,否则我们是以__dict__来存储实例属性,如果我们涉及到很多需要处理的数据,使用元组来存储当然会节省时间和内存。
    如果我们还是想要有可以随意添加实例属性,那么把 __dict__放入 __slots__ 中既可,实例会在元组中保存各个实例的属性,此外还支持动态创建属性,这些属性存储在常规的__dict__ 中。优化完全就不见了。o(╯□╰)o比如这样:

    >>> class Student:
        __slots__ = ('name', 'age', '__dict__')
    
    >>> s.score = 99
    >>> s.score
    99
    

    知道如何定义和使用元类,了解其使用场景。

    fuck 两句话轻松掌握 Python 最难知识点——元类 - 楚阳的文章 - 知乎
    https://zhuanlan.zhihu.com/p/60461261

    知道python中的多进程和多线程模型,知道多进程和多线程下间的通信实现。

    深入Python多进程编程基础——图文版 - 老钱的文章 - 知乎
    https://zhuanlan.zhihu.com/p/37370577
    深入Python进程间通信原理——图文版 - 老钱的文章 - 知乎
    https://zhuanlan.zhihu.com/p/37370601

    知道深拷贝和浅拷贝在python中的实现方式。

    所谓浅拷贝就是对引用的拷贝,所谓深拷贝就是对对象的资源的拷贝。

    • 浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素( 新瓶装旧酒 )。

    • 深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说( 新瓶装新酒 )。

    知道python的调试工具,知道unittest和doctest的使用。

    pdb是Python自带的一个库,为Python程序提供了一种交互式的源代码调试功能,包含了现代调试器应有的功能,包括设置断点、单步调试、查看源码、查看程序堆栈等。

    如果读者具有C或C++程序语言背景,则一定听说过gdb。gdb是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。如果读者之前使用过gdb,那么,几乎不用学习就可以直接使用pdb。pdb和gdb保持了一样的用法,这样可以降低工程师的学习负担和Python调试的难度,pdb提供的部分调试命令见下表。
    pdb命令行:

        1)进入命令行Debug模式,python -m pdb xxx.py
    
        2)h:(help)帮助
    
        3)w:(where)打印当前执行堆栈
    
        4)d:(down)执行跳转到在当前堆栈的深一层(个人没觉得有什么用处)
    
        5)u:(up)执行跳转到当前堆栈的上一层
    
        6)b:(break)添加断点
    
                     b 列出当前所有断点,和断点执行到统计次数
    
                     b line_no:当前脚本的line_no行添加断点
    
                     b filename:line_no:脚本filename的line_no行添加断点
    
                     b function:在函数function的第一条可执行语句处添加断点
    
        7)tbreak:(temporary break)临时断点
    
                     在第一次执行到这个断点之后,就自动删除这个断点,用法和b一样
    
        8)cl:(clear)清除断点
    
                    cl 清除所有断点
    
                    cl bpnumber1 bpnumber2... 清除断点号为bpnumber1,bpnumber2...的断点
    
                    cl lineno 清除当前脚本lineno行的断点
    
                    cl filename:line_no 清除脚本filename的line_no行的断点
    
        9)disable:停用断点,参数为bpnumber,和cl的区别是,断点依然存在,只是不启用
    
        10)enable:激活断点,参数为bpnumber
    
        11)s:(step)执行下一条命令
    
                    如果本句是函数调用,则s会执行到函数的第一句
    
        12)n:(next)执行下一条语句
    
                    如果本句是函数调用,则执行函数,接着执行当前执行语句的下一条。
    
        13)r:(return)执行当前运行函数到结束
    
        14)c:(continue)继续执行,直到遇到下一条断点
    
        15)l:(list)列出源码
    
                     l 列出当前执行语句周围11条代码
    
                     l first 列出first行周围11条代码
    
                     l first second 列出first--second范围的代码,如果second<first,second将被解析为行数
    
        16)a:(args)列出当前执行函数的函数
    
        17)p expression:(print)输出expression的值
    
        18)pp expression:好看一点的p expression
    
        19)run:重新启动debug,相当于restart
    
        20)q:(quit)退出debug
    
        21)j lineno:(jump)设置下条执行的语句函数
    
                    只能在堆栈的最底层跳转,向后重新执行,向前可直接执行到行号
    
        22)unt:(until)执行到下一行(跳出循环),或者当前堆栈结束
    
        23)condition bpnumber conditon,给断点设置条件,当参数condition返回True的时候bpnumber断点有效,否则bpnumber断点无效
    

    有两种不同的方法启动Python调试器,一种直接在命令行参数指定使用pdb模块启动Python文件,如下所示:python -m pdb test_pdb.py另一种方法是在Python代码中,调用pdb模块的set_trace方法设置一个断点,当程序运行自此时,将会暂停执行并打开pdb调试器。

    #/usr/bin/python
    from __future__ import print_function
    import pdb
    
    def sum_nums(n):
        s=0
        for i in range(n):
            pdb.set_trace()
            s += i
            print(s)
    
    if __name__ == '__main__':
        sum_nums(5)
    

    两种方法并没有什么质的区别,选择使用哪一种方式主要取决于应用场景,如果程序文件较短,可以通过命令行参数的方式启动Python调试器;如果程序文件较大,则可以在需要调试的地方调用set_trace方法设置断点。无论哪一种方式,都会启动Python调试器,前者将在Python源码的第一行启动Python调试器,后者会在执行到pdb.set_trace()时启动调试器。启动Python调试器以后,就可以使用前面的调试命令进行调试,例如,下面这段调试代码,我们先通过bt命令查看了当前函数的调用堆栈,然后使用list命令查看了我们的Python代码,之后使用p命令打印了变量当前的取值,最后使用n执行下一行Python代码。

    lmx@host1:~/temp$ python test_pdb.py
    > test_pdb.py(9)sum_nums()
    -> s += i
    (Pdb) bt
      test_pdb.py(13)<module>()
    -> sum_nums(5)
    > test_pdb.py(9)sum_nums()
    -> s += i
    (Pdb) list
      4
      5     def sum_nums(n):
      6         s=0
      7         for i in range(n):
      8             pdb.set_trace()
      9  ->         s += i
     10             print(s)
     11
     12     if __name__ == '__main__':
     13         sum_nums(5)
    [EOF]
    (Pdb) p s
    0
    (Pdb) p i
    0
    (Pdb) n
    > test_pdb.py(10)sum_nums()
    -> print(s)
    

    相关文章

      网友评论

          本文标题:我眼中一个好的Pythoneer应该具备的品质(一)

          本文链接:https://www.haomeiwen.com/subject/ohlfxhtx.html