NYC's Blog - Python http://niyanchun.com/tag/python/ Python中的global和nonlocal关键字 http://niyanchun.com/global-and-nonlocal-in-python.html 2018-09-02T13:35:00+08:00 在进入正题之前先简单说明一下:局部变量:函数或者方法里面定义的变量;全局变量:本文指module级别的变量(一个python文件就是一个module)。关于Python的作用域这里推荐一篇文章:A Beginner's Guide to Python's Namespaces, Scope Resolution, and the LEGB Rule,有兴趣的可以看一下。global先看个例子:a = 1 def func1(): print("func1:", a) def func2(): a = 10 print("func2:", a) def func3(): print("func3:", a) a = 100 print("func3:", a) if __name__ == "__main__": print(a) func1() func2() print(a) func3()输出结果如下:1 func1: 1 func2: 10 1 Traceback (most recent call last): File "/Users/allan/Desktop/test/test/test.py", line 24, in <module> func3() File "/Users/allan/Desktop/test/test/test.py", line 11, in func3 print("func3:", a) UnboundLocalError: local variable 'a' referenced before assignmentfunc1和func2的输出应该没有什么疑问,就是局部变量和全局变量作用域的问题:模块内都可以访问到全局变量;函数内局部变量与全局变量重名,会将全局变量隐藏掉(这种最好的理解方式就是把局部变量换个名字)。这里不再赘述。func3中,因为定义了和全局变量同名的a,所以,全局变量在整个方法中都被隐藏掉了,这个与局部变量a定义的位置没有关系。所以,在局部变量a定义之前使用a就会报异常。这里需要说明的一个关键点是:函数或方法中的任何地方,当给某个变量赋值的时候(特别要注意这个场景限定,后面会有一个例子),Python会将该变量当成一个局部变量,除非显式的使用global关键字声明该变量为全局变量。当然,赋值是写的过程,如果是读的话,会先查有没有这个名字的局部变量,如果没有,再查有没有这个名字的全局变量。所以说,一般global的使用场景就是当我们想在函数里面修改全局变量的值。看下面代码:a = 1 def func4(): global a a = 100 print("func4:", a) if __name__ == "__main__": print(a) func4() print(a)输出如下:1 func4: 100 100借助于global关键字,我们成功的在函数func4里面修改了全局变量a的值。func4和之前的func2的唯一区别就在于没有使用global关键字。所以,这样来看,其实global的使用还是挺简单的。下面看另外一个和global作用非常像的关键字nonlocal。nonlocalnonlocal关键字是Python3引入的(见PEP 3104 - Access to Names in Outer Scopes, The specification for the nonlocal statement.),PEP里面对该关键字的作用已经有了大概的说明了:访问作用域之外的(对象)名字。还是先看个例子:def outside(): msg = 'outside' def inside(): msg = 'inside' print(msg) inside() print(msg) if __name__ == "__main__": outside()这个例子和之前的例子实质一样,inside里面的msg变量其实是inside函数创建的一个局部变量而已,只不过和外层的msg同名了而已,但实质还是两个完全不同的变量。所以输出也并不意外:inside outside现在问题来了,如果我想在inside里面给外层的msg重新赋值(即修改外层的msg)怎么办?使用刚才介绍的global关键字行不行?试一下便知:def outside(): msg = 'outside' def inside(): global msg msg = 'inside' print(msg) inside() print(msg) if __name__ == "__main__": outside()输出结果:inside outsideOh, holy shit,没有成功。其实也正常,因为外层的msg并不是一个全局变量,它的作用域并不是module级别的,而只是outside函数范围内的而已。现在能实现我们上面那个需求的方式就是用nonlocal代替上面代码中的global:def outside(): msg = 'outside' def inside(): nonlocal msg msg = 'inside' print(msg) inside() print(msg) if __name__ == "__main__": outside()输出如下:inside inside所以,nonlocal这个关键字主要就是在闭包函数(enclosing function or closures)中使用,可以修改外层函数里面的对象。综合来看nonlocal和global的确是非常像的,不同之处在于前者用于修改函数作用域内的局部变量,而后者用于修改module级别的全局变量。提提神文章最后,再来一个提神的,看下面代码输出什么:def outside(): msg = {"outside": 1} def inside(): msg['inside'] = 2 print(msg) inside() print(msg) if __name__ == "__main__": outside()输出结果如下:{'outside': 1, 'inside': 2} {'outside': 1, 'inside': 2}Oh, holy shit,有没有意外?这里没有使用nonlocal修改msg,但里面的inside函数还是修改了外边的msg。不是说不行吗?nonlocal:这个锅我不背。其实这个的确和nonlocal没有什么关系,文章前面已经提醒过了,只有在给变量赋值这个场景下,且没有使用global和nonlocal,Python才去创建本函数的局部变量,而不是访问全局或外层函数的变量。而上面的msg['inside'] = 2其实不是个变量赋值语句。What?没错,字典的插入的确不是变量赋值,而是方法调用(调用的是__setitem__方法)。所以,msg['inside'] = 2的底层其实是执行了如下语句:msg.__setitem__("inside", 2)有没有很提神?(〃'▽'〃)References:Using global variables in a functionA Quick Guide To Nonlocal In Python 3 Python中的finally解析 http://niyanchun.com/finally-in-python.html 2018-07-25T21:29:00+08:00 结论先简单总结下try语句块,Python中的异常处理使用try...except...[else]...[finally]的方式,其中的方括号表示是可选的。所以一个最全的try语句块如下所示:try: statement1 except A: // A异常处理 statement2 except: // 通用异常处理 statement3 else: some statement block finally: some statement block注:为了说明的更清楚,约定一下文中的“try语句块”指的是try、except、else、finally所有的部分;而“try代码块”只指try里面正常执行的代码块,即只代表上面的statement1。其执行逻辑为:先执行try代码块,如果出现异常,则开始匹配后面的except列表,如果匹配到具体的异常,则进程异常处理,否则进入到通用的except异常处理(即except后面什么也没有,这种可以捕获任何类型的异常)里面。需要注意的是:通用异常语句一定要写在具体的异常语句之后,不然会有语法错误。如果没有出现任何异常,则执行else里面的语句。最后执行finally里面的语句。当然,这些都是最基本的,不是本文的重点,本文要分析的是一些容易产生混淆的场景:当finally和return等语句结合的时候执行顺序是什么样?我们先上结论,然后再逐一验证:以下内容来自Python官方文档the-try-statement部分:If finally is present, it specifies a ‘cleanup’ handler. The try clause is executed, including any except and else clauses. If an exception occurs in any of the clauses and is not handled, the exception is temporarily saved. The finally clause is executed. If there is a saved exception it is re-raised at the end of the finally clause. If the finally clause raises another exception, the saved exception is set as the context of the new exception. If the finally clause executes a return or break statement, the saved exception is discarded.以下内容来自Python官方文档Defining Clean-up Actions部分:A finally clause is always executed before leaving the try statement, whether an exception has occurred or not. When an exception has occurred in the try clause and has not been handled by an except clause (or it has occurred in an except or else clause), it is re-raised after the finally clause has been executed. The finally clause is also executed “on the way out” when any other clause of the try statement is left via a break, continue or return statement.两处说的其实是一个意思,我们从中整理出一些结论:finally代码块永远是try语句块中最后一个执行的。如果在非finally代码块发生了异常,并且在执行到finally代码块之前该异常还没有被处理,那么该异常会被暂存;然后:如果finally里面没有发生新的异常,并且没有return和break语句,那么在finally执行结束后,会重新抛出之前暂存的异常;如果finally里面没有发生新的异常,但执行了return或break语句,那原来暂存的异常就会被丢弃,代码正常跳出try语句块;如果在finally里面发生了新的异常,那原来暂存的异常会作为新异常的上下文一起被抛出。注:暂存的异常被存储在sys模块里面,可通过sys.exc_info()访问,该函数返回一个3个元素的元组:(异常类,异常实例,traceback对象)。下面我们用一些例子来验证一下上面的结论。结论1验证关于第1条结论,主要是要理解最后两个字,最后指的是程序运行即将离开try语句块的时候,主要有return、break、continue三种情况。return的情况:def demo1(): try: raise RuntimeError("To Force Issue") return 1 except: return 2 else: return 3 finally: return 4 if __name__ == "__main__": print(demo1())输出结果如下:4demo1的的运行顺序为:先执行try,发生异常,跳到except里面,执行return 1,最后再执行finally里面的return 4。所以demo1函数最终返回的是4而非2。break的情况:def demo2(): cnt = 0 while True: try: cnt += 1 if cnt == 5: break finally: print(cnt) if __name__ == "__main__": demo2()输出为:1 2 3 4 5可以看到,每一轮循环都会跳出try语句块,但跳出之前都执行一遍finally。另外看最后一次,当cnt等于5的时候,执行了break,即将跳出while循环,同时也要跳出try语句块的时候(即上面所谓的"on the way out"),跳出之前还是执行了finally,打印了5。continue的情况:def demo3(): cnt = 0 while cnt < 5: try: cnt += 1 continue finally: print(cnt) if __name__ == "__main__": demo3()运行结果:1 2 3 4 5和break一样,每一轮循环都会跳出try语句块,但都执行了finally。所以,只要try语句块中有finally,那么它一定是最后执行的。因为这样,一般不建议在finally里面写return语句,这样会使其他地方的return语句都失效。finally一般只做一些资源释放的操作。结论2验证结论2首先是否和结论1的,即finally肯定是最后被执行的,只是如果产生了异常,并且在执行到finally的时候该异常还没有被处理(可能是没有匹配到except,也可能是处理异常的时候又产生了新异常),那就有3种情况了:先看情况1:如果finally里面没有发生新的异常,并且没有return和break语句,那么在finally执行结束后,会重新抛出之前暂存的异常;def demo4(): try: raise RuntimeError("To Force Issue") return 1 except: print('when handle exception, a new excetion occured') 5 / 0 else: return 3 finally: print('in finally') if __name__ == "__main__": demo4()运行结果:when handle exception, a new excetion occured in finally Traceback (most recent call last): File "/Users/allan/Desktop/temp/test.py", line 3, in demo4 raise RuntimeError("To Force Issue") RuntimeError: To Force Issue During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/allan/Desktop/temp/test.py", line 14, in <module> print(demo4()) File "/Users/allan/Desktop/temp/test.py", line 7, in demo4 5 / 0 ZeroDivisionError: division by zero可以看到,先是执行try代码块,产生了RuntimeError异常,在except中处理该异常时,有产生了新的ZeroDivisionError异常,导致在执行到finally代码块时,异常还没有处理掉,所以之前的两个异常被先暂存起来,等finally里面的print语句执行结束之后,finally将刚才暂存的异常重新抛了出来。再看情况2:如果finally里面没有发生新的异常,但执行了return或break语句,那原来暂存的异常就会被丢弃,代码正常跳出try语句块;我们把demo4做简单修改:在finally里面加一个return(加break效果一样):def demo5(): try: raise RuntimeError("To Force Issue") return 1 except: print('when handle exception, a new excetion occured') 5 / 0 else: return 3 finally: print('in finally') return if __name__ == "__main__": demo5()运行结果:when handle exception, a new excetion occured in finally可见之前demo4里面的异常信息没有了,因为finally里面有return,导致暂存的异常被丢弃了。所以再次不建议在finally里面写return还有break。另外Python语法不支持finally里面有continue。最后看情况3:如果在finally里面发生了新的异常,那原来暂存的异常会作为新异常的上下文一起被抛出。我们把demo5做小修改,在finally中增加一条会产生异常的语句:def demo6(): try: raise RuntimeError("To Force Issue") return 1 except: print('when handle exception, a new excetion occured') 5 / 0 else: return 3 finally: print('in finally') 2 + 's' if __name__ == "__main__": demo6()执行结果:when handle exception, a new excetion occured in finally Traceback (most recent call last): File "/Users/allan/Desktop/temp/test.py", line 3, in demo5 raise RuntimeError("To Force Issue") RuntimeError: To Force Issue During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/allan/Desktop/temp/test.py", line 7, in demo5 5 / 0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/Users/allan/Desktop/temp/test.py", line 15, in <module> demo5() File "/Users/allan/Desktop/temp/test.py", line 12, in demo5 2 + 's' TypeError: unsupported operand type(s) for +: 'int' and 'str'可以看到异常栈有三层,分别是try代码块中的RuntimeError、except代码块中的ZeroDivisionError、finally里面的TypeError,前二者在进入finally之前被暂存,因为finally里面发生了新异常,暂存的异常就作为新异常的一部分被一起抛出。 Python中单双下划线的区别 http://niyanchun.com/underlines-in-python.html 2017-11-30T22:52:00+08:00 注:本文大部分内容参考自Difference between _, and __xx in Python.在学习Python的时候,很多人都不理解为什么在方法(method)前面会加好几个下划线,有时甚至两边都会加,比如像__this__这种。在我看到上面的文章之前,我一直以为Python中这些下划线的作用就像Golang中方法/函数的大小写一样,或是一些其他语言中的private、public的作用一样,但仔细深究,这不全是Python这样设计的初衷。下面我们具体分析。单下划线开头我们经常看到方法或者属性前面加了单下划线,并认为它表示该方法或者属性是该类型(Python和Golang一样,不光类可以有方法,很多类型甚至基本类型也可以定义方法)的私有方法或属性。但其实在Python中不存在真正意义上的私有方法或者属性,前面加单下划线_只是表示你不应该去访问这个方法或者属性,因为它不是API的一部分。举个例子:class BaseForm(StrAndUnicode): ... def _get_errors(self): "Returns an ErrorDict for the data provided for the form" if self._errors is None: self.full_clean() return self._errors errors = property(_get_errors)该代码片段来自Django源码(django/forms/forms.py)。这段代码的设计就是errors属性是对外API的一部分,如果你想获取错误详情,应该访问errors属性,而不是(也不应该)访问_get_errors方法。双下划线开头之前很多人跟我说Python中双下划线开头表示私有,我在很多地方也见到这样的说法。这样理解可能也不能说错,但这不是Python设计双下划线开头的初衷和目的,Python设计此的真正目的仅仅是为了避免子类覆盖父类的方法。我们看个例子:class A(object): def __method(self): print("I'm a method in class A") def method_x(self): print("I'm another method in class A\n") def method(self): self.__method() self.method_x() class B(A): def __method(self): print("I'm a method in class B") def method_x(self): print("I'm another method in class B\n") if __name__ == '__main__': print("situation 1:") a = A() a.method() b = B() b.method() print("situation 2:") # a.__method() a._A__method() 执行结果:situation 1: I'm a method in class A I'm another method in class A I'm a method in class A I'm another method in class B situation 2: I'm a method in class A这里有两个点需要注意:A类中我们定义了__method()、method_x和method()三个方法;然后我们重新定义一个类B,继承自A,并且在B类中覆写(override)了其父类的__method()和method_x方法,但是从输出结果看,B对象调用method()方法时调用了其父类A的__method()方法和自己的method_x()方法。也就是说,__method()覆写没有生效,而method_x()覆写生效了。而这也正是Python设计双下划线开头的唯一目的。这一点也可在Python官方说明中得到答案:https://www.python.org/dev/peps/pep-0008/#method-names-and-instance-variables。前面我们就说了,Python中不存在真正意义上的私有变量。对于双下划线开头的方法和属性虽然我们不能直接引用,那是因为Python默认在其前面加了前缀_类名,所以就像situation 2下面的代码,虽然我们不能用a直接访问__method(),但却可以加上前缀去访问,即_A__method()。开头结尾双下划线一般来说像__this__这种开头结尾都加双下划线的方法表示这是Python自己调用的,你不要调用。比如我们可以调用len()函数来求长度,其实它后台是调用了__len__()方法。一般我们应该使用len,而不是直接使用__len__():a = [1, 2, 3] print(len(a)) print(a.__len__()) # 和上面等效 num = 10 print(num + 10) print(num.__add__(10)) # 和上面等效我们一般称__len__()这种方法为magic methods,一些操作符后台调用的也是也是这些magic methods,比如+后台调用的是__add__,-调用的是__sub__,所以这种机制使得我们可以在自己的类中覆写操作符(见后面例子)。另外,有的时候这种开头结尾双下划线方法仅仅是某些特殊场景的回调函数,比如__init__()会在对象的初始化时调用,__new__()会在构建一个实例的时候调用等等。下面我们看两个例子:class CrazyNumber(object): def __init__(self, n): self.n = n def __add__(self, other): return self.n - other def __sub__(self, other): return self.n + other def __str__(self): return str(self.n) num = CrazyNumber(10) print(num) # output is: 10 print(num + 5) # output is: 5 print(num - 20) # output is: 30在上面这个例子中,我们覆写了+和-操作符,将他们的功能交换了。再看个例子:class Room(object): def __init__(self): self.people = [] def add(self, person): self.people.append(person) def __len__(self): return len(self.people) room = Room() room.add("Igor") print len(room) # output is: 1这个例子中,因为我们实现了__len__(),所以Room对象也可以使用len函数了。所有此类的方法都在这里有说明:documentation.结论使用单下划线(_one_underline)开头表示方法不是API的一部分,不要直接访问(虽然语法上访问也没有什么问题)。使用双下划线开头(__two_underlines)开头表示子类不能覆写该方法。除非你真的知道你在干什么,否则不要使用这种方式。当你想让自己定义的对象也可以像Python内置的对象一样使用Python内置的一些函数或操作符(比如len、add、+、-、==等)时,你可以定义该类方法。当然还有些属性只在末尾加了但下划线,这仅仅是为了避免我们起的一些名字和Python保留关键字冲突,没有特殊含义。 Python学习笔记(2)——字符串 http://niyanchun.com/python-notes-2.html 2014-05-03T12:40:00+08:00 字符串是Python中使用非常之多的一种数据结构,它也是一种序列,所以上一篇文章中所讲的关于序列的所有操作也都适用于字符串。不过,因为字符串长度是不可变的,所以分片赋值时需要注意要和字符串的长度相符。字符串的知识主要包括字符串格式化和字符串相关的一些方法。一,字符串格式化Python的字符串格式化和C语言是比较像的,使用字符串格式化操作符%来实现。在%的左侧放置一个字符串(格式化字符串),而右侧放置希望格式化的值。例如:>>> format = "Hello, %s. %s enough for ya?" >>> values = ('world', 'Hot') >>> print format % values Hello, world. Hot enough for ya? 总结一下,字符串基本的转换说明符包括以下几种: %字符:标记转换说明符的开始 转换标志(可选):-表示左对齐;+表示在转换值之前要加上正负号;“”(空白字符)表示正数之前保留空格;0表示转换值若位数不够则用0填充。 最小字段宽度(可选):转换后的字符串至少应该具有该值指定的宽度。如果是*,那么精度将会从元组中读出。 下面看一些例子:>>> 'Price of eggs: $%d' % 42 'Price of eggs: $42' >>> 'Hexadecimal price of eggs: %x' % 42 'Hexadecimal price of eggs: 2a' >>> from math import pi >>> 'Pi: %f...' % pi 'Pi: 3.141593...' >>> 'Very inexact estimate of pi: %i' % pi 'Very inexact estimate of pi: 3' >>> 'Using str: %s' % 42L 'Using str: 42' >>> 'Using repr: %r' % 42L 'Using repr: 42L' >>> '%10f' % pi #字段宽度10 ' 3.141593' >>> '%10.2f' % pi #字段宽度10,精度2 ' 3.14' >>> '%.2f' % pi #精度2 '3.14' >>> '%.5s' % 'Guido van Rossum' 'Guido' # 可以使用*作为字段宽度或这精度,此时数值会从元组参数中读出 >>> '%.*s' % (5, 'Guido van Rossum') 'Guido' # 在字段宽度和精度值之前还可以放置一个“标表”,该标表可以时0、+、-或空格。这些标表的作用前面已经有说明 >>> '%010.2f' % pi '0000003.14' >>> '%-10.2f' % pi '3.14 ' >>> print ('% 5d' % 10) + 'n' + ('% 5d' % -10) 10 -10 >>> print ('%+5d' % 10) + 'n' + ('%+5d' % -10) +10 -10  二,字符串方法1,findfind方法可以在一个较长的字符串中查找子字符串,它返回子串所在位置的最左端索引。如果没有找到则返回-1。该方法还可以接受可选的起始点和结束点参数(但指定范围包括第一个索引,不包括第二个索引,即半闭半开)。>>> 'With a moo-moo here, and a moo-moo there'.find('moo') 7 >>> 'With a moo-moo here, and a moo-moo there'.find('mooo') -1 >>> subject = '$$$ Get rich now!!! $$$' >>> subject.find('$$$') 0 >>> subject.find('$$$', 1) #只提供起始点 20 >>> subject.find('$$$', 0, 16) #提供起始点和结束点 0 2,joinjoin时非常重要的字符串方法,它是split方法的逆方法,用来在队列中添加元素(所添加的元素都必须时字符串):>>> seq = [1, 2, 3, 4, 5] >>> sep = '+' >>> sep.join(seq) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: sequence item 0: expected string, int found >>> seq = ['1', '2', '3', '4', '5'] >>> sep.join(seq) '1+2+3+4+5' >>> dirs = '', 'usr', 'bin', 'env' >>> dirs ('', 'usr', 'bin', 'env') >>> '/'.join(dirs) '/usr/bin/env' >>> print 'C:' + ''.join(dirs) C:usrbinenv 3,lowerlower方法返回字符串的小写字母版。相反的时upper。>>> 'Trondheim Hammer Dance'.lower() 'trondheim hammer dance' >>> 'Trondheim Hammer Dance'.upper() 'TRONDHEIM HAMMER DANCE' 4,replacereplace方法返回某字符串的所有匹配项均为替换之后得到的字符串。>>> 'This is a test'.replace('is', 'eez') 'Theez eez a test' 5,split该方法时join的逆方法,用来将字符串分割成序列。>>> '1+2+3+4+5'.split('+') ['1', '2', '3', '4', '5'] >>> '/usr/bin/env'.split('/') ['', 'usr', 'bin', 'env'] 6,stripstrip方法返回去除两侧(不包括内部)空格的字符串。>>> ' internal whitespace is kept '.strip() 'internal whitespace is kept' >>> '*** SPAM * for * everyone!!! ***'.strip(' *!') 'SPAM * for * everyone' 7,translatetranslate方法和replace方法一样,可以替换字符串中的某些部分,但是和前者不同的是,translate方法只处理单个字符。它的优势在于可以同时进行多个替换。>>> from string import maketrans >>> table = maketrans('cs', 'kz') >>> len(table) 256 >>> table[97:123] 'abkdefghijklmnopqrztuvwxyz' >>> maketrans('', '')[97:123] 'abcdefghijklmnopqrstuvwxyz' >>> 'this is an incredible test'.translate(table) 'thiz iz an inkredible tezt' 上例中使用了maketrans函数,该函数用于生成一个长256 bytes的转换表,它接受两个参数:两个等长的字符串,表示第一个字符串中的每个字符都用第二个字符串中相同位置的字符替换。 Python学习笔记(1)——列表和元组 http://niyanchun.com/python-notes-1.html 2014-05-03T01:16:00+08:00 Python中有四种特别常用的数据结构——列表,元组,字符串和字典。这里我们先介绍列表和元组。列表和元组是Python内建的6种序列(sequence)种最常用的一种数据类型。所有Python的序列类型都有一些通用的操作:索引(indexing)、分片(slicing)、加(adding)、乘(multiplying)以及检查某个元素是否属于序列的成员(成员资格)。除此以外,Python还有计算序列的长度、找出最大元素和最小元素的内建函数。一,序列的通用操作下面我们以列表(用方括号括起来的一组数据)为例说明序列的一些通用操作。1,索引同许多编程语言一样,Python中序列的索引下标是从0开始的。而且,可以使用负数下标进行索引——从序列的右边的最后一个元素开始索引,最后一个元素的位置编号是-1。而且可以直接对字符串字面值使用索引。比如:>>> greeting = 'Hello' >>> greeting[0] 'H' >>> greeting[-1] 'o' >>> 'Hello'[1] 'e'2,分片使用索引可以对单个元素进行操作,而使用分片却可以访问一定范围内的元素。分片通过冒号相隔的两个索引来实现,且第一个索引的元素是包含在分片内的,而第二个则不包含在分片内。分片时也可以指定步长,默认值步长为1。下面通过一些列子来说明其他关于分片的特性:>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> numbers[3:6] [4, 5, 6] >>> numbers[0:1] [1] >>> numbers[-3:-1] [8, 9] >>> numbers[-3:0] #如果分片中最左边的索引比它右边的晚出现在序列中,结果就是一个空的序列 [] >>> numbers[-3:] #如果分片所得部分包括序列结尾的元素,那么置空最后一个索引即可 [8, 9, 10] >>> numbers[:3] [1, 2, 3] >>> numbers[:] #如果要复制整个序列,可以将两个索引都置空 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> numbers[0:10:1] #显式的设置步长 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> numbers[0:10:2] #设置步长为2 [1, 3, 5, 7, 9] >>> numbers[0:10:-2] #步长可以为正数、负数,但不能为0。为负数时,表示从右向左提取元素,所以必须让开始点大于结束点 [] >>> numbers[10:0:-2] [10, 8, 6, 4, 2] >>> numbers[::-3] [10, 7, 4, 1] 3,序列相加可以通过加号进行序列的连接操作,但必须时两种相同类型的序列才能进行连接操作。如下例:>>> [1, 2, 3] + [4, 5, 6] [1, 2, 3, 4, 5, 6] >>> 'Hello' + 'World' 'HelloWorld' >>> [1, 2, 3] + 'World' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate list (not "str") to list 4,乘法用数字n乘一个序列会生成新的序列,而在新的序列之中,原来的序列将被重复n次。>>> 'python' * 5 'pythonpythonpythonpythonpython' >>> [42] * 10 [42, 42, 42, 42, 42, 42, 42, 42, 42, 42] >>> sequence = [None] * 10 #None是Python的一个内建值,表示什么都没有,可以用于初始化列表。 >>> sequence [None, None, None, None, None, None, None, None, None, None]5,成员资格为了检查一个值是否存在于序列之中,可以使用in操作符。返回值为bool值True或False。>>> 'r' in permissions True >>> 'x' in permissions False6,长度、最小值和最大值内建函数len、min和max分别用于计算序列的长度、最小值和最大值。>>> members = [100, 34, 678] >>> len(members) 3 >>> min(members) 34 >>> max(members) 678 >>> min(9, 4, 7, 2) 2以上便是序列的一些通用操作,适用于Python内建的6中序列。下面我们学习一下最常用的一种序列:列表。二,列表列表和元组以及字符串相比,最大的特别时列表是可变的(mutable)——列表的内容可以改变,除此以外,列表还有许多有用的专门的方法。1,list函数list函数用于根据字符串创建列表。>>> list('Hello') ['H', 'e', 'l', 'l', 'o']其实,list不光适用于字符串,还适用于所有类型的序列。2,基本的列表操作列表除了适用前面介绍的所有序列的标准操作外,还有一些其他的操作:(1)改变列表:元素赋值>>> x = [1, 1, 1] >>> x[1] = 2 >>> x [1, 2, 1](2)删除元素从列表中删除元素,使用del语句来实现。>>> names = ['Alice', 'Beth', 'Cecil', 'Dee-Dee', 'Earl'] >>> del names[2] >>> names ['Alice', 'Beth', 'Dee-Dee', 'Earl'](3)分片赋值分片是一个非常强大的特性,分片赋值操作则更加显现它的强大。合理使用分片赋值往往能实现很多赋值之外的操作。具体见下面的例子:>>> name = list('Perl') >>> name ['P', 'e', 'r', 'l'] >>> name[2:] = list('ar') >>> name ['P', 'e', 'a', 'r'] >>> name = list('Perl') >>> name[1:] = list('ython') #使用分片赋值时,可以使用与原序列不等长的序列将分片替换 >>> name ['P', 'y', 't', 'h', 'o', 'n'] >>> numbers = [1, 5] >>> numbers[1:1] = [2, 3, 4] #分片赋值可以在不需要替换任何原有元素的情况下插入新的元素 >>> numbers [1, 2, 3, 4, 5] >>> numbers[1:4] = [] #替换一个空的序列,达到del的操作 >>> numbers [1, 5] 3,列表方法(1)append——用于在列表末尾追加新的对象>>> lst = [1, 2, 3] >>> lst.append(4) >>> lst [1, 2, 3, 4](2)count——用于统计某个元素在列表中出现的次数>>> ['to', 'be', 'or', 'not', 'to', 'be'].count('to') 2 >>> x = [[1, 2], 1, 1, [2, 1, [1, 2]]] >>> x [[1, 2], 1, 1, [2, 1, [1, 2]]] >>> x.count(1) 2 >>> x.count([1, 2]) (3)extend——用于在列表末尾追加另一个序列中的多个值>>> a = [1, 2, 3] >>> b = [4, 5, 6] >>> a.extend(b) >>> a [1, 2, 3, 4, 5, 6] (4)index——用于从列表中找出某个值与第一个匹配项的索引位置:>>> knights = ['we', 'are', 'the', 'knights', 'who', 'say', 'ni'] >>> knights.index('who') 4 >>> knights.index('herring') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: 'herring' is not in list >>> knights[4] 'who' (5)insert——用于将对象插入到列表中>>> numbers = [1, 2, 3, 5, 6, 7] >>> numbers.insert(3, 'four') >>> numbers [1, 2, 3, 'four', 5, 6, 7](6)pop——移除列表中的一个元素(默认是最后一个),并且返回该元素的值>>> x = [1, 2, 3] >>> x [1, 2, 3] >>> x.pop() 3 >>> x [1, 2] >>> x.pop() 2 >>> x [1](7)remove——用于移除列表中某个值的第一个匹配项>>> x = ['to', 'be', 'or', 'not', 'to', 'be'] >>> x ['to', 'be', 'or', 'not', 'to', 'be'] >>> x.remove('be') >>> x ['to', 'or', 'not', 'to', 'be'] >>> x.remove('bee') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.remove(x): x not in list >>>(8)reverse——将列表中的元素反向存放>>> x = [1, 2, 3] >>> x [1, 2, 3] >>> x.reverse() >>> x [3, 2, 1](9)sort——用于在原位置对列表进行排序>>> x = [4, 6, 2, 1, 7, 9] >>> x.sort() >>> x [1, 2, 4, 6, 7, 9] 列表我们就介绍到这里,下面我们介绍另外一种非常常见的序列:元组。三,元组元组与列表一样,也是一种序列。唯一的不同是元组不能修改。创建元组的语法很简单:如果用逗号分隔了一些值,就会自动创建元组。注意:逗号在元组里面是必须的,即使只有一个值。>>> 1, 2, 3 (1, 2, 3) >>> (1, 2, 3) (1, 2, 3) >>> () #空元组 () >>> 42 #不是元组 42 >>> 42, #只有一个值的元组 (42,) >>> (42) #不是元组 42 >>> (42,) (42,) >>> 3 * (40+2) #普通乘法计算 126 >>> 3 * (40+2,) #元组乘法 (42, 42, 42)1,tuple函数该函数的功能与list函数基本上是一样的:以一个序列作为参数并把它转换为元组。如果参数就是元组,那么该参数就会被原样返回:>>> tuple([1, 2, 3]) (1, 2, 3) >>> tuple('abc') ('a', 'b', 'c') >>> tuple((1, 2, 3)) (1, 2, 3) 2,元组的基本操作元组因为不可改变,所以其操作除了创建和访问之外,没有太多的操作。3,元组的意义元组其实就是一个不可变的序列,很多情况下,元组都是可以被列表替代的,但除了下面两个方面:(1)元组可以再映射(和集合的成员)中当键使用——而列表不可以。(2)元组可以作为很多内建函数和方法的返回值。