• 关于

    python无限循环

    的搜索结果

问题

字典无限循环意外退出

正在尝试在Python中创建无限循环的各种方法(除了通常之外while True),并提出了这个想法:x = {0: None}for i in x:del x[i] x[i+1] = None # Value doesn't matte...
一码平川MACHEL 2019-12-01 19:32:13 435 浏览量 回答数 1

问题

python怎么无限循环

python怎么无限循环...
云计算小粉 2019-12-01 20:05:06 2135 浏览量 回答数 3

回答

Python的循环有两种,一种是for...in循环,第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环, whie true 就可以实现无限循环 while true: print ("loop")
路舟 2019-12-02 01:10:06 0 浏览量 回答数 0

回答

for 循环 本系列前面 “探索 Python,第 5 部分:用 Python 编程” 一文讨论了 if 语句和 while 循环,讨论了复合语句以及适当缩进 Python 语句来指示相关 Python 代码块。该文的结尾介绍了 Python for 循环。但就其使用和功能来说,for 循环更值得关注,所以本文单独讲述该循环。 for 循环有一个简单的语法,使您可以从容器对象中提取单个项目并对其进行某些操作。简单地说,使用 for 循环,可以迭代中对象集合的项目。对象集合可以是任何 Python 容器类型,包括前面文章中讨论的 tuple、string 和 list 类型。但是容器 metaphor 的功能比这三种类型更强大。metaphor 包括其他序列类型,如 dictionary 和 set,将来的文章中将对它们进行讨论。 但是请稍等!还有更多信息:for 循环可以用于迭代支持迭代 metaphor 的任何对象,这使 for 循环非常有用。 清单 1 中显示了 for 循环的基本语法,还演示了如何在 for 循环中使用 continue 和 break 语句。 清单 1. for 循环的伪代码 ? 12345678910111213 for item in container: if conditionA: # Skip this item continue elif conditionB: # Done with loop break # action to repeat for each item in the container else: # action to take once we have finished the loop. 本系列中的第二篇文章 “探索 Python,第 2 部分:探索 Python 类型的层次结构” 介绍了 Python tuple。如文中所述,tuple 类型是不可变的异构容器。这主要是说 tuple 可以存放不同类型的对象,但是它一旦创建,就无法更改。清单 2 演示了如何使用 for 循环迭代 tuple 的元素。 清单 2. for 循环和 tuple ? 1234567891011121314151617 t = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) count = 0for num in t: ... count += num... else:... print count... 45 count = 0for num in t: ... if num % 2:... continue... count += num... else:... print count... 20 本例首先创建了名为 t 的 tuple,存放整数 0 至 9(包含 9)。第一个 for 循环迭代此 tuple,在 count 变量中累计 tuple 中数值的和。一旦代码已经迭代了 tuple 中的所有元素,它将进入 for 循环的 else 子句,打印 count 变量的值。 清单 2 中显示的第二个 for 循环也迭代 tuple 中的所有元素。但是,它仅累计容器中能够被 2 整除的那些项的值(请记住如果表达式为非零,if 语句将确定为真,num 不能被 2 整除时使用 % 运算符会返回非零值)。此限制通过使用适当的 if 语句和 continue 语句来完成。如前面的文章中所述,continue 语句使包含它的循环开始下一次迭代。实现相同结果的另一种方法是测试 tuple 中的当前项是否是偶数(使用 if not num % 2:),如果为真,那么将当前项添加到运行总和中。一旦代码完成 tuple 中的迭代,将调用 else 子句,打印总和。 本系列中的第三篇文章 “探索 Python:第 3 部分:探索 Python 类型的层次结构” 讨论了 Python string。string 是不可变的同构容器,这意味着它仅能存放字符且一旦建立将无法修改。清单 3 演示了如何使用 Python string 作为 for 循环的容器。 清单 3. for 循环和 string ? 123456789101112131415161718192021 st = "Python Is A Great Programming Language!"for c in st: ... print c,... P y t h o n I s A G r e a t P r o g r a m m i n g L a n g u a g e ! count = 0for c in st: ... if c in "aeiou":... count += 1... else:... print count...10 count = 0for c in st.lower(): ... if c in "aeiou":... count += 1... else:... print count... 12 本例提供了三个不同的 for 循环,它们都迭代同一 string。第一个 for 循环迭代 string “Python Is A Great Programming Language!” 并一次打印 string 中的一个字符。在此例中,print 语句变量 c 后加了一个逗号。这使 print 语句打印字符值时后面跟着空格字符,而不是换行字符。如果没有后面的逗号,字符将全部打印在单独的行中,会很难读。 下两个 for 循环迭代该字符串并计算其包含多少个元音字母(“a”、“e”、“i”、“o” 或 “u”)。第二个 for 循环在迭代原始 string 时仅查找小写元音字母。第三个 for 循环迭代通过调用 string 对象的 lower 方法返回的临时 string。lower 方法将 string 中的所有字符转换为小写。因此,第三个 for 循环可找到另外两个元音字母。 本系列中的第四篇文章 “探索 Python,第 4 部分:探索 Python 类型的层次结构” 介绍了 Python list。list 是异构可变容器,这意味着它可以存放不同类型的对象且创建后可以修改。清单 4 演示了如何使用 list 和 for 循环。 清单 4. for 循环和 list ? 12345678910 mylist = [1, 1.0, 1.0j, '1', (1,), [1]]for item in mylist: ... print item, "t", type(item))... 1 1.0 1j 1 (1,) [1] 既然 list 是很灵活的 Python 容器类型(您将在本系列其余的文章中多次看到它),本例看起来可能过于简单了。但是,这是一部分要点:使用 for 循环使处理容器中的每个项目非常简单,甚至处理包含各种不同对象的 list 也是如此。本例迭代 Python list 中的所有项目,并在单独的行中打印每一项及其相对应的 Python 类型。 迭代和可变容器 Python list 是一个可变序列,提供了一种令人好奇的可能性:for 循环主体可以修改其正在迭代的 list。正如您可能认为的,这样并不好,如果进行此操作,Python 解释器将无法很好地工作,如清单 5 所示。 清单 5. 在 for 循环中修改容器 ? 1234567891011121314151617 mylist = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]for item in mylist: ... if item % 2:... mylist.insert(0, 100)... ^CTraceback (most recent call last): File "", line 3, in ?KeyboardInterrupt print mylist [100, ...., 100, 100, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # Many lines deleted for clarity mylist = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]for item in mylist[:]: ... if item % 2:... mylist.insert(0, 100)... print mylist [100, 100, 100, 100, 100, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 本例中的第一个 for 循环只要在原始 list 中发现奇数,它就在 list 的开始插入数值 100。当然,这是一种演示此问题的不同寻常的方式,但却非常好。一旦在三个点的 Python 提示后按 Enter 键,Python 解释器就处于无限循环的混乱中。要停止这种混乱,必须通过按 Ctrl-C(其在 Python 输出中显示为 ^C)来中断进程,然后会出现 KeyboardInterrupt 异常。如果打印出修改的 list,将看到 mylist 现在包含大量的值为 100 的元素(新元素的准确数量取决于您中断循环的速度)。 本例中的第二个 for 循环演示了如何避免此问题。使用切片运算符创建原始 list 的副本。现在 for 循环将迭代该副本,而对原始 list 进行修改。最终的结果是修改后的原始 list,它现在以五个值为 100 的新元素开始。 for 循环和序列索引 如果您用过其他编程语言,Python for 循环可能看起来有点儿古怪。您可能认为它更像 foreach 循环。基于 C 的编程语言具有 for 循环,但它的设计目的是对一系列操作执行特定次数。Python for 循环可以通过使用内置的 range 和 xrange 方法来模拟该行为。清单 6 中演示了这两种方法。 清单 6. range 和 xrange 方法 ? 12345678910 r = range(10)print r [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] type(r) xr = xrange(10)print xr xrange(10) type(xr) 本例首先演示了 range 方法,它创建一个包含一系列整数的新 list。调用 range 方法的一般形式是提供单个值,用作整数 list 的上限。零为起始值。因此,调用 range(10) 将创建包含整数 0 至 9(包含 9)的 list。range 方法接受起始索引以及步长。所以,调用 range(11,20) 将创建从 11 至 19(包含 19)的整数 list,而调用 range(12, 89, 2) 将创建从 12 至 88 的偶数 list。 由于 xrange 方法也创建整数 list(其使用相同参数),所以它与 range 方法非常相似。但是,xrange 方法仅在需要时才在 list 中创建整数。例如,在清单 6 中,尝试打印出新创建的 xrange 时除了 xrange 的名称,不会显示任何数据。当需要迭代大量整数时,xrange 方法更适用,因为它不会创建极大的 list,那样会消耗大量计算机内存。 清单 7 演示了如何在 for 循环内使用 range 方法来创建整数 1 至 10(包含 10)的乘法表。 清单 7. 创建乘法表 ? 123456789101112131415 for row in range(1, 11): ... for col in range(1, 11):... print "%3d " % (row * col),... print... 1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 8 16 24 32 40 48 56 64 72 80 9 18 27 36 45 54 63 72 81 90 10 20 30 40 50 60 70 80 90 100 本例使用两个 for 循环,外面的 for 循环关注乘法表中的每一行,嵌套的 for 循环关注每行内的列。每个循环都迭代包含整数 1 至 10(包含 10)的 list。最里面的 print 语句使用了一个名为 字符串格式化 的新概念来创建格式设置精美的表。字符串格式化是一种非常有用的技术,用于以格式设置精美的布局创建由不同数据类型组成的 string。现在详细信息并不重要,将来的文章中将讲述这些内容(了解 C 编程语言的 printf 方法的任何人都会很熟悉这些内容)。在本例中,字符串格式化指定将从整数创建新 string 且需要保留三个字符来存放该整数(如果该整数小于三个字符,将在左边用空格填补,从而使数据排列整齐)。第二个 print 语句用于打印新行,从而使乘法表中的下一行被打印在新的行中。 range 方法还可用于迭代容器,通过使用适当的索引访问序列中的每一项。要进行此操作,需要包含容器的允许范围索引值的整数 list,这可以通过使用 range 方法和 len 方法来轻松实现,如清单 8 所示。 清单 8. 在 for 循环内索引容器 ? 1234567891011121314 st = "Python Is A Great Programming Language!"for index in range(len(st)): ... print st[index],... P y t h o n I s A G r e a t P r o g r a m m i n g L a n g u a g e ! for item in st.split(' '): ... print item, len(item)... Python 6Is 2A 1Great 5Programming 11Language! 9 这个最后的示例演示了如何使用 len 方法作为 range 方法的参数,创建可用于单独访问 string 中每个字符的整数 list。第二个 for 循环还显示了如何将 string 分割为子字符串的 list(使用空格字符来指示子字符串的边界)。for 循环迭代子字符串 list,打印每个子字符串及其长度。
xuning715 2019-12-02 01:10:25 0 浏览量 回答数 0

回答

如果你在循环中改变它,则无法保证迭代所有dict条目。在字典中添加或删除条目时迭代视图可能会引发RuntimeError或无法迭代所有条目。 您可以创建一个“枚举”无限循环,类似于您最初使用的尝试itertools.count()。例如: from itertools import count for i in count(): print(i) # don't run this without some mechanism to break the loop, i.e. # if (i == 10): # break OUTPUT 0 1 2 ...and so on 我刚刚在python2和python3中测试了你的代码 python3 output0,1,2,3,4python20,1,2,3,4,5,6,7有一件事可能会发生。当您创建第一个键值时,字典中只分配了一定量的内存,当您删除键值时,我们不会分配任何内存或释放内存,而只是删除值。一旦使用了所有分配的内存,它就会退出。因为如果你在没有del的情况下运行,你将收到此错误 RuntimeError: dictionary changed size during iteration因此python为该键值创建了足够的内存以及更多内存,一旦用完,就不再为字典分配内存。
一码平川MACHEL 2019-12-02 01:54:24 0 浏览量 回答数 0

回答

这是一个广泛的问题,有很多方法可以实现这一目标。例如:在无限循环上编写一个简单的bash脚本,该循环使用另一个程序(可能是用Pika 编写的python)从RabbitMQ获取消息。一旦收到消息,就发出一个消息docker run ...使用等待消息的Pika编写您自己的程序,然后使用python K8s客户端在Kubernetes中创建一个pod 。您还可以使用具有RabbitMQ,Docker和/或Kubernetes客户端API的任何其他语言。
k8s小能手 2019-12-02 01:52:02 0 浏览量 回答数 0

回答

许多Python初学者都会问:我应该学习哪个版本的Python。对于这个问题,我的回答通常是“先选择一个最适合你的Python教程,教程中使用哪个版本的Python,你就用那个版本。等学得差不多了,再来研究不同版本之间的差别”。 许多Python初学者都会问:我应该学习哪个版本的Python。对于这个问题,我的回答通常是“先选择一个最适合你的Python教程,教程中使用哪个版本的Python,你就用那个版本。等学得差不多了,再来研究不同版本之间的差别”。但如果想要用Python开发一个新项目,那么该如何选择Python版本呢?我可以负责任的说,大部分Python库都同时支持Python 2.7.x和3.x版本的,所以不论选择哪个版本都是可以的。但为了在使用Python时避开某些版本中一些常见的陷阱,或需要移植某个Python项目时,依然有必要了解一下Python两个常见版本之间的主要区别。__future__模块Python 3.x引入了一些与Python 2不兼容的关键字和特性,在Python 2中,可以通过内置的__future__模块导入这些新内容。如果你希望在Python 2环境下写的代码也可以在Python 3.x中运行,那么建议使用__future__模块。例如,如果希望在Python 2中拥有Python 3.x的整数除法行为,可以通过下面的语句导入相应的模块。from future import division 下表列出了__future__中其他可导入的特性:特性 可选版本 强制版本 效果nested_scopes 2.1.0b1 2.2 PEP 227:Statically Nested Scopesgenerators 2.2.0a1 2.3 PEP 255:Simple Generatorsdivision 2.2.0a2 3.0 PEP 238:Changing the Division Operatorabsolute_import 2.5.0a1 3.0 PEP 328:Imports: Multi-Line and Absolute/Relativewith_statement 2.5.0a1 2.6 PEP 343:The “with” Statementprint_function 2.6.0a2 3.0 PEP 3105:Make print a functionunicode_literals 2.6.0a2 3.0 PEP 3112:Bytes literals in Python 3000(来源: https://docs.python.org/2/library/future.html)示例:from platform import python_version print函数虽然print语法是Python 3中一个很小的改动,且应该已经广为人知,但依然值得提一下:Python 2中的print语句被Python 3中的print()函数取代,这意味着在Python 3中必须用括号将需要输出的对象括起来。在Python 2中使用额外的括号也是可以的。但反过来在Python 3中想以Python2的形式不带括号调用print函数时,会触发SyntaxError。Python 2print 'Python', python_version() print 'Hello, World!' print('Hello, World!') print "text", ; print 'print more text on the same line' Python 2.7.6 Hello, World! Hello, World! text print more text on the same line Python 3print('Python', python_version()) print('Hello, World!') print("some text,", end="") print(' print more text on the same line') Python 3.4.1 Hello, World! some text, print more text on the same line print 'Hello, World!' File "", line 1 print 'Hello, World!' ^ SyntaxError: invalid syntax 注意:在Python中,带不带括号输出”Hello World”都很正常。但如果在圆括号中同时输出多个对象时,就会创建一个元组,这是因为在Python 2中,print是一个语句,而不是函数调用。print 'Python', python_version() print('a', 'b') print 'a', 'b' Python 2.7.7 ('a', 'b') a b 整数除法由于人们常常会忽视Python 3在整数除法上的改动(写错了也不会触发Syntax Error),所以在移植代码或在Python 2中执行Python 3的代码时,需要特别注意这个改动。所以,我还是会在Python 3的脚本中尝试用float(3)/2或 3/2.0代替3/2,以此来避免代码在Python 2环境下可能导致的错误(或与之相反,在Python 2脚本中用from future import division来使用Python 3的除法)。Python 2print 'Python', python_version() print '3 / 2 =', 3 / 2 print '3 // 2 =', 3 // 2 print '3 / 2.0 =', 3 / 2.0 print '3 // 2.0 =', 3 // 2.0 Python 2.7.6 3 / 2 = 1 3 // 2 = 1 3 / 2.0 = 1.5 3 // 2.0 = 1.0 Python 3print('Python', python_version()) print('3 / 2 =', 3 / 2) print('3 // 2 =', 3 // 2) print('3 / 2.0 =', 3 / 2.0) print('3 // 2.0 =', 3 // 2.0) Python 3.4.1 3 / 2 = 1.5 3 // 2 = 1 3 / 2.0 = 1.5 3 // 2.0 = 1.0 UnicodePython 2有基于ASCII的str()类型,其可通过单独的unicode()函数转成unicode类型,但没有byte类型。而在Python 3中,终于有了Unicode(utf-8)字符串,以及两个字节类:bytes和bytearrays。Python 2print 'Python', python_version() Python 2.7.6 print type(unicode('this is like a python3 str type')) print type(b'byte type does not exist') print 'they are really' + b' the same' they are really the same print type(bytearray(b'bytearray oddly does exist though')) Python 3print('Python', python_version()) print('strings are now utf-8 u03BCnicou0394é!') Python 3.4.1 strings are now utf-8 μnicoΔé! print('Python', python_version(), end="") print(' has', type(b' bytes for storing data')) Python 3.4.1 has print('and Python', python_version(), end="") print(' also has', type(bytearray(b'bytearrays'))) and Python 3.4.1 also has 'note that we cannot add a string' + b'bytes for data' TypeError Traceback (most recent call last) in () ----> 1 'note that we cannot add a string' + b'bytes for data' TypeError: Can't convert 'bytes' object to str implicitly xrange在Python 2.x中,经常会用xrange()创建一个可迭代对象,通常出现在“for循环”或“列表/集合/字典推导式”中。这种行为与生成器非常相似(如”惰性求值“),但这里的xrange-iterable无尽的,意味着可能在这个xrange上无限迭代。由于xrange的“惰性求知“特性,如果只需迭代一次(如for循环中),range()通常比xrange()快一些。不过不建议在多次迭代中使用range(),因为range()每次都会在内存中重新生成一个列表。在Python 3中,range()的实现方式与xrange()函数相同,所以就不存在专用的xrange()(在Python 3中使用xrange()会触发NameError)。import timeit n = 10000 def test_range(n): return for i in range(n): pass def test_xrange(n): for i in xrange(n): pass Python 2print 'Python', python_version() print 'ntiming range()' %timeit test_range(n) print 'nntiming xrange()' %timeit test_xrange(n) Python 2.7.6 timing range() 1000 loops, best of 3: 433 µs per loop timing xrange() 1000 loops, best of 3: 350 µs per loop Python 3print('Python', python_version()) print('ntiming range()') %timeit test_range(n) Python 3.4.1 timing range() 1000 loops, best of 3: 520 µs per loop print(xrange(10)) NameError Traceback (most recent call last) in () ----> 1 print(xrange(10)) NameError: name 'xrange' is not defined Python 3中的range对象中的__contains__方法另一个值得一提的是,在Python 3.x中,range有了一个新的__contains__方法。__contains__方法可以有效的加快Python 3.x中整数和布尔型的“查找”速度。x = 10000000 def val_in_range(x, val): return val in range(x) def val_in_xrange(x, val): return val in xrange(x) print('Python', python_version()) assert(val_in_range(x, x/2) == True) assert(val_in_range(x, x//2) == True) %timeit val_in_range(x, x/2) %timeit val_in_range(x, x//2) Python 3.4.1 1 loops, best of 3: 742 ms per loop 1000000 loops, best of 3: 1.19 µs per loop 根据上面的timeit的结果,查找整数比查找浮点数要快大约6万倍。但由于Python 2.x中的range或xrange没有__contains__方法,所以在Python 2中的整数和浮点数的查找速度差别不大。print 'Python', python_version() assert(val_in_xrange(x, x/2.0) == True) assert(val_in_xrange(x, x/2) == True) assert(val_in_range(x, x/2) == True) assert(val_in_range(x, x//2) == True) %timeit val_in_xrange(x, x/2.0) %timeit val_in_xrange(x, x/2) %timeit val_in_range(x, x/2.0) %timeit val_in_range(x, x/2) Python 2.7.7 1 loops, best of 3: 285 ms per loop 1 loops, best of 3: 179 ms per loop 1 loops, best of 3: 658 ms per loop 1 loops, best of 3: 556 ms per loop 下面的代码证明了Python 2.x中没有__contain__方法:print('Python', python_version()) range.__contains__ Python 3.4.1 print('Python', python_version()) range.__contains__ Python 2.7.7 AttributeError Traceback (most recent call last) in () 1 print 'Python', python_version() ----> 2 range.__contains__ AttributeError: 'builtin_function_or_method' object has no attribute '__contains__' print('Python', python_version()) xrange.__contains__ Python 2.7.7 AttributeError Traceback (most recent call last) in () 1 print 'Python', python_version() ----> 2 xrange.__contains__ AttributeError: type object 'xrange' has no attribute '__contains__' 关于Python 2中xrange()与Python 3中range()之间的速度差异的一点说明:有读者指出了Python 3中的range()和Python 2中xrange()执行速度有差异。由于这两者的实现方式相同,因此理论上执行速度应该也是相同的。这里的速度差别仅仅是因为Python 3的总体速度就比Python 2慢。def test_while(): i = 0 while i < 20000: i += 1 return print('Python', python_version()) %timeit test_while() Python 3.4.1 %timeit test_while() 100 loops, best of 3: 2.68 ms per loop print 'Python', python_version() %timeit test_while() Python 2.7.6 1000 loops, best of 3: 1.72 ms per loop 触发异常Python 2支持新旧两种异常触发语法,而Python 3只接受带括号的的语法(不然会触发SyntaxError):Python 2print 'Python', python_version()Python 2.7.6 raise IOError, "file error" IOError Traceback (most recent call last) in ()----> 1 raise IOError, "file error" IOError: file error raise IOError("file error") IOError Traceback (most recent call last) in ()----> 1 raise IOError("file error") IOError: file errorPython 3print('Python', python_version())Python 3.4.1raise IOError, "file error"File "", line 1raise IOError, "file error"^SyntaxError: invalid syntaxThe proper way to raise an exception in Python 3:print('Python', python_version())raise IOError("file error")Python 3.4.1 OSError Traceback (most recent call last) in ()1 print('Python', python_version())----> 2 raise IOError("file error") OSError: file error异常处理Python 3中的异常处理也发生了一点变化。在Python 3中必须使用“as”关键字。Python 2print 'Python', python_version()try: let_us_cause_a_NameError except NameError, err: print err, '--> our error message' Python 2.7.6name 'let_us_cause_a_NameError' is not defined --> our error messagePython 3print('Python', python_version())try: let_us_cause_a_NameError except NameError as err: print(err, '--> our error message') Python 3.4.1name 'let_us_cause_a_NameError' is not defined --> our error messagenext()函数和.next()方法由于会经常用到next()(.next())函数(方法),所以还要提到另一个语法改动(实现方面也做了改动):在Python 2.7.5中,函数形式和方法形式都可以使用,而在Python 3中,只能使用next()函数(试图调用.next()方法会触发AttributeError)。Python 2print 'Python', python_version()my_generator = (letter for letter in 'abcdefg')next(my_generator)my_generator.next()Python 2.7.6'b'Python 3print('Python', python_version())my_generator = (letter for letter in 'abcdefg')next(my_generator)Python 3.4.1'a' my_generator.next() AttributeError Traceback (most recent call last) in ()----> 1 my_generator.next() AttributeError: 'generator' object has no attribute 'next'For循环变量与全局命名空间泄漏好消息是:在Python 3.x中,for循环中的变量不再会泄漏到全局命名空间中了!这是Python 3.x中做的一个改动,在“What’s New In Python 3.0”中有如下描述:“列表推导不再支持[… for var in item1, item2, …]这样的语法,使用[… for var in (item1, item2, …)]代替。还要注意列表推导有不同的语义:现在列表推导更接近list()构造器中的生成器表达式这样的语法糖,特别要注意的是,循环控制变量不会再泄漏到循环周围的空间中了。”Python 2print 'Python', python_version() i = 1print 'before: i =', i print 'comprehension: ', [i for i in range(5)] print 'after: i =', iPython 2.7.6before: i = 1comprehension: [0, 1, 2, 3, 4]after: i = 4Python 3print('Python', python_version()) i = 1print('before: i =', i) print('comprehension:', [i for i in range(5)]) print('after: i =', i)Python 3.4.1before: i = 1comprehension: [0, 1, 2, 3, 4]after: i = 1比较无序类型Python 3中另一个优秀的改动是,如果我们试图比较无序类型,会触发一个TypeError。Python 2print 'Python', python_version()print "[1, 2] > 'foo' = ", [1, 2] > 'foo'print "(1, 2) > 'foo' = ", (1, 2) > 'foo'print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2)Python 2.7.6[1, 2] > 'foo' = False(1, 2) > 'foo' = True[1, 2] > (1, 2) = FalsePython 3print('Python', python_version())print("[1, 2] > 'foo' = ", [1, 2] > 'foo')print("(1, 2) > 'foo' = ", (1, 2) > 'foo')print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2)) Python 3.4.1 TypeError Traceback (most recent call last) in ()1 print('Python', python_version())----> 2 print("[1, 2] > 'foo' = ", [1, 2] > 'foo')3 print("(1, 2) > 'foo' = ", (1, 2) > 'foo')4 print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))TypeError: unorderable types: list() > str()通过input()解析用户的输入幸运的是,Python 3改进了input()函数,这样该函数就会总是将用户的输入存储为str对象。在Python 2中,为了避免读取非字符串类型会发生的一些危险行为,不得不使用raw_input()代替input()。Python 2Python 2.7.6[GCC 4.0.1 (Apple Inc. build 5493)] on darwinType "help", "copyright", "credits" or "license" for more information. my_input = input('enter a number: ') enter a number: 123 type(my_input) my_input = raw_input('enter a number: ') enter a number: 123 type(my_input) Python 3Python 3.4.1[GCC 4.2.1 (Apple Inc. build 5577)] on darwinType "help", "copyright", "credits" or "license" for more information. my_input = input('enter a number: ') enter a number: 123 type(my_input) 返回可迭代对象,而不是列表在xrange一节中可以看到,某些函数和方法在Python中返回的是可迭代对象,而不像在Python 2中返回列表。由于通常对这些对象只遍历一次,所以这种方式会节省很多内存。然而,如果通过生成器来多次迭代这些对象,效率就不高了。此时我们的确需要列表对象,可以通过list()函数简单的将可迭代对象转成列表。Python 2print 'Python', python_version() print range(3)print type(range(3))Python 2.7.6[0, 1, 2]Python 3print('Python', python_version())print(range(3))print(type(range(3)))print(list(range(3)))Python 3.4.1range(0, 3)[0, 1, 2]下面列出了Python 3中其他不再返回列表的常用函数和方法:zip()map()filter()字典的.key()方法字典的.value()方法字典的.item()方法 __future__模块 [回到目录] Python 3.x引入了一些与Python 2不兼容的关键字和特性,在Python 2中,可以通过内置的__future__模块导入这些新内容。如果你希望在Python 2环境下写的代码也可以在Python 3.x中运行,那么建议使用__future__模块。例如,如果希望在Python 2中拥有Python 3.x的整数除法行为,可以通过下面的语句导入相应的模块。 ? 1 from future import division 下表列出了__future__中其他可导入的特性: 特性 可选版本 强制版本 效果 nested_scopes 2.1.0b1 2.2 PEP 227:Statically Nested Scopes generators 2.2.0a1 2.3 PEP 255:Simple Generators division 2.2.0a2 3.0 PEP 238:Changing the Division Operator absolute_import 2.5.0a1 3.0 PEP 328:Imports: Multi-Line and Absolute/Relative with_statement 2.5.0a1 2.6 PEP 343:The “with” Statement print_function 2.6.0a2 3.0 PEP 3105:Make print a function unicode_literals 2.6.0a2 3.0 PEP 3112:Bytes literals in Python 3000 (来源: https://docs.python.org/2/library/future.html) 示例: ? 1 from platform import python_version print函数 [回到目录] 虽然print语法是Python 3中一个很小的改动,且应该已经广为人知,但依然值得提一下:Python 2中的print语句被Python 3中的print()函数取代,这意味着在Python 3中必须用括号将需要输出的对象括起来。 在Python 2中使用额外的括号也是可以的。但反过来在Python 3中想以Python2的形式不带括号调用print函数时,会触发SyntaxError。 Python 2 ? 1234 print 'Python', python_version()print 'Hello, World!'print('Hello, World!')print "text", ; print 'print more text on the same line' ? 1234 Python 2.7.6Hello, World!Hello, World!text print more text on the same line Python 3 ? 12345 print('Python', python_version())print('Hello, World!') print("some text,", end="") print(' print more text on the same line') ? 123 Python 3.4.1Hello, World!some text, print more text on the same line ? 1 print 'Hello, World!' ? File "", line 1print 'Hello, World!'^SyntaxError: invalid syntax 注意: 在Python中,带不带括号输出”Hello World”都很正常。但如果在圆括号中同时输出多个对象时,就会创建一个元组,这是因为在Python 2中,print是一个语句,而不是函数调用。 ? 123 print 'Python', python_version()print('a', 'b')print 'a', 'b' Python 2.7.7('a', 'b')a b 整数除法 [回到目录] 由于人们常常会忽视Python 3在整数除法上的改动(写错了也不会触发Syntax Error),所以在移植代码或在Python 2中执行Python 3的代码时,需要特别注意这个改动。 所以,我还是会在Python 3的脚本中尝试用float(3)/2或 3/2.0代替3/2,以此来避免代码在Python 2环境下可能导致的错误(或与之相反,在Python 2脚本中用from future import division来使用Python 3的除法)。 Python 2 ? 12345 print 'Python', python_version()print '3 / 2 =', 3 / 2print '3 // 2 =', 3 // 2print '3 / 2.0 =', 3 / 2.0print '3 // 2.0 =', 3 // 2.0 Python 2.7.63 / 2 = 13 // 2 = 13 / 2.0 = 1.53 // 2.0 = 1.0 Python 3 ? 12345 print('Python', python_version())print('3 / 2 =', 3 / 2)print('3 // 2 =', 3 // 2)print('3 / 2.0 =', 3 / 2.0)print('3 // 2.0 =', 3 // 2.0) Python 3.4.13 / 2 = 1.53 // 2 = 13 / 2.0 = 1.53 // 2.0 = 1.0 Unicode [回到目录] Python 2有基于ASCII的str()类型,其可通过单独的unicode()函数转成unicode类型,但没有byte类型。 而在Python 3中,终于有了Unicode(utf-8)字符串,以及两个字节类:bytes和bytearrays。 Python 2 ? 1 print 'Python', python_version() Python 2.7.6 ? 1 print type(unicode('this is like a python3 str type')) ? 1 print type(b'byte type does not exist') ? 1 print 'they are really' + b' the same' they are really the same ? 1 print type(bytearray(b'bytearray oddly does exist though')) Python 3 ? 12 print('Python', python_version())print('strings are now utf-8 u03BCnicou0394é!') Python 3.4.1strings are now utf-8 μnicoΔé! ? 12 print('Python', python_version(), end="")print(' has', type(b' bytes for storing data')) Python 3.4.1 has ? 12 print('and Python', python_version(), end="")print(' also has', type(bytearray(b'bytearrays'))) and Python 3.4.1 also has ? 1 'note that we cannot add a string' + b'bytes for data' TypeError Traceback (most recent call last) in ()----> 1 'note that we cannot add a string' + b'bytes for data' TypeError: Can't convert 'bytes' object to str implicitly xrange [回到目录] 在Python 2.x中,经常会用xrange()创建一个可迭代对象,通常出现在“for循环”或“列表/集合/字典推导式”中。 这种行为与生成器非常相似(如”惰性求值“),但这里的xrange-iterable无尽的,意味着可能在这个xrange上无限迭代。 由于xrange的“惰性求知“特性,如果只需迭代一次(如for循环中),range()通常比xrange()快一些。不过不建议在多次迭代中使用range(),因为range()每次都会在内存中重新生成一个列表。 在Python 3中,range()的实现方式与xrange()函数相同,所以就不存在专用的xrange()(在Python 3中使用xrange()会触发NameError)。 ? 12345678910 import timeit n = 10000def test_range(n): return for i in range(n): pass def test_xrange(n): for i in xrange(n): pass Python 2 ? 1234567 print 'Python', python_version() print 'ntiming range()'%timeit test_range(n) print 'nntiming xrange()'%timeit test_xrange(n) Python 2.7.6 timing range()1000 loops, best of 3: 433 µs per loop timing xrange()1000 loops, best of 3: 350 µs per loop Python 3 ? 1234 print('Python', python_version()) print('ntiming range()')%timeit test_range(n) Python 3.4.1 timing range()1000 loops, best of 3: 520 µs per loop ? 1 print(xrange(10)) NameError Traceback (most recent call last)in ()----> 1 print(xrange(10)) NameError: name 'xrange' is not defined Python 3中的range对象中的__contains__方法 另一个值得一提的是,在Python 3.x中,range有了一个新的__contains__方法。__contains__方法可以有效的加快Python 3.x中整数和布尔型的“查找”速度。 ? 123456789101112 x = 10000000def val_in_range(x, val): return val in range(x) def val_in_xrange(x, val): return val in xrange(x) print('Python', python_version())assert(val_in_range(x, x/2) == True)assert(val_in_range(x, x//2) == True)%timeit val_in_range(x, x/2)%timeit val_in_range(x, x//2) Python 3.4.11 loops, best of 3: 742 ms per loop1000000 loops, best of 3: 1.19 µs per loop 根据上面的timeit的结果,查找整数比查找浮点数要快大约6万倍。但由于Python 2.x中的range或xrange没有__contains__方法,所以在Python 2中的整数和浮点数的查找速度差别不大。 ? 12345678910 print 'Python', python_version() assert(val_in_xrange(x, x/2.0) == True)assert(val_in_xrange(x, x/2) == True)assert(val_in_range(x, x/2) == True)assert(val_in_range(x, x//2) == True)%timeit val_in_xrange(x, x/2.0)%timeit val_in_xrange(x, x/2)%timeit val_in_range(x, x/2.0)%timeit val_in_range(x, x/2) Python 2.7.71 loops, best of 3: 285 ms per loop1 loops, best of 3: 179 ms per loop1 loops, best of 3: 658 ms per loop1 loops, best of 3: 556 ms per loop 下面的代码证明了Python 2.x中没有__contain__方法: ? 12 print('Python', python_version())range.__contains__ Python 3.4.1 ? 12 print('Python', python_version())range.__contains__ Python 2.7.7 AttributeError Traceback (most recent call last) in ()1 print 'Python', python_version()----> 2 range.__contains__ AttributeError: 'builtin_function_or_method' object has no attribute '__contains__' ? 12 print('Python', python_version())xrange.__contains__ Python 2.7.7 AttributeError Traceback (most recent call last)in ()1 print 'Python', python_version()----> 2 xrange.__contains__ AttributeError: type object 'xrange' has no attribute '__contains__' 关于Python 2中xrange()与Python 3中range()之间的速度差异的一点说明: 有读者指出了Python 3中的range()和Python 2中xrange()执行速度有差异。由于这两者的实现方式相同,因此理论上执行速度应该也是相同的。这里的速度差别仅仅是因为Python 3的总体速度就比Python 2慢。 ? 12345 def test_while(): i = 0 while i < 20000: i += 1 return ? 12 print('Python', python_version())%timeit test_while() Python 3.4.1%timeit test_while()100 loops, best of 3: 2.68 ms per loop ? 12 print 'Python', python_version()%timeit test_while() Python 2.7.61000 loops, best of 3: 1.72 ms per loop 触发异常 [回到目录] Python 2支持新旧两种异常触发语法,而Python 3只接受带括号的的语法(不然会触发SyntaxError): Python 2 ? 1 print 'Python', python_version() Python 2.7.6 ? 1 raise IOError, "file error" IOError Traceback (most recent call last) in ()----> 1 raise IOError, "file error" IOError: file error ? 1 raise IOError("file error") IOError Traceback (most recent call last) in ()----> 1 raise IOError("file error") IOError: file error Python 3 ? 1 print('Python', python_version()) Python 3.4.1 ? 1 raise IOError, "file error" File "", line 1raise IOError, "file error"^SyntaxError: invalid syntaxThe proper way to raise an exception in Python 3: ? 12 print('Python', python_version())raise IOError("file error") Python 3.4.1 OSError Traceback (most recent call last) in ()1 print('Python', python_version())----> 2 raise IOError("file error") OSError: file error 异常处理 [回到目录] Python 3中的异常处理也发生了一点变化。在Python 3中必须使用“as”关键字。 Python 2 ? 12345 print 'Python', python_version()try: let_us_cause_a_NameErrorexcept NameError, err: print err, '--> our error message' Python 2.7.6name 'let_us_cause_a_NameError' is not defined --> our error message Python 3 ? 12345 print('Python', python_version())try: let_us_cause_a_NameErrorexcept NameError as err: print(err, '--> our error message') Python 3.4.1name 'let_us_cause_a_NameError' is not defined --> our error message next()函数和.next()方法 [回到目录] 由于会经常用到next()(.next())函数(方法),所以还要提到另一个语法改动(实现方面也做了改动):在Python 2.7.5中,函数形式和方法形式都可以使用,而在Python 3中,只能使用next()函数(试图调用.next()方法会触发AttributeError)。 Python 2 ? 1234 print 'Python', python_version()my_generator = (letter for letter in 'abcdefg')next(my_generator)my_generator.next() Python 2.7.6'b' Python 3 ? 123 print('Python', python_version())my_generator = (letter for letter in 'abcdefg')next(my_generator) Python 3.4.1'a' ? 1 my_generator.next() AttributeError Traceback (most recent call last) in ()----> 1 my_generator.next() AttributeError: 'generator' object has no attribute 'next' For循环变量与全局命名空间泄漏 [回到目录] 好消息是:在Python 3.x中,for循环中的变量不再会泄漏到全局命名空间中了! 这是Python 3.x中做的一个改动,在“What's New In Python 3.0”中有如下描述: “列表推导不再支持[... for var in item1, item2, ...]这样的语法,使用[... for var in (item1, item2, ...)]代替。还要注意列表推导有不同的语义:现在列表推导更接近list()构造器中的生成器表达式这样的语法糖,特别要注意的是,循环控制变量不会再泄漏到循环周围的空间中了。” Python 2 ? 12345678 print 'Python', python_version() i = 1print 'before: i =', i print 'comprehension: ', [i for i in range(5)] print 'after: i =', i Python 2.7.6before: i = 1comprehension: [0, 1, 2, 3, 4]after: i = 4 Python 3 ? 12345678 print('Python', python_version()) i = 1print('before: i =', i) print('comprehension:', [i for i in range(5)]) print('after: i =', i) Python 3.4.1before: i = 1comprehension: [0, 1, 2, 3, 4]after: i = 1 比较无序类型 [回到目录] Python 3中另一个优秀的改动是,如果我们试图比较无序类型,会触发一个TypeError。 Python 2 ? 1234 print 'Python', python_version()print "[1, 2] > 'foo' = ", [1, 2] > 'foo'print "(1, 2) > 'foo' = ", (1, 2) > 'foo'print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2) Python 2.7.6[1, 2] > 'foo' = False(1, 2) > 'foo' = True[1, 2] > (1, 2) = False Python 3 ? 1234 print('Python', python_version())print("[1, 2] > 'foo' = ", [1, 2] > 'foo')print("(1, 2) > 'foo' = ", (1, 2) > 'foo')print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2)) Python 3.4.1 TypeError Traceback (most recent call last) in ()1 print('Python', python_version())----> 2 print("[1, 2] > 'foo' = ", [1, 2] > 'foo')3 print("(1, 2) > 'foo' = ", (1, 2) > 'foo')4 print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2))TypeError: unorderable types: list() > str() 通过input()解析用户的输入 [回到目录] 幸运的是,Python 3改进了input()函数,这样该函数就会总是将用户的输入存储为str对象。在Python 2中,为了避免读取非字符串类型会发生的一些危险行为,不得不使用raw_input()代替input()。 Python 2 ? 1234567891011121314151617 Python 2.7.6[GCC 4.0.1 (Apple Inc. build 5493)] on darwinType "help", "copyright", "credits" or "license" for more information. my_input = input('enter a number: ') enter a number: 123 type(my_input) my_input = raw_input('enter a number: ') enter a number: 123 type(my_input) Python 3 ? 12345678 Python 3.4.1[GCC 4.2.1 (Apple Inc. build 5577)] on darwinType "help", "copyright", "credits" or "license" for more information. my_input = input('enter a number: ') enter a number: 123 type(my_input) 返回可迭代对象,而不是列表 [回到目录] 在xrange一节中可以看到,某些函数和方法在Python中返回的是可迭代对象,而不像在Python 2中返回列表。 由于通常对这些对象只遍历一次,所以这种方式会节省很多内存。然而,如果通过生成器来多次迭代这些对象,效率就不高了。 此时我们的确需要列表对象,可以通过list()函数简单的将可迭代对象转成列表。 Python 2 ? 1234 print 'Python', python_version() print range(3)print type(range(3)) Python 2.7.6[0, 1, 2] Python 3 ? 1234 print('Python', python_version())print(range(3))print(type(range(3)))print(list(range(3))) Python 3.4.1range(0, 3)[0, 1, 2] 下面列出了Python 3中其他不再返回列表的常用函数和方法:•zip()•map()•filter()•字典的.key()方法•字典的.value()方法•字典的.item()方法 更多关于Python 2和Python 3的文章 [回到目录] 下面列出了其他一些可以进一步了解Python 2和Python 3的优秀文章, //迁移到 Python 3•Should I use Python 2 or Python 3 for my development activity?•What's New In Python 3.0•Porting to Python 3•Porting Python 2 Code to Python 3•How keep Python 3 moving forward // 对Python 3的褒与贬•10 awesome features of Python that you can't use because you refuse to upgrade to Python 3•关于你不想知道的所有Python3 unicode特性•Python 3 正在毁灭 Python•Python 3 能振兴 Python•Python 3 is fine
xuning715 2019-12-02 01:10:35 0 浏览量 回答数 0

回答

可以说 Python 没有赋值,只有引用。你这样相当于创建了一个引用自身的结构,所以导致了无限循环。为了理解这个问题,有个基本概念需要搞清楚。 Python 没有「变量」,我们平时所说的变量其实只是「标签」,是引用。 执行:values=[0,1,2]的时候,python做的事情是首先创建一个列表对象[0,1,2],然后给它贴上名为values的标签。如果随后执行values=[3,4,5] 的话,python做的事情是创建另一个列表对象[3,4,5],然后把刚才那张名为values的标签从前面的[0,1,2]对象上撕下来,重新贴到[3,4,5]这个对象上。
游客h2jwb5k5qe2pa 2020-03-11 11:23:31 0 浏览量 回答数 0

问题

【精品问答】Python二级考试题库

1.关于数据的存储结构,以下选项描述正确的是( D ) A: 数据所占的存储空间量 B: 存储在外存中的数据 C: 数据在计算机中的顺序存储方式 D: 数据的逻辑结构在计算机中的表示 2.关于线性...
珍宝珠 2019-12-01 22:03:38 7177 浏览量 回答数 3

回答

是的你可以。我使用ActivePython随附的pythoncom库或可以与pywin32(适用于Windows的Python扩展)一起安装的方式来执行此操作。 这是简单服务的基本框架: import win32serviceutil import win32service import win32event import servicemanager import socket class AppServerSvc (win32serviceutil.ServiceFramework): _svc_name_ = "TestService" _svc_display_name_ = "Test Service" def __init__(self,args): win32serviceutil.ServiceFramework.__init__(self,args) self.hWaitStop = win32event.CreateEvent(None,0,0,None) socket.setdefaulttimeout(60) def SvcStop(self): self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) win32event.SetEvent(self.hWaitStop) def SvcDoRun(self): servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_,'')) self.main() def main(self): pass if __name__ == '__main__': win32serviceutil.HandleCommandLine(AppServerSvc) 您的代码将进入main()方法中-通常带有某种无限循环,可能会通过检查在SvcStop方法中设置的标志而中断
祖安文状元 2020-02-22 18:18:58 0 浏览量 回答数 0

回答

在批评Python的讨论中,常常说起Python多线程是多么的难用。还有人对 global interpreter lock(也被亲切的称为“GIL”)指指点点,说它阻碍了Python的多线程程序同时运行。因此,如果你是从其他语言(比如C++或Java)转过来的话,Python线程模块并不会像你想象的那样去运行。必须要说明的是,我们还是可以用Python写出能并发或并行的代码,并且能带来性能的显著提升,只要你能顾及到一些事情。如果你还没看过的话,我建议你看看Eqbal Quran的文章《Ruby中的并发和并行》。 在本文中,我们将会写一个小的Python脚本,用于下载Imgur上最热门的图片。我们将会从一个按顺序下载图片的版本开始做起,即一个一个地下载。在那之前,你得注册一个Imgur上的应用。如果你还没有Imgur账户,请先注册一个。 本文中的脚本在Python3.4.2中测试通过。稍微改一下,应该也能在Python2中运行——urllib是两个版本中区别最大的部分。 1、开始动手让我们从创建一个叫“download.py”的Python模块开始。这个文件包含了获取图片列表以及下载这些图片所需的所有函数。我们将这些功能分成三个单独的函数: get_links download_link setup_download_dir 第三个函数,“setup_download_dir”,用于创建下载的目标目录(如果不存在的话)。 Imgur的API要求HTTP请求能支持带有client ID的“Authorization”头部。你可以从你注册的Imgur应用的面板上找到这个client ID,而响应会以JSON进行编码。我们可以使用Python的标准JSON库去解码。下载图片更简单,你只需要根据它们的URL获取图片,然后写入到一个文件即可。 代码如下: import jsonimport loggingimport osfrom pathlib import Pathfrom urllib.request import urlopen, Request logger = logging.getLogger(__name__) def get_links(client_id): headers = {'Authorization': 'Client-ID {}'.format(client_id)} req = Request('https://api.imgur.com/3/gallery/', headers=headers, method='GET') with urlopen(req) as resp: data = json.loads(resp.readall().decode('utf-8')) return map(lambda item: item['link'], data['data']) def download_link(directory, link): logger.info('Downloading %s', link) download_path = directory / os.path.basename(link) with urlopen(link) as image, download_path.open('wb') as f: f.write(image.readall()) def setup_download_dir(): download_dir = Path('images') if not download_dir.exists(): download_dir.mkdir() return download_dir接下来,你需要写一个模块,利用这些函数去逐个下载图片。我们给它命名为“single.py”。它包含了我们最原始版本的Imgur图片下载器的主要函数。这个模块将会通过环境变量“IMGUR_CLIENT_ID”去获取Imgur的client ID。它将会调用“setup_download_dir”去创建下载目录。最后,使用get_links函数去获取图片的列表,过滤掉所有的GIF和专辑URL,然后用“download_link”去将图片下载并保存在磁盘中。下面是“single.py”的代码: import loggingimport osfrom time import time from download import setup_download_dir, get_links, download_link logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')logging.getLogger('requests').setLevel(logging.CRITICAL)logger = logging.getLogger(__name__) def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception("Couldn't find IMGUR_CLIENT_ID environment variable!") download_dir = setup_download_dir() links = [l for l in get_links(client_id) if l.endswith('.jpg')] for link in links: download_link(download_dir, link) print('Took {}s'.format(time() - ts)) if name == '__main__': main()注:为了测试方便,上面两段代码可以用如下代码替代演示: coding=utf-8 测试utf-8编码 from time import sleep, timeimport sys, threading reload(sys)sys.setdefaultencoding('utf-8') def getNums(N): return xrange(N) def processNum(num): num_add = num + 1 sleep(1) print str(threading.current_thread()) + ": " + str(num) + " → " + str(num_add) if name == "__main__": t1 = time() for i in getNums(3): processNum(i) print "cost time is: {:.2f}s".format(time() - t1) 结果: <_MainThread(MainThread, started 4436)>: 0 → 1<_MainThread(MainThread, started 4436)>: 1 → 2<_MainThread(MainThread, started 4436)>: 2 → 3cost time is: 3.00s在我的笔记本上,这个脚本花了19.4秒去下载91张图片。请注意这些数字在不同的网络上也会有所不同。19.4秒并不是非常的长,但是如果我们要下载更多的图片怎么办呢?或许是900张而不是90张。平均下载一张图片要0.2秒,900张的话大概需要3分钟。那么9000张图片将会花掉30分钟。好消息是使用了并发或者并行后,我们可以将这个速度显著地提高。 接下来的代码示例将只会显示导入特有模块和新模块的import语句。所有相关的Python脚本都可以在这方便地找到this GitHub repository。 2、使用线程线程是最出名的实现并发和并行的方式之一。操作系统一般提供了线程的特性。线程比进程要小,而且共享同一块内存空间。 在这里,我们将写一个替代“single.py”的新模块。它将创建一个有八个线程的池,加上主线程的话总共就是九个线程。之所以是八个线程,是因为我的电脑有8个CPU内核,而一个工作线程对应一个内核看起来还不错。在实践中,线程的数量是仔细考究的,需要考虑到其他的因素,比如在同一台机器上跑的的其他应用和服务。 下面的脚本几乎跟之前的一样,除了我们现在有个新的类,DownloadWorker,一个Thread类的子类。运行无限循环的run方法已经被重写。在每次迭代时,它调用“self.queue.get()”试图从一个线程安全的队列里获取一个URL。它将会一直堵塞,直到队列中出现一个要处理元素。一旦工作线程从队列中得到一个元素,它将会调用之前脚本中用来下载图片到目录中所用到的“download_link”方法。下载完成之后,工作线程向队列发送任务完成的信号。这非常重要,因为队列一直在跟踪队列中的任务数。如果工作线程没有发出任务完成的信号,“queue.join()”的调用将会令整个主线程都在阻塞状态。 from queue import Queuefrom threading import Thread class DownloadWorker(Thread): def __init__(self, queue): Thread.__init__(self) self.queue = queue def run(self): while True: # Get the work from the queue and expand the tuple # 从队列中获取任务并扩展tuple directory, link = self.queue.get() download_link(directory, link) self.queue.task_done() def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception("Couldn't find IMGUR_CLIENT_ID environment variable!") download_dir = setup_download_dir() links = [l for l in get_links(client_id) if l.endswith('.jpg')] # Create a queue to communicate with the worker threads queue = Queue() # Create 8 worker threads # 创建八个工作线程 for x in range(8): worker = DownloadWorker(queue) # Setting daemon to True will let the main thread exit even though the workers are blocking # 将daemon设置为True将会使主线程退出,即使worker都阻塞了 worker.daemon = True worker.start() # Put the tasks into the queue as a tuple # 将任务以tuple的形式放入队列中 for link in links: logger.info('Queueing {}'.format(link)) queue.put((download_dir, link)) # Causes the main thread to wait for the queue to finish processing all the tasks # 让主线程等待队列完成所有的任务 queue.join() print('Took {}'.format(time() - ts))注:为了测试方便,上面的代码可以用如下代码替代演示: coding=utf-8 测试utf-8编码 from Queue import Queuefrom threading import Threadfrom single import *import sys reload(sys)sys.setdefaultencoding('utf-8') class ProcessWorker(Thread): def __init__(self, queue): Thread.__init__(self) self.queue = queue def run(self): while True: # Get the work from the queue num = self.queue.get() processNum(num) self.queue.task_done() def main(): ts = time() nums = getNums(4) # Create a queue to communicate with the worker threads queue = Queue() # Create 4 worker threads # 创建四个工作线程 for x in range(4): worker = ProcessWorker(queue) # Setting daemon to True will let the main thread exit even though the workers are blocking # 将daemon设置为True将会使主线程退出,即使worker都阻塞了 worker.daemon = True worker.start() # Put the tasks into the queue for num in nums: queue.put(num) # Causes the main thread to wait for the queue to finish processing all the tasks # 让主线程等待队列完成所有的任务 queue.join() print("cost time is: {:.2f}s".format(time() - ts)) if name == "__main__": main() 结果: : 3 → 4: 2 → 3: 1 → 2 : 0 → 1cost time is: 1.01s在同一个机器上运行这个脚本,下载时间变成了4.1秒!即比之前的例子快4.7倍。虽然这快了很多,但还是要提一下,由于GIL的缘故,在这个进程中同一时间只有一个线程在运行。因此,这段代码是并发的但不是并行的。而它仍然变快的原因是这是一个IO密集型的任务。进程下载图片时根本毫不费力,而主要的时间都花在了等待网络上。这就是为什么线程可以提供很大的速度提升。每当线程中的一个准备工作时,进程可以不断转换线程。使用Python或其他有GIL的解释型语言中的线程模块实际上会降低性能。如果你的代码执行的是CPU密集型的任务,例如解压gzip文件,使用线程模块将会导致执行时间变长。对于CPU密集型任务和真正的并行执行,我们可以使用多进程(multiprocessing)模块。 官方的Python实现——CPython——带有GIL,但不是所有的Python实现都是这样的。比如,IronPython,使用.NET框架实现的Python就没有GIL,基于Java实现的Jython也同样没有。你可以点这查看现有的Python实现。 3、生成多进程多进程模块比线程模块更易使用,因为我们不需要像线程示例那样新增一个类。我们唯一需要做的改变在主函数中。 为了使用多进程,我们得建立一个多进程池。通过它提供的map方法,我们把URL列表传给池,然后8个新进程就会生成,它们将并行地去下载图片。这就是真正的并行,不过这是有代价的。整个脚本的内存将会被拷贝到各个子进程中。在我们的例子中这不算什么,但是在大型程序中它很容易导致严重的问题。 from functools import partialfrom multiprocessing.pool import Pool def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception("Couldn't find IMGUR_CLIENT_ID environment variable!") download_dir = setup_download_dir() links = [l for l in get_links(client_id) if l.endswith('.jpg')] download = partial(download_link, download_dir) with Pool(8) as p: p.map(download, links) print('Took {}s'.format(time() - ts))注:为了测试方便,上面的代码可以用如下代码替代演示: coding=utf-8 测试utf-8编码 from functools import partialfrom multiprocessing.pool import Poolfrom single import *from time import time import sys reload(sys)sys.setdefaultencoding('utf-8') def main(): ts = time() nums = getNums(4) p = Pool(4) p.map(processNum, nums) print("cost time is: {:.2f}s".format(time() - ts)) if name == "__main__": main() 结果: <_MainThread(MainThread, started 6188)>: 0 → 1<_MainThread(MainThread, started 3584)>: 1 → 2<_MainThread(MainThread, started 2572)>: 3 → 4<_MainThread(MainThread, started 4692)>: 2 → 3 cost time is: 1.21s4、分布式任务你已经知道了线程和多进程模块可以给你自己的电脑跑脚本时提供很大的帮助,那么在你想要在不同的机器上执行任务,或者在你需要扩大规模而超过一台机器的的能力范围时,你该怎么办呢?一个很好的使用案例是网络应用的长时间后台任务。如果你有一些很耗时的任务,你不会希望在同一台机器上占用一些其他的应用代码所需要的子进程或线程。这将会使你的应用的性能下降,影响到你的用户们。如果能在另外一台甚至很多台其他的机器上跑这些任务就好了。 Python库RQ非常适用于这类任务。它是一个简单却很强大的库。首先将一个函数和它的参数放入队列中。它将函数调用的表示序列化(pickle),然后将这些表示添加到一个Redis列表中。任务进入队列只是第一步,什么都还没有做。我们至少还需要一个能去监听任务队列的worker(工作线程)。 第一步是在你的电脑上安装和使用Redis服务器,或是拥有一台能正常的使用的Redis服务器的使用权。接着,对于现有的代码只需要一些小小的改动。先创建一个RQ队列的实例并通过redis-py 库传给一台Redis服务器。然后,我们执行“q.enqueue(download_link, download_dir, link)”,而不只是调用“download_link” 。enqueue方法的第一个参数是一个函数,当任务真正执行时,其他的参数或关键字参数将会传给该函数。 最后一步是启动一些worker。RQ提供了方便的脚本,可以在默认队列上运行起worker。只要在终端窗口中执行“rqworker”,就可以开始监听默认队列了。请确认你当前的工作目录与脚本所在的是同一个。如果你想监听别的队列,你可以执行“rqworker queue_name”,然后将会开始执行名为queue_name的队列。RQ的一个很好的点就是,只要你可以连接到Redis,你就可以在任意数量上的机器上跑起任意数量的worker;因此,它可以让你的应用扩展性得到提升。下面是RQ版本的代码: from redis import Redisfrom rq import Queue def main(): client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception("Couldn't find IMGUR_CLIENT_ID environment variable!") download_dir = setup_download_dir() links = [l for l in get_links(client_id) if l.endswith('.jpg')] q = Queue(connection=Redis(host='localhost', port=6379)) for link in links: q.enqueue(download_link, download_dir, link) 然而RQ并不是Python任务队列的唯一解决方案。RQ确实易用并且能在简单的案例中起到很大的作用,但是如果有更高级的需求,我们可以使用其他的解决方案(例如 Celery)。 5、总结如果你的代码是IO密集型的,线程和多进程可以帮到你。多进程比线程更易用,但是消耗更多的内存。如果你的代码是CPU密集型的,多进程就明显是更好的选择——特别是所使用的机器是多核或多CPU的。对于网络应用,在你需要扩展到多台机器上执行任务,RQ是更好的选择。 6、注:关于并发、并行区别与联系并发是指,程序在运行的过程中存在多于一个的执行上下文。这些执行上下文一般对应着不同的调用栈。 在单处理器上,并发程序虽然有多个上下文运行环境,但某一个时刻只有一个任务在运行。 但在多处理器上,因为有了多个执行单元,就可以同时有数个任务在跑。 这种物理上同一时刻有多个任务同时运行的方式就是并行。 和并发相比,并行更加强调多个任务同时在运行。 而且并行还有一个层次问题,比如是指令间的并行还是任务间的并行。
xuning715 2019-12-02 01:10:10 0 浏览量 回答数 0

回答

在批评Python的讨论中,常常说起Python多线程是多么的难用。还有人对 global interpreter lock(也被亲切的称为“GIL”)指指点点,说它阻碍了Python的多线程程序同时运行。因此,如果你是从其他语言(比如C++或Java)转过来的话,Python线程模块并不会像你想象的那样去运行。必须要说明的是,我们还是可以用Python写出能并发或并行的代码,并且能带来性能的显著提升,只要你能顾及到一些事情。如果你还没看过的话,我建议你看看Eqbal Quran的文章《Ruby中的并发和并行》。 在本文中,我们将会写一个小的Python脚本,用于下载Imgur上最热门的图片。我们将会从一个按顺序下载图片的版本开始做起,即一个一个地下载。在那之前,你得注册一个Imgur上的应用。如果你还没有Imgur账户,请先注册一个。 本文中的脚本在Python3.4.2中测试通过。稍微改一下,应该也能在Python2中运行——urllib是两个版本中区别最大的部分。 1、开始动手让我们从创建一个叫“download.py”的Python模块开始。这个文件包含了获取图片列表以及下载这些图片所需的所有函数。我们将这些功能分成三个单独的函数: get_links download_link setup_download_dir 第三个函数,“setup_download_dir”,用于创建下载的目标目录(如果不存在的话)。 Imgur的API要求HTTP请求能支持带有client ID的“Authorization”头部。你可以从你注册的Imgur应用的面板上找到这个client ID,而响应会以JSON进行编码。我们可以使用Python的标准JSON库去解码。下载图片更简单,你只需要根据它们的URL获取图片,然后写入到一个文件即可。 代码如下: import jsonimport loggingimport osfrom pathlib import Pathfrom urllib.request import urlopen, Request logger = logging.getLogger(__name__) def get_links(client_id): headers = {'Authorization': 'Client-ID {}'.format(client_id)} req = Request('https://api.imgur.com/3/gallery/', headers=headers, method='GET') with urlopen(req) as resp: data = json.loads(resp.readall().decode('utf-8')) return map(lambda item: item['link'], data['data']) def download_link(directory, link): logger.info('Downloading %s', link) download_path = directory / os.path.basename(link) with urlopen(link) as image, download_path.open('wb') as f: f.write(image.readall()) def setup_download_dir(): download_dir = Path('images') if not download_dir.exists(): download_dir.mkdir() return download_dir接下来,你需要写一个模块,利用这些函数去逐个下载图片。我们给它命名为“single.py”。它包含了我们最原始版本的Imgur图片下载器的主要函数。这个模块将会通过环境变量“IMGUR_CLIENT_ID”去获取Imgur的client ID。它将会调用“setup_download_dir”去创建下载目录。最后,使用get_links函数去获取图片的列表,过滤掉所有的GIF和专辑URL,然后用“download_link”去将图片下载并保存在磁盘中。下面是“single.py”的代码: import loggingimport osfrom time import time from download import setup_download_dir, get_links, download_link logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')logging.getLogger('requests').setLevel(logging.CRITICAL)logger = logging.getLogger(__name__) def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception("Couldn't find IMGUR_CLIENT_ID environment variable!") download_dir = setup_download_dir() links = [l for l in get_links(client_id) if l.endswith('.jpg')] for link in links: download_link(download_dir, link) print('Took {}s'.format(time() - ts)) if name == '__main__': main()注:为了测试方便,上面两段代码可以用如下代码替代演示: coding=utf-8 测试utf-8编码 from time import sleep, timeimport sys, threading reload(sys)sys.setdefaultencoding('utf-8') def getNums(N): return xrange(N) def processNum(num): num_add = num + 1 sleep(1) print str(threading.current_thread()) + ": " + str(num) + " → " + str(num_add) if name == "__main__": t1 = time() for i in getNums(3): processNum(i) print "cost time is: {:.2f}s".format(time() - t1) 结果: <_MainThread(MainThread, started 4436)>: 0 → 1<_MainThread(MainThread, started 4436)>: 1 → 2<_MainThread(MainThread, started 4436)>: 2 → 3cost time is: 3.00s在我的笔记本上,这个脚本花了19.4秒去下载91张图片。请注意这些数字在不同的网络上也会有所不同。19.4秒并不是非常的长,但是如果我们要下载更多的图片怎么办呢?或许是900张而不是90张。平均下载一张图片要0.2秒,900张的话大概需要3分钟。那么9000张图片将会花掉30分钟。好消息是使用了并发或者并行后,我们可以将这个速度显著地提高。 接下来的代码示例将只会显示导入特有模块和新模块的import语句。所有相关的Python脚本都可以在这方便地找到this GitHub repository。 2、使用线程线程是最出名的实现并发和并行的方式之一。操作系统一般提供了线程的特性。线程比进程要小,而且共享同一块内存空间。 在这里,我们将写一个替代“single.py”的新模块。它将创建一个有八个线程的池,加上主线程的话总共就是九个线程。之所以是八个线程,是因为我的电脑有8个CPU内核,而一个工作线程对应一个内核看起来还不错。在实践中,线程的数量是仔细考究的,需要考虑到其他的因素,比如在同一台机器上跑的的其他应用和服务。 下面的脚本几乎跟之前的一样,除了我们现在有个新的类,DownloadWorker,一个Thread类的子类。运行无限循环的run方法已经被重写。在每次迭代时,它调用“self.queue.get()”试图从一个线程安全的队列里获取一个URL。它将会一直堵塞,直到队列中出现一个要处理元素。一旦工作线程从队列中得到一个元素,它将会调用之前脚本中用来下载图片到目录中所用到的“download_link”方法。下载完成之后,工作线程向队列发送任务完成的信号。这非常重要,因为队列一直在跟踪队列中的任务数。如果工作线程没有发出任务完成的信号,“queue.join()”的调用将会令整个主线程都在阻塞状态。 from queue import Queuefrom threading import Thread class DownloadWorker(Thread): def __init__(self, queue): Thread.__init__(self) self.queue = queue def run(self): while True: # Get the work from the queue and expand the tuple # 从队列中获取任务并扩展tuple directory, link = self.queue.get() download_link(directory, link) self.queue.task_done() def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception("Couldn't find IMGUR_CLIENT_ID environment variable!") download_dir = setup_download_dir() links = [l for l in get_links(client_id) if l.endswith('.jpg')] # Create a queue to communicate with the worker threads queue = Queue() # Create 8 worker threads # 创建八个工作线程 for x in range(8): worker = DownloadWorker(queue) # Setting daemon to True will let the main thread exit even though the workers are blocking # 将daemon设置为True将会使主线程退出,即使worker都阻塞了 worker.daemon = True worker.start() # Put the tasks into the queue as a tuple # 将任务以tuple的形式放入队列中 for link in links: logger.info('Queueing {}'.format(link)) queue.put((download_dir, link)) # Causes the main thread to wait for the queue to finish processing all the tasks # 让主线程等待队列完成所有的任务 queue.join() print('Took {}'.format(time() - ts))注:为了测试方便,上面的代码可以用如下代码替代演示: coding=utf-8 测试utf-8编码 from Queue import Queuefrom threading import Threadfrom single import *import sys reload(sys)sys.setdefaultencoding('utf-8') class ProcessWorker(Thread): def __init__(self, queue): Thread.__init__(self) self.queue = queue def run(self): while True: # Get the work from the queue num = self.queue.get() processNum(num) self.queue.task_done() def main(): ts = time() nums = getNums(4) # Create a queue to communicate with the worker threads queue = Queue() # Create 4 worker threads # 创建四个工作线程 for x in range(4): worker = ProcessWorker(queue) # Setting daemon to True will let the main thread exit even though the workers are blocking # 将daemon设置为True将会使主线程退出,即使worker都阻塞了 worker.daemon = True worker.start() # Put the tasks into the queue for num in nums: queue.put(num) # Causes the main thread to wait for the queue to finish processing all the tasks # 让主线程等待队列完成所有的任务 queue.join() print("cost time is: {:.2f}s".format(time() - ts)) if name == "__main__": main() 结果: : 3 → 4: 2 → 3: 1 → 2 : 0 → 1cost time is: 1.01s在同一个机器上运行这个脚本,下载时间变成了4.1秒!即比之前的例子快4.7倍。虽然这快了很多,但还是要提一下,由于GIL的缘故,在这个进程中同一时间只有一个线程在运行。因此,这段代码是并发的但不是并行的。而它仍然变快的原因是这是一个IO密集型的任务。进程下载图片时根本毫不费力,而主要的时间都花在了等待网络上。这就是为什么线程可以提供很大的速度提升。每当线程中的一个准备工作时,进程可以不断转换线程。使用Python或其他有GIL的解释型语言中的线程模块实际上会降低性能。如果你的代码执行的是CPU密集型的任务,例如解压gzip文件,使用线程模块将会导致执行时间变长。对于CPU密集型任务和真正的并行执行,我们可以使用多进程(multiprocessing)模块。 官方的Python实现——CPython——带有GIL,但不是所有的Python实现都是这样的。比如,IronPython,使用.NET框架实现的Python就没有GIL,基于Java实现的Jython也同样没有。你可以点这查看现有的Python实现。 3、生成多进程多进程模块比线程模块更易使用,因为我们不需要像线程示例那样新增一个类。我们唯一需要做的改变在主函数中。 为了使用多进程,我们得建立一个多进程池。通过它提供的map方法,我们把URL列表传给池,然后8个新进程就会生成,它们将并行地去下载图片。这就是真正的并行,不过这是有代价的。整个脚本的内存将会被拷贝到各个子进程中。在我们的例子中这不算什么,但是在大型程序中它很容易导致严重的问题。 from functools import partialfrom multiprocessing.pool import Pool def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception("Couldn't find IMGUR_CLIENT_ID environment variable!") download_dir = setup_download_dir() links = [l for l in get_links(client_id) if l.endswith('.jpg')] download = partial(download_link, download_dir) with Pool(8) as p: p.map(download, links) print('Took {}s'.format(time() - ts))注:为了测试方便,上面的代码可以用如下代码替代演示: coding=utf-8 测试utf-8编码 from functools import partialfrom multiprocessing.pool import Poolfrom single import *from time import time import sys reload(sys)sys.setdefaultencoding('utf-8') def main(): ts = time() nums = getNums(4) p = Pool(4) p.map(processNum, nums) print("cost time is: {:.2f}s".format(time() - ts)) if name == "__main__": main() 结果: <_MainThread(MainThread, started 6188)>: 0 → 1<_MainThread(MainThread, started 3584)>: 1 → 2<_MainThread(MainThread, started 2572)>: 3 → 4<_MainThread(MainThread, started 4692)>: 2 → 3 cost time is: 1.21s4、分布式任务你已经知道了线程和多进程模块可以给你自己的电脑跑脚本时提供很大的帮助,那么在你想要在不同的机器上执行任务,或者在你需要扩大规模而超过一台机器的的能力范围时,你该怎么办呢?一个很好的使用案例是网络应用的长时间后台任务。如果你有一些很耗时的任务,你不会希望在同一台机器上占用一些其他的应用代码所需要的子进程或线程。这将会使你的应用的性能下降,影响到你的用户们。如果能在另外一台甚至很多台其他的机器上跑这些任务就好了。 Python库RQ非常适用于这类任务。它是一个简单却很强大的库。首先将一个函数和它的参数放入队列中。它将函数调用的表示序列化(pickle),然后将这些表示添加到一个Redis列表中。任务进入队列只是第一步,什么都还没有做。我们至少还需要一个能去监听任务队列的worker(工作线程)。 第一步是在你的电脑上安装和使用Redis服务器,或是拥有一台能正常的使用的Redis服务器的使用权。接着,对于现有的代码只需要一些小小的改动。先创建一个RQ队列的实例并通过redis-py 库传给一台Redis服务器。然后,我们执行“q.enqueue(download_link, download_dir, link)”,而不只是调用“download_link” 。enqueue方法的第一个参数是一个函数,当任务真正执行时,其他的参数或关键字参数将会传给该函数。 最后一步是启动一些worker。RQ提供了方便的脚本,可以在默认队列上运行起worker。只要在终端窗口中执行“rqworker”,就可以开始监听默认队列了。请确认你当前的工作目录与脚本所在的是同一个。如果你想监听别的队列,你可以执行“rqworker queue_name”,然后将会开始执行名为queue_name的队列。RQ的一个很好的点就是,只要你可以连接到Redis,你就可以在任意数量上的机器上跑起任意数量的worker;因此,它可以让你的应用扩展性得到提升。下面是RQ版本的代码: from redis import Redisfrom rq import Queue def main(): client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception("Couldn't find IMGUR_CLIENT_ID environment variable!") download_dir = setup_download_dir() links = [l for l in get_links(client_id) if l.endswith('.jpg')] q = Queue(connection=Redis(host='localhost', port=6379)) for link in links: q.enqueue(download_link, download_dir, link) 然而RQ并不是Python任务队列的唯一解决方案。RQ确实易用并且能在简单的案例中起到很大的作用,但是如果有更高级的需求,我们可以使用其他的解决方案(例如 Celery)。 5、总结如果你的代码是IO密集型的,线程和多进程可以帮到你。多进程比线程更易用,但是消耗更多的内存。如果你的代码是CPU密集型的,多进程就明显是更好的选择——特别是所使用的机器是多核或多CPU的。对于网络应用,在你需要扩展到多台机器上执行任务,RQ是更好的选择。 6、注:关于并发、并行区别与联系并发是指,程序在运行的过程中存在多于一个的执行上下文。这些执行上下文一般对应着不同的调用栈。 在单处理器上,并发程序虽然有多个上下文运行环境,但某一个时刻只有一个任务在运行。 但在多处理器上,因为有了多个执行单元,就可以同时有数个任务在跑。 这种物理上同一时刻有多个任务同时运行的方式就是并行。 和并发相比,并行更加强调多个任务同时在运行。 而且并行还有一个层次问题,比如是指令间的并行还是任务间的并行。
xuning715 2019-12-02 01:10:11 0 浏览量 回答数 0

回答

协程是一种计算机程序设计的组件,它范化了子例程的概念.协程非常适合用于实现一些有用的程序组件例如,协作式多任务,异常处理,事件循环,迭代器,无限链表和管道等.下面对比一般的子例程和协程:子例程的起始处是惟一的入口点,一旦退出即完成了子例程的执行,子例程的一个实例只会返回一次。子程序总是在它的开始处被启动,它通常是一个固定的位置.共行程序总是在它上次结束处的下一位置被启动.协程可以通过yield来调用其它协程。通过yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称、平等的。子例程容易实现于堆栈之上,因为子例程将调用的其他子例程作为下级。相反地,协程对等地调用其他协程,最好的实现是用 continuations(由有垃圾回收的堆实现)以跟踪控制流程。Marlin对coroutine的特性描述如下:coroutine中的局部变量在后续调用中保持有效.当控制离开的时候coroutine被挂起,将控制权交还给它的时候在它挂起的地方继续执行.协程的分类1)通过控制转移机制分类:对称协程与非对称协程非对称协程(asymmetric),或称为半对称协程(semi-symmetric)或半协程(semi-coroutines).非对称协程可以被看作是调用者的从属过程,它们之间的关系跟调用和被调用例程的关系类似.对称协程只提供了一种控制转移机制:将执行流程交给指定的协程.因为对称协程之间可以互相转移执行流程,所以它们之间的关系 就像在同一个层级.2) 协程是否一级类型(first-class)3) 是否有完整的栈有完整栈的协程,允许协程在最内层的函数中挂起自己.以后此协程被resume将会从它被挂起的 地方继续执行.没有完整栈的协程,例如python的generator,则只能在generator的主body中被挂起.有完整栈的协程可以用于实现用户级多任务而没有完整栈的协程则不行.
蛮大人123 2019-12-02 01:52:21 0 浏览量 回答数 0

回答

不良的编程习惯TOP1:粘贴复制 在学生时代,我们都知道抄袭是不对的。但在工作中,这方面的规则还很模糊。虽然有些代码块是不能盗用的——不要把专有代码拷贝到你的堆栈中,尤其是这些代码有标记版权信息。这种时候你应该编写自己的版本,老板付你薪水就是要做正事的。 但是当原始创作者想要共享代码时,问题就变得复杂了。这些共享代码也许放到了某个在线编程论坛上,也许它们是带有许可证(BSD,MIT)的开放源代码,允许使用一到三个函数。你使用这些共享代码是没有问题的,而且你上班是为了解决问题,而不是重新发明轮子。 大多数情况下,复制代码的优势非常明显,小心对待的话问题也不大。至少那些从靠谱的来源获得的代码已经被大致“检查“过了。 问题的复杂之处在于,这些共享代码是否存在一些未发现的错误,代码的用途或底层数据是否存在一些特别的假设。也许你的代码混入了空指针,而原始代码从未检查过。如果你能解决这些问题,那么就可以理解为你的老板得到了两位程序员共同努力的成果。这就是某种形式的结对编程,而且用不着什么高大上的办公桌。 不良的编程习惯TOP2:非函数式代码 在过去十年间,函数范式愈加流行。喜欢用嵌套函数调用来构建程序的人们引用了很多研究成果。这些研究表明,与旧式的变量和循环相比,函数式编程代码更安全,错误更少,而且可以随程序员的喜好任意组合在一起。粉丝们十分追捧函数式编程,还会在代码审查和拉取请求中诋毁非函数式方法。关于这种方法的优势,他们的观点其实并没有错。 但有时你需要的仅仅是一卷胶带而已。精心设计并细心计划的代码需要花费很多时间,不仅需要花费时间想象,还需要构建和之后导航的时间。这些都增加了复杂性,并且会花费很多的时间与精力。开发漂亮的函数式代码需要提前做计划,还要确保所有数据都通过正确的途径传递。有时找出并更改变量会简单得多,可能再加个注释说明一下就够了。就算要在注释中为之后的程序员致以冗长而难懂的歉意,也比重新设计整个系统,把它扳回正轨上要省事得多。 不良的编程习惯第 3 位:非标准间距 软件中的大多数空格都不会影响程序的性能。除少数使用间距指示代码块的语言(如 Python)外,大多数空格对程序行为的影响为零。尽管如此,仍然有一些得了强迫症的程序员会数空格,并坚持认为它们很重要。曾有这样一位程序员以最严肃的口吻告诉我的老板,说我正在写“非标准代码”,还说他一眼就看出来了。我的错咯?因为我没在等号的两侧放置空格,违反了 ESLint space-infix-ops 规则[1]。 有时候你只要操心那些更深层的内容就行了,谁管什么空格的位置。也许你担心数据库过载,也许你担心空指针可能会让你的代码崩溃。一套代码中,几乎所有的部分都比空格更重要,就算那些喜欢走形式的标准委员会写出来一大堆规则来限制这些空格或制表符的位置,那又如何呢。 令人欣喜的是,网上可以找到一些很好用的工具来自动重新格式化你的代码,让你的代码遵守所有精心定义的 linting 规则。人类不应该在这种事情上浪费时间和脑细胞。如果这些规则这么重要,我们就应该用工具来解决这些问题。 不良的编程习惯第 4 位:使用 goto 禁止使用 goto 的规则可以追溯到许多结构化编程工具还没有出现的时代。如果程序员想创建一个循环或跳转到另一个例程,则需要键入 goto,后跟一个行号。多年之后,编译器团队开始允许程序员使用字符串标签来代替行号。这在当时被认为是一项热门的新特性。 有的人把这样做法的结果称为“意大利面条式代码”。因为以后没人能读懂你的代码,没人搞得清楚执行路径。成为一团混乱的线程,缠结在一起。Edsger Dijkstra 写过一篇题为“我们认为 goto 声明是有害的”的一篇文章[2],号召大家拒绝使用这个命令。 但是绝对分支并不是问题所在,问题在于它产生的那堆纠缠的结果。一般来说,精心设计的 break 或 return 能提供有关该位置的代码执行情况的非常清晰的陈述。有时,将 goto 添加到一个 case 语句中所生成的东西与联 if-then-else 块的相比,结构更正确的列表理解起来更容易。 也有反例。苹果 SSL 堆栈中的“goto fail”安全漏洞[3]就是一个很好的例子。但是,如果我们谨慎地避免 case 语句和循环中出现的一些问题,我们就可以插入很好用的绝对跳转,使代码读者更容易理解正在发生的事情。有时我们可以放一个 break 或 return,不仅更简洁,而且大家读起来更愉快,除了那些讨厌 goto 的人们。 不良的编程习惯第 5 位:不声明类型 热爱类型化语言的人们有他们的理由。当我们为每个变量的数据类型添加清晰的声明时,我们会编写更好,错误更少的代码。花点时间来阐明类型,就可以帮助编译器在代码开始运行之前标记出愚蠢的错误。这可能会很痛苦,但也会有回报。这是一种编程的笨办法,就是为了避免错误。 时代变了。许多较新的编译器已经足够聪明了,它们可以在查看代码时推断出类型。它们可以在代码中前后移动,最后确认变量应该是 string 或 int,抑或是其他类型。而且,如果推断出来的这些类型没法对齐,则编译器会给出错误标志。它们不需要我们再类型化变量了。 换句话说,我们可以省略一些最简单的声明,然后就能轻松节省一些时间了。代码变得更简洁,代码读者也往往能猜出 for 循环中名为 i 的变量是一个整数。 不良的编程习惯第 6 位:溜溜球代码 程序员喜欢将其称为“yo-yo 代码”。首先,这些值将存储为字符串,然后将它们解析为整数,接下来将它们转换回字符串。这种方法效率极低。你几乎能感受到一大堆额外负载让 CPU 不堪重负的样子。能快速编写代码的聪明程序员会调整自己的代码架构,以最大程度地减少转换。因为他们安排好了计划,他们的代码也能跑得更快。 但不管你信不信,有时溜溜球代码也是有意义的。有的时候,你需要用一个可以在自己的黑匣子里搞定一大堆智能操作的库。有的老板花了很多钱,请好多天才做出来这么一个库。如果这个库需要字符串形式的数据,那么你就得给它字符串,就算你最近刚把数据转换为整数也得再转回去。 当然,你可以重写所有代码以最大程度地减少转换,但这会花费一些时间。有时,代码多运行一分钟、一小时、一天甚至一周也是可以接受的,因为重写代码会花费更多时间。有时候,增加技术债务要比重新建立一笔技术债的成本更低些。 有时这种库里面不是专有代码,而是你很久以前编写的代码。有时,转换一次数据要比重写该库中的所有内容更省事。这种时候你就可以编写悠悠球代码了,不要怕,我们都遇到过这种事情。 不良的编程习惯第7位:编写自己的数据结构 有一条标准规则是,程序员在大二学完数据结构课程后,再也不要编写用于存储数据的代码了。已经有人编写过了我们所需要的所有数据结构,并且他们的代码经过了多年的测试和重新测试。这些结构与语言打包在一起,还可能是免费的。你自己写的代码只会是一堆错误。 但有的时候数据结构库的速度有点缓慢。有时候我们被迫使用的标准结构并不适合我们自己的代码。有时,库会要求我们在使用它的结构之前重新配置数据。有时,这些库带有笨重的保护,还有一些诸如线程锁定之类的特性,而我们的代码并不需要它们。 发生这种情况时就该编写我们自己的数据结构了。有时我们自己的结构会快很多,还可能让我们的代码更整洁,因为我们不需要一大堆额外的代码来重新精确地格式化数据。 不良的编程习惯第 8 位:老式循环 很久以前,创建 C 语言的某人想将所有抽象可能性封装在一个简单的构造中。这个构造开始时要做一些事情,每次循环都要做一些事情,所有事情都完成时还有一些方法来提示我们。当时,这似乎是一种拥有无限可能性的完美语法。 此一时彼一时,如今一些现代评论者只看到了其中的麻烦,发生的事情太多了,所有这些可能性既可能向善也可能作恶。这种构造让阅读和理解代码变得非常困难。他们喜欢更加函数式的的范式,其中没有循环,只有应用到列表的函数,还有映射到某些数据的计算模板。 有时无循环方法更简洁,尤其是当我们只有一个简单的函数和一个数组的时候。但还有些时候,老式的循环要简单得多,因为它可以做更多事情。例如,当你找到第一个匹配项后就立刻停止搜索,这样的代码就简单得多。 此外,要对数据执行多项操作时,映射函数会要求更严格的编码。假设你要对每个数字取绝对值,然后取平方根,最快的方案是先映射第一个函数,然后映射第二个函数,将数据循环两次。 不良的编程习惯第 9 位:在中间打破循环 从有一天开始,一个规则制定小组宣布每个循环都应该有一个“不变项”,就是一个在整个循环中都为真的逻辑语句。当不变量不再为真时,循环就结束了。这是处理复杂循环的好方法,但会带来一些令人抓狂的约束,例如禁止我们在循环中间使用 return 或 break。这条规则是禁止 goto 语句规则的子集。 这个理论很不错,但它通常会导致代码变得更复杂。考虑以下这种简单的情况,其中会扫描一个数组,找出通过测试的一个条目: while (i<a.length){ ... if (test(a[i]) then return a[i]; ... } 喜欢循环不变项的人们宁愿我们添加另一个布尔变量,将其称为 notFound,然后这样用它: while ((notFound) && (i<a.length){ ... if (test(a[i])) then notFound=false; ... } 如果这个布尔名称取得很合适,那就会是一段自我注释得很好的代码。它可以让大家理解起来更容易。但这也增加了复杂性。这还意味着要分配另一个局部变量并阻塞一个寄存器,编译器可能没那么聪明,没法修复这个错误。 有时使用 goto 或 jump 会更简洁。 不良的编程习惯第10位:重载运算符和函数 一些有趣的语言会让你绕一些大弯子,比如说重新定义看起来应该是常量的元素值。拿 Python 来说,至少在 2.7 版及更低版本中,它允许你键入 TRUE=FALSE。这不会引发某种逻辑崩溃,也不会导致宇宙的终结;它只是交换了 TRUE 和 FALSE 的含义。你还可以使用 C 预处理器和其他一些语言来玩这种危险的游戏。还有一些语言允许你重新定义加号之类的运算符。 有时候,在一大段代码中重新定义一个或一些所谓常量,结果效率会更高。有时,老板会希望代码执行完全不同的操作。当然,你可以检查代码,逐一更改对应的部分,也可以干脆重新定义现实来节省时间。别人会觉得你是天才。用不着重写庞大的库,只需翻转一下即可。 这里也许应该划一条底线。无论这种做法多有意思,看起来多聪明,你都不应该在家里做实验。这太危险了——我是认真的。
茶什i 2019-12-30 11:01:01 0 浏览量 回答数 0

问题

MaxCompute百问集锦

大数据计算服务(MaxCompute,原名 ODPS)是一种快速、完全托管的 GB/TB/PB 级数据仓库解决方案。MaxCompute 向用户提供了完善的数据导入方案以及多种经典的分布式计算模型,能够更快速的解决用户海量数据计算问题,有效...
yq传送门 2019-12-01 20:16:47 2404 浏览量 回答数 1

回答

数据采集链接 检查数据 首先检查“ex1data1”文件中的数据。“txt”在“我的存储库”的“数据”目录中。首先导入一些库。 import os import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline 现在开始运行,使用Pandas把数据加载到数据帧里,并且使用“head”函数显示前几行。 path= os.getcwd()+ '\data\ex1data1.txt' data= pd.read_csv(path, header=None, names=['Population','Profit']) data.head() Population Profit 0 6.1101 17.5920 1 5.5277 9.1302 2 8.5186 13.6620 3 7.0032 11.8540 4 5.8598 6.8233 Pandas提供的另外一个有用的函数是”describe”函数,它能在数据集上计算一些基本统计数据,这有助于在项目的探索性分析阶段获得数据的“feel”。 data.describe() Population Profit count 97.000000 97.000000 mean 8.159800 5.839135 std 3.869884 5.510262 min 5.026900 -2.680700 25% 5.707700 1.986900 50% 6.589400 4.562300 75% 8.578100 7.046700 max 22.203000 24.147000 检查有相关的统计数据可能会有帮助,但有时需要找到方法使它可视化。这个数据集只有一个因变量,我们可以把它放到散点图中以便更好地了解它。我们可以使用pandas为它提供的“plot”函数,这实际上只是matplotlib的一个包装器。 data.plot(kind='scatter', x='Population', y='Profit', figsize=(12,8)) 我们可以清楚地看到,随着城市规模的增加,利润呈线性增长。现在让我们进入有趣的部分——从零开始实现python中的线性回归算法。 实现简单的线性回归 线性回归是建立因变量和一个或多个自变量之间关系的一种方法(如果只有一个自变量就是简单线性回归;如果是多个自变量就是多重线性回归)。我们试图使用参数theta创建数据X的线性模型,它描述了数据的方差,给出新的数据点,我们可以在不知道实际结果的情况下准确地预测。 在实现过程中,我们使用叫做梯度下降的优化技术寻找参数theta。如果你熟悉线性回归,你可能会意识到有另一种方法可以找到线性模型的最优参数,就是做“正态方程”,它可以用一系列矩阵运算来解决这个问题。然而,这种方法的问题就是在大数据集中不能很好地扩展,相比之下,我们可以使用梯度下降和其他优化方法的变体来扩展到无限大小的数据集,因此对于机器学习问题,梯度下降更实用。 理论知识已经足够了,下面我们写一些代码。我们首先要写的就是成本函数,成本函数通过计算模型参数和实际数据点之间的误差来计算模型预测的误差,从而评估模型的质量。例如,如果给定城市的人口数量是4,但是我们预测是7,我们的误差就是 (7-4)^2 = 3^2 = 9(假设为L2或“最小二乘法”损失函数)。我们为X中的每个数据点执行此操作,并对结果求和以获取成本。下面是函数: def computeCost(X, y, theta): inner= np.power(((X* theta.T)- y),2) return np.sum(inner)/ (2 * len(X)) 注意,这里没有循环。我们利用numpy的linear algrebra功能将结果计算为一系列矩阵运算。这比不优化的“for”循环的效率要高得多。 为了使这个成本函数与我们上面创建的pandas数据框架无缝对接,我们需要做一些操作。首先,在开始插入一列1s的数据帧使矩阵运算正常工作。然后把数据分离成自变量X和因变量y。 # append a ones column to the front of the data set data.insert(0,'Ones',1) # set X (training data) and y (target variable) cols= data.shape[1] X= data.iloc[:,0:cols-1] y= data.iloc[:,cols-1:cols] 最后把数据框架转换为numpy矩阵并实例化参数matirx。 # convert from data frames to numpy matrices X= np.matrix(X.values) y= np.matrix(y.values) theta= np.matrix(np.array([0,0])) 在调试矩阵运算时要查看正在处理的矩阵的形状。矩阵乘法看起来像(i x j)*(j x k)=(i x k),其中i、j和k是矩阵相对维度的形状。 X.shape, theta.shape, y.shape ((97L, 2L), (1L, 2L), (97L, 1L)) 调试一下成本函数。参数已经被初始化为0,所以解不是最优的,但是我们可以看看它是否有效。 computeCost(X, y, theta) 32.072733877455676 目前为止一切都很顺利。现在我们需要使用练习文本中定义的更新规则来定义一个函数,来对参数theta执行梯度下降。这是梯度下降的函数: def gradientDescent(X, y, theta, alpha, iters): temp= np.matrix(np.zeros(theta.shape)) parameters= int(theta.ravel().shape[1]) cost= np.zeros(iters) for iin range(iters): error= (X* theta.T)- y for jin range(parameters): term= np.multiply(error, X[:,j]) temp[0,j]= theta[0,j]- ((alpha/ len(X))* np.sum(term)) theta= temp cost[i]= computeCost(X, y, theta) return theta, cost 梯度下降的就是计算出每一个迭代的误差项的梯度,以找出适当的方向来移动参数向量。换句话说,就是计算对参数的修改以减少错误,从而使我们的解决方案更接近最佳解决方案。 我们再一次依赖于numpy和线性代数求解,你可能注意到我的实现不是100%的优化,事实上,有完全去除内循环和一次性更新所有参数的方法。我把它留给读者去完成。 现在我们已经有了一种评估解决方案的方法,并且找到一个好的解决方案,把它应用到我们的数据集中。 # initialize variables for learning rate and iterations alpha= 0.01 iters= 1000 # perform gradient descent to "fit" the model parameters g, cost= gradientDescent(X, y, theta, alpha, iters) g 矩阵x([[-3.24140214, 1.1272942 ]]) 注意我们已经初始化了一些新的变量。梯度下降函数中有叫做alpha和iters的参数。alpha是学习速率-它是参数更新规则中的一个因素,它帮助决定算法收敛到最优解的速度。iters是迭代次数。没有严格的规则去规定如何初始化这些参数,但是通常会涉及到试错法。 现在有一个参数向量描述数据集的最优线性模型,一个快速评估回归模型的方法就是观察数据集上的解决方案的总误差: computeCost(X, y, g) 4.5159555030789118 这要比32好很多。 查看结果 我们将使用matplotlib来可视化我们的解决方案。我们在数据的散点图上覆盖一条线表示我们的模型,看它是否合适。我们使用numpy的“linspace”函数在我们的数据范围内创建一系列均匀间隔的点,然后用我们的模型“评估”这些点,看预期的利润会是多少。我们把它变成线形图。 x= np.linspace(data.Population.min(), data.Population.max(),100) f= g[0,0]+ (g[0,1]* x) fig, ax= plt.subplots(figsize=(12,8)) ax.plot(x, f,'r', label='Prediction') ax.scatter(data.Population, data.Profit, label='Traning Data') ax.legend(loc=2) ax.set_xlabel('Population') ax.set_ylabel('Profit') ax.set_title('Predicted Profit vs. Population Size') 我们的解决方案看起来是数据集的最优线性模型。梯度体系函数会在每个训练迭代中输出一个成本向量,我们可以出绘制出线形图。 fig, ax = plt.subplots(figsize=(12,8)) ax.plot(np.arange(iters), cost, 'r') ax.set_xlabel('Iterations') ax.set_ylabel('Cost') ax.set_title('Error vs. Training Epoch') 成本一直在降低——这就是凸优化问题的一个示例。如果你要绘制问题的整个解决方案空间,它看起来会像一个碗的形状,“盆地”表示最优解。
珍宝珠 2019-12-02 03:22:32 0 浏览量 回答数 0

回答

在这个问题中,我们集中讨论根据特殊语法去解析文本的问题。为了这样做,你首先要以BNF或者EBNF形式指定一个标准语法。比如,一个简单数学表达式语法可能像下面这样: expr ::= expr + term | expr - term | term term ::= term * factor | term / factor | factor factor ::= ( expr ) | NUM 或者,以EBNF形式: expr ::= term { (+|-) term }* term ::= factor { (|/) factor } factor ::= ( expr ) | NUM 在EBNF中,被包含在 {...}* 中的规则是可选的。*代表0次或多次重复(跟正则表达式中意义是一样的)。 现在,如果你对BNF的工作机制还不是很明白的话,就把它当做是一组左右符号可相互替换的规则。一般来讲,解析的原理就是你利用BNF完成多个替换和扩展以匹配输入文本和语法规则。为了演示,假设你正在解析形如 3 + 4 * 5 的表达式。这个表达式先要通过使用2.18节中介绍的技术分解为一组令牌流。结果可能是像下列这样的令牌序列: NUM + NUM * NUM 在此基础上, 解析动作会试着去通过替换操作匹配语法到输入令牌: expr expr ::= term { (+|-) term }* expr ::= factor { (|/) factor } { (+|-) term }* expr ::= NUM { (|/) factor } { (+|-) term }* expr ::= NUM { (+|-) term }* expr ::= NUM + term { (+|-) term }* expr ::= NUM + factor { (|/) factor } { (+|-) term }* expr ::= NUM + NUM { (|/) factor} { (+|-) term }* expr ::= NUM + NUM * factor { (|/) factor } { (+|-) term }* expr ::= NUM + NUM * NUM { (|/) factor } { (+|-) term }* expr ::= NUM + NUM * NUM { (+|-) term }* expr ::= NUM + NUM * NUM 下面所有的解析步骤可能需要花点时间弄明白,但是它们原理都是查找输入并试着去匹配语法规则。第一个输入令牌是NUM,因此替换首先会匹配那个部分。一旦匹配成功,就会进入下一个令牌+,以此类推。当已经确定不能匹配下一个令牌的时候,右边的部分(比如 { (/) factor } )就会被清理掉。在一个成功的解析中,整个右边部分会完全展开来匹配输入令牌流。 有了前面的知识背景,下面我们举一个简单示例来展示如何构建一个递归下降表达式求值程序: #!/usr/bin/env python -- encoding: utf-8 -- """ Topic: 下降解析器 Desc : """ import re import collections Token specification NUM = r'(?P \d+)' PLUS = r'(?P +)' MINUS = r'(?P -)' TIMES = r'(?P *)' DIVIDE = r'(?P /)' LPAREN = r'(?P ()' RPAREN = r'(?P ))' WS = r'(?P \s+)' master_pat = re.compile('|'.join([NUM, PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN, WS])) Tokenizer Token = collections.namedtuple('Token', ['type', 'value']) def generate_tokens(text): scanner = master_pat.scanner(text) for m in iter(scanner.match, None): tok = Token(m.lastgroup, m.group()) if tok.type != 'WS': yield tok Parser class ExpressionEvaluator: ''' Implementation of a recursive descent parser. Each method implements a single grammar rule. Use the ._accept() method to test and accept the current lookahead token. Use the ._expect() method to exactly match and discard the next token on on the input (or raise a SyntaxError if it doesn't match). ''' def parse(self, text): self.tokens = generate_tokens(text) self.tok = None # Last symbol consumed self.nexttok = None # Next symbol tokenized self._advance() # Load first lookahead token return self.expr() def _advance(self): 'Advance one token ahead' self.tok, self.nexttok = self.nexttok, next(self.tokens, None) def _accept(self, toktype): 'Test and consume the next token if it matches toktype' if self.nexttok and self.nexttok.type == toktype: self._advance() return True else: return False def _expect(self, toktype): 'Consume next token if it matches toktype or raise SyntaxError' if not self._accept(toktype): raise SyntaxError('Expected ' + toktype) # Grammar rules follow def expr(self): "expression ::= term { ('+'|'-') term }*" exprval = self.term() while self._accept('PLUS') or self._accept('MINUS'): op = self.tok.type right = self.term() if op == 'PLUS': exprval += right elif op == 'MINUS': exprval -= right return exprval def term(self): "term ::= factor { ('*'|'/') factor }*" termval = self.factor() while self._accept('TIMES') or self._accept('DIVIDE'): op = self.tok.type right = self.factor() if op == 'TIMES': termval *= right elif op == 'DIVIDE': termval /= right return termval def factor(self): "factor ::= NUM | ( expr )" if self._accept('NUM'): return int(self.tok.value) elif self._accept('LPAREN'): exprval = self.expr() self._expect('RPAREN') return exprval else: raise SyntaxError('Expected NUMBER or LPAREN') def descent_parser(): e = ExpressionEvaluator() print(e.parse('2')) print(e.parse('2 + 3')) print(e.parse('2 + 3 * 4')) print(e.parse('2 + (3 + 4) * 5')) # print(e.parse('2 + (3 + * 4)')) # Traceback (most recent call last): # File " ", line 1, in # File "exprparse.py", line 40, in parse # return self.expr() # File "exprparse.py", line 67, in expr # right = self.term() # File "exprparse.py", line 77, in term # termval = self.factor() # File "exprparse.py", line 93, in factor # exprval = self.expr() # File "exprparse.py", line 67, in expr # right = self.term() # File "exprparse.py", line 77, in term # termval = self.factor() # File "exprparse.py", line 97, in factor # raise SyntaxError("Expected NUMBER or LPAREN") # SyntaxError: Expected NUMBER or LPAREN if name == 'main': descent_parser() 讨论 文本解析是一个很大的主题, 一般会占用学生学习编译课程时刚开始的三周时间。如果你在找寻关于语法,解析算法等相关的背景知识的话,你应该去看一下编译器书籍。很显然,关于这方面的内容太多,不可能在这里全部展开。 尽管如此,编写一个递归下降解析器的整体思路是比较简单的。开始的时候,你先获得所有的语法规则,然后将其转换为一个函数或者方法。因此如果你的语法类似这样: expr ::= term { ('+'|'-') term }* term ::= factor { (''|'/') factor } factor ::= '(' expr ')' | NUM 你应该首先将它们转换成一组像下面这样的方法: class ExpressionEvaluator: ... def expr(self): ... def term(self): ... def factor(self): ... 每个方法要完成的任务很简单 - 它必须从左至右遍历语法规则的每一部分,处理每个令牌。从某种意义上讲,方法的目的就是要么处理完语法规则,要么产生一个语法错误。为了这样做,需采用下面的这些实现方法: 如果规则中的下个符号是另外一个语法规则的名字(比如term或factor),就简单的调用同名的方法即可。这就是该算法中”下降”的由来 - 控制下降到另一个语法规则中去。有时候规则会调用已经执行的方法(比如,在 factor ::= '('expr ')' 中对expr的调用)。这就是算法中”递归”的由来。 如果规则中下一个符号是个特殊符号(比如(),你得查找下一个令牌并确认是一个精确匹配)。如果不匹配,就产生一个语法错误。这一节中的 _expect() 方法就是用来做这一步的。 如果规则中下一个符号为一些可能的选择项(比如 + 或 -),你必须对每一种可能情况检查下一个令牌,只有当它匹配一个的时候才能继续。这也是本节示例中 _accept() 方法的目的。它相当于_expect()方法的弱化版本,因为如果一个匹配找到了它会继续,但是如果没找到,它不会产生错误而是回滚(允许后续的检查继续进行)。 对于有重复部分的规则(比如在规则表达式 ::= term { ('+'|'-') term }* 中),重复动作通过一个while循环来实现。循环主体会收集或处理所有的重复元素直到没有其他元素可以找到。 一旦整个语法规则处理完成,每个方法会返回某种结果给调用者。这就是在解析过程中值是怎样累加的原理。比如,在表达式求值程序中,返回值代表表达式解析后的部分结果。最后所有值会在最顶层的语法规则方法中合并起来。 尽管向你演示的是一个简单的例子,递归下降解析器可以用来实现非常复杂的解析。比如,Python语言本身就是通过一个递归下降解析器去解释的。如果你对此感兴趣,你可以通过查看Python源码文件Grammar/Grammar来研究下底层语法机制。看完你会发现,通过手动方式去实现一个解析器其实会有很多的局限和不足之处。 其中一个局限就是它们不能被用于包含任何左递归的语法规则中。比如,加入你需要翻译下面这样一个规则: items ::= items ',' item | item 为了这样做,你可能会像下面这样使用 items() 方法: def items(self): itemsval = self.items() if itemsval and self._accept(','): itemsval.append(self.item()) else: itemsval = [ self.item() ] 唯一的问题是这个方法根本不能工作,事实上,它会产生一个无限递归错误。 关于语法规则本身你可能也会碰到一些棘手的问题。比如,你可能想知道下面这个简单扼语法是否表述得当: expr ::= factor { ('+'|'-'|''|'/') factor } factor ::= '(' expression ')' | NUM 这个语法看上去没啥问题,但是它却不能察觉到标准四则运算中的运算符优先级。比如,表达式 "3 + 4 * 5" 会得到35而不是期望的23.分开使用”expr”和”term”规则可以让它正确的工作。 对于复杂的语法,你最好是选择某个解析工具比如PyParsing或者是PLY。下面是使用PLY来重写表达式求值程序的代码: from ply.lex import lex from ply.yacc import yacc Token list tokens = [ 'NUM', 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'LPAREN', 'RPAREN' ] Ignored characters t_ignore = ' \t\n' Token specifications (as regexs) t_PLUS = r'+' t_MINUS = r'-' t_TIMES = r'*' t_DIVIDE = r'/' t_LPAREN = r'(' t_RPAREN = r')' Token processing functions def t_NUM(t): r'\d+' t.value = int(t.value) return t Error handler def t_error(t): print('Bad character: {!r}'.format(t.value[0])) t.skip(1) Build the lexer lexer = lex() Grammar rules and handler functions def p_expr(p): ''' expr : expr PLUS term | expr MINUS term ''' if p[2] == '+': p[0] = p[1] + p[3] elif p[2] == '-': p[0] = p[1] - p[3] def p_expr_term(p): ''' expr : term ''' p[0] = p[1] def p_term(p): ''' term : term TIMES factor | term DIVIDE factor ''' if p[2] == '*': p[0] = p[1] * p[3] elif p[2] == '/': p[0] = p[1] / p[3] def p_term_factor(p): ''' term : factor ''' p[0] = p[1] def p_factor(p): ''' factor : NUM ''' p[0] = p[1] def p_factor_group(p): ''' factor : LPAREN expr RPAREN ''' p[0] = p[2] def p_error(p): print('Syntax error') parser = yacc() 这个程序中,所有代码都位于一个比较高的层次。你只需要为令牌写正则表达式和规则匹配时的高阶处理函数即可。而实际的运行解析器,接受令牌等等底层动作已经被库函数实现了。 下面是一个怎样使用得到的解析对象的例子: parser.parse('2') 2 parser.parse('2+3') 5 parser.parse('2+(3+4)*5') 37
景凌凯 2020-04-16 19:33:06 0 浏览量 回答数 0

问题

MaxCompute百问集锦(持续更新20171011)

大数据计算服务(MaxCompute,原名 ODPS)是一种快速、完全托管的 GB/TB/PB 级数据仓库解决方案。MaxCompute 向用户提供了完善的数据导入方案以及多种经典的分布式计算模型,能够更快速的解决用户海量数据计算问题,有效...
隐林 2019-12-01 20:19:23 38430 浏览量 回答数 18

问题

【Java学习全家桶】1460道Java热门问题,阿里百位技术专家答疑解惑

阿里极客公益活动: 或许你挑灯夜战只为一道难题 或许你百思不解只求一个答案 或许你绞尽脑汁只因一种未知 那么他们来了,阿里系技术专家来云栖问答为你解答技术难题了 他们用户自己手中的技术来帮助用户成长 本次活动特邀百位阿里技术专家对Java常...
管理贝贝 2019-12-01 20:07:15 27612 浏览量 回答数 19

云产品推荐

上海奇点人才服务相关的云产品 小程序定制 上海微企信息技术相关的云产品 国内短信套餐包 ECS云服务器安全配置相关的云产品 开发者问答 阿里云建站 自然场景识别相关的云产品 万网 小程序开发制作 视频内容分析 视频集锦 代理记账服务 阿里云AIoT