python 如何边改代码边调试_『Python 工匠』如何快速对python代码进行调试和Debug?...

对于一个天天与代码打交道的算法程序猿,不免会时时刻刻去调试你的程序代码,并且与Debug做斗争,是的,这个就是程序猿的日常工作,接下来我会说下我的理解,如果有写的不周到的地方,还望各位给我留言评论,如果觉得写的还可以,那还麻烦各位给我点个关注给个赞。好啦,废话说的有点多了,下面我们正式开始进入正题~~~

代码调试

对于一般我们刚码的代码,我们一般都会进行调试,看程序每一步执行的结果,以便我们能保证是按照我们的想法进行下去的,当然这个前提就是我们的代码要写的简洁明了,至于如何写好代码,参考我的上一篇文章:『Python 工匠』如何让你的python代码更优雅?,接下来就教大家如何进行调试python代码(笔者推荐JetBrains公司旗下的IDE——Pycharm):首先定位到我们需要调试的地方,如图中,我要从152行这里开始进行调试

那么我们就到鼠标挪到最左边152行处,单击鼠标左键,然后就出现一个图中出现的红色的断点符号;

2:接下来我们开始进行调试了,按照图中第2处,我们单击 Debug 'XXX',然后程序就开始进入到Debug模式了,出现如下图所示的操作,代表我们现在即将进行执行第152行这行代码的操作了(记住:此时还未执行152行代码);

3:然后我们通过快捷键F8进行一步步执行,你通过下面的菜单栏便可以看到每一步执行的结果,如果你想对其中任何一处代码或者变量,你想执行其结果,我们可以通过如下方式,上图

按照操作,第一步:按照图中1,右击鼠标,出现下拉菜单,然后单击图中2:Evaluate Expression,出现如下图所示的对话框,

点击Evaluate,便可以看到你选中的这处代码执行的结果;

4:有同学想问,如果按下F8,感觉执行得太慢了,比如我想直接到161行代码这里开始执行,那如何做呢,其实也很简单,只需要将152行前面的断点符号去掉,然后再161行这行打上断点符号,然后按下快捷键Shift+F8,就可以看到效果了,上图

5:以上就是python调试代码的简单操作了,

代码Debug

我介绍代码调试就是为了代码Debug来铺垫的,因为我们很多时候Debug就是通过代码调试一步步来发现其中的问题的,好了,我首先来说下Debug大致的的流程,

1:定位错误——代码出错我们首先看到是在哪一行报错,也就是定位错误源

2:错误类型——找到错误位置了,然后我们来看下错误是哪种类型,以便于我们缩小排查的范围

3:修改错误——定位到错误具体原因了,接下来我们变知道如何进行修改了,当然我们大多数时候都是通过去网上去搜这个错误原因,这里要说下,国内的网站大多数可能不能满足你的话,我建议你去国外的,比如stackflow,之类,我在专栏中Debug网址也介绍过,

接下来我贴一些大家平时常见的错误以及它的原因:

常见错误1:错误地将表达式作为函数的默认参数

在Python中,我们可以为函数的某个参数设置默认值,使该参数成为可选参数。虽然这是一个很好的语言特性,但是当默认值是可变类型时,也会导致一些令人困惑的情况。我们来看看下面这个Python函数定义:

>>> def foo(bar=[]): # bar是可选参数,如果没有提供bar的值,则默认为[],

... bar.append("baz") # 但是稍后我们会看到这行代码会出现问题。

... return bar

Python程序员常犯的一个错误,就是想当然地认为:在每次调用函数时,如果没有为可选参数传入值,那么这个可选参数就会被设置为指定的默认值。在上面的代码中,你们可能觉得重复调用foo()函数应该会一直返回'baz',因为你们默认每次foo()函数执行时(没有指定bar变量的值),bar变量都被设置为[](也就是,一个新的空列表)。

但是,实际运行结果却是这样的:

>>> foo()

["baz"]>>> foo()

["baz", "baz"]>>> foo()

["baz", "baz", "baz"]

很奇怪吧?为什么每次调用foo()函数时,都会把"baz"这个默认值添加到已有的列表中,而不是重新创建一个新的空列表呢?

答案就是,可选参数默认值的设置在Python中只会被执行一次,也就是定义该函数的时候。因此,只有当foo()函数被定义时,bar参数才会被初始化为默认值(也就是,一个空列表),但是之后每次foo()函数被调用时,都会继续使用bar参数原先初始化生成的那个列表。

当然,一个常见的解决办法就是:

>>> def foo(bar=None):

... if bar is None: # or if not bar:

... bar = []

... bar.append("baz")... return bar

...

>>> foo()

["baz"]>>> foo()

["baz"]>>> foo()

["baz"]

常见问题2:错误地使用类变量

我们来看下面这个例子:

>>> class A(object):

... x = 1

...

>>> class B(A):

... pass

...

>>> class C(A):

... pass

...

>>> print A.x, B.x, C.x

1 1 1

这个结果很正常。

>>> B.x = 2

>>> print A.x, B.x, C.x

1 2 1

嗯,结果和预计的一样。

>>> A.x = 3

>>> print A.x, B.x, C.x

3 2 3

在Python语言中,类变量是以字典的形式进行处理的,并且遵循方法解析顺序(Method Resolution Order,MRO)。因此,在上面的代码中,由于类C中并没有x这个属性,解释器将会查找它的基类(base class,尽管Python支持多重继承,但是在这个例子中,C的基类只有A)。换句话说,C并不没有独立于A、真正属于自己的x属性。所以,引用C.x实际上就是引用了A.x。如果没有处理好这里的关系,就会导致示例中出现的这个问题。

常见错误3:错误地指定异常代码块(exception block)的参数

请看下面这段代码:

>>> try:

... l = ["a", "b"]... int(l[2])

... except ValueError, IndexError: # To catch both exceptions, right?

... pass

...

Traceback (most recent call last):

File "", line 3, in IndexError: list index out of range

这段代码的问题在于,except语句并不支持以这种方式指定异常。在Python 2.x中,需要使用变量e将异常绑定至可选的第二个参数中,才能进一步查看异常的情况。因此,在上述代码中,except语句并没有捕获IndexError异常;而是将出现的异常绑定到了一个名为IndexError的参数中。

要想在except语句中正确地捕获多个异常,则应将第一个参数指定为元组,然后在元组中写下希望捕获的异常类型。另外,为了提高可移植性,请使用as关键词,Python 2和Python 3均支持这种用法。

>>> try:

... l = ["a", "b"]... int(l[2])

... except (ValueError, IndexError) as e:

... pass

...

>>>

常见错误4:错误理解Python中的变量名解析

Python中的变量名解析遵循所谓的LEGB原则,也就是“L:本地作用域;E:上一层结构中def或lambda的本地作用域;G:全局作用域;B:内置作用域”(Local,Enclosing,Global,Builtin),按顺序查找。看上去是不是很简单?不过,事实上这个原则的生效方式还是有着一些特殊之处。说到这点,我们就不得不提下面这个常见的Python编程错误。请看下面的代码:

>>> x = 10

>>> def foo():

... x += 1

... print x

...

>>> foo()

Traceback (most recent call last):

File "", line 1, in File "", line 2, in fooUnboundLocalError: local variable 'x' referenced before assignment

出了什么问题?

上述错误的出现,是因为当你在某个作用域内为变量赋值时,该变量被Python解释器自动视作该作用域的本地变量,并会取代任何上一层作用域中相同名称的变量。

正是因为这样,才会出现一开始好好的代码,在某个函数内部添加了一个赋值语句之后却出现了UnboundLocalError,难怪会让许多人吃惊。

在使用列表时,Python程序员尤其容易陷入这个圈套。

请看下面这个代码示例:

>>> lst = [1, 2, 3]

>>> def foo1():

... lst.append(5) # 这里没问题

...

>>> foo1()

>>> lst

[1, 2, 3, 5]

>>> lst = [1, 2, 3]

>>> def foo2():

... lst += [5] # ... 但这里就不对了!

...

>>> foo2()

Traceback (most recent call last):

File "", line 1, in File "", line 2, in fooUnboundLocalError: local variable 'lst' referenced before assignment

呃?为什么函数foo1运行正常,foo2却出现了错误?

答案与上一个示例相同,但是却更难捉摸清楚。foo1函数并没有为lst变量进行赋值,但是foo2却有赋值。我们知道,lst += [5]只是lst = lst + [5]的简写,从中我们就可以看出,foo2函数在尝试为lst赋值(因此,被Python解释器认为是函数本地作用域的变量)。但是,我们希望为lst赋的值却又是基于lst变量本身(这时,也被认为是函数本地作用域内的变量),也就是说该变量还没有被定义。这才出现了错误。

一、引用

使用到的全局变量只是作为引用,不在函数中修改它的值的话,不需要加global关键字。如:

#! /usr/bin/python

a = 1

b = [2, 3]

def func():

if a == 1:

print("a: %d" %a) for i in range(4):

if i in b:

print("%d in list b" %i) else:

print("%d not in list b" %i)

if __name__ == '__main__':

func()

输出结果:

可以看出,无论是列表还是变量,都是可以直接引用的。

二、修改

使用到的全局变量,需要在函数中修改的话,就涉及到歧义问题,如:

#! /usr/bin/python

a = 1

b = [2, 3]

def func():

a = 2

print "in func a:", a b[0] = 1

print "in func b:", b

if __name__ == '__main__':

print "before func a:", a print "before func b:", b func()

print "after func a:", a print "after func b:", b

输出结果:

可以看出,对于变量a,在函数func中"a = 2",因为存在既可以表示引用全局变量a,也可以表示创建一个新的局部变量的歧义,所以python默认指定创建一个新的局部变量来消除这一歧义,但对于列表b而言,"b[0] = 1"不存在这种歧义,因此直接修改了全局变量,但是如果改成了"b = [3, 4]",那么b也会变成局部变量。特别地,当在func中a = 2之前加入"if a == 1:"这一语句,脚本运行出错,因为这一语句引入了全局变量,导致了"a = 1"这一语句无法创建同名的局部变量。

因此,需要修改全局变量a,可以在"a = 2"之前加入global a声明,如:

#! /usr/bin/python

a = 1

b = [2, 3]

def func():

global a

a = 2

print "in func a:", a b[0] = 1

print "in func b:", b

if __name__ == '__main__':

print "before func a:", a print "before func b:", b func()

print "after func a:", a print "after func b:", b

输出结果:

结论:引用全局变量,不需要golbal声明,修改全局变量,需要使用global声明,特别地,列表、字典等如果只是修改其中元素的值,可以直接使用全局变量,不需要global声明。

关于python函数和类的理解

函数的参数

def add_end(L=[]):

L.append('END')

return L

当你使用默认参数调用时,一开始结果也是对的:

>>> add_end()

['END']

但是,再次调用add_end()时,结果就不对了:

>>> add_end()

['END', 'END']

很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。

切记: 定义默认参数要牢记一点:默认参数必须指向不变对象!

要修改上面的例子,我们可以用None这个不变对象来实现:

def add_end(L=None):

if L is None:

L = []

L.append('END')

return L

可变参数

传入可变参数:由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

def calc(numbers):

sum = 0

for n in numbers:

sum = sum + n * n

return sum

相当于传进来的是一个list或者tuple

若使用python自带的可变参数的话:仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数

def calc(*numbers):

sum = 0

for n in numbers:

sum = sum + n * n

return sum

calc(1, 2, 3)

14

calc(1, 3, 5, 7)

84

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:

def person(name, age, **kw):

print('name:', name, 'age:', age, 'other:', kw)

命名关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。

仍以person()函数为例,我们希望检查是否有city和job参数:

def person(name, age, **kw):

if 'city' in kw:

# 有city参数

pass

if 'job' in kw:

# 有job参数

pass

print('name:', name, 'age:', age, 'other:', kw)

递归函数

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000):

map/reduce/filter/sorted/lamda

map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回

再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)

[5, 9, -12, -21, 36]

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

>>> f = lambda x: x * x

>>> f

at 0x101c6ef28>

>>> f(5)

25

python类

访问限制:

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

class Student(object):

def __init__(self, name, score):

self.__name = name

self.__score = score

def print_score(self):

print('%s: %s' % (self.__name, self.__score))

类的继承的两种简单方法:

简单的继承:

方法一

class Settings(object):

def __init__(self):

self.numDimensions = 300

self.maxSeqlength = 250

self.batchSize = 24

self.lstmUnits = 64

self.numClasses = 2

class LSTM():

def __init__(self,iteration,settings):

self.numDimensions = settings.numDimensions

self.maxSeqlength = settings.maxSeqlength

self.batchSize = settings.batchSize

self.lstmUnits = settings.lstmUnits

self.numClasses = settings.numClasses

self.iteration = iteration

settings = Settings()

lstm = LSTM(iteration=10000,settings=settings)

print(lstm.lstmUnits,lstm.iteration)

方法二:

class Person(object):

def __init__(self, name, gender):

self.name = name

self.gender = gender

class Teacher(Person):

def __init__(self, name, gender, course):

super(Teacher,self).__init__(name,gender)

self.course = course

t = Teacher('Alice', 'Female', 'English')

print(t.name)

print(t.course)

以上就是我对python代码调试和Debug的一些理解,如果有哪些地方不对的话,欢迎大家给我留言和评论哦,记住我是那个悔恨晚写知乎的大白算法攻城狮哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值