对于一个天天与代码打交道的算法程序猿,不免会时时刻刻去调试你的程序代码,并且与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的一些理解,如果有哪些地方不对的话,欢迎大家给我留言和评论哦,记住我是那个悔恨晚写知乎的大白算法攻城狮哈