最近要改TensorFlow结构,在网上搜别人的代码的时候,看到了一个完全vectorize的写法的代码。他的代码中所有的内容都是用tf封装起来,以张量的形式表现的,这导致我觉得我之前根本不会用tensorflow,只用个sess.run去训练就完了。
为了改他的代码为我所用,第一步得先看懂他写的东西。然而我这个半路出家的java背景的家伙发现有很多python的概念我还是不懂。比如关键字“with”到底是干什么用的。然后我再stackoverflow上找了找解释with的的帖子,发现很多例子里有“yield”,这个是什么我又不知道。解释yield的帖子又提到了generator。。。。。心里一阵mmp,这tm又是个啥?? 这一下午,我怀着吃了狗屎一样的绝望心情,把这3个点弄会了,搭起了下一步通往理解vectorize写法的基础。在这里我把这三个关键字做个笔记,希望对别人有所帮助。
既然我是倒着看会的,那就先记录generator和yield。
Generator:
generator在java里没有对应的概念。它的特点是创建一个只能iterate一次的东西,不能被叫第二次。所以它内部不会把数值存入memory,这样做速度快,而且节省空间。比如有一个随机数数组,只需要用一次。那就没必要用数组来储存,用generator就好了。
建立generator跟建立数组差不多,区别是数组用中括号【】,而generator用小括号(),比如下面的例子:
myGenerator = (x * 2 for x in range(256)) # Generator expression
myList = [x * 2 for x in range(256)] # List comprehension
如果不知道这种写法,在看python代码的时候就容易出问题。有时候用debug模式一步一步追看也容易看到什么东西本来刚创建出来,怎么莫名其妙的就跳出func没了??
不明真相的好奇宝宝们,欢迎把下面的代码那走自己用debug模式运行一下,你会发现第二段代码的mygenerator里面显示是个object的reference,而不是[0,1,4]。这说明generator也是个object。mygenerator只能被叫一次,虽然第一次iterate mygenerator之后你会发现这个变量所保持的object还存在,但是第二个for loop不会打印任何东西,也不会报错。因为里面的东西已经空了。
# 第一段
mylist = [x * x for x in range(3)]
for i in mylist:
print(i)
# 第二段
mygenerator = (x*x for x in range(3))
for i in mygenerator:
print(i)
for i in mygenerator:
print(i)
除了面的写法,generator还可以伪装成函数的样子。如果一个函数里面有一个或几个“yield”,这个函数就是个generator。比如下面的例子:
1 def createGenerator():
2 mylist = range(3)
3 for i in mylist:
4 yield i
5 yield i*i
6 mygenerator = createGenerator() # create a generator
7 print(mygenerator) # mygenerator is an object!
8 for j in mygenerator:
9 print(i)
上面的例子里,def createGenerator()每一次会创建一个generator,所以它可以被多次调用。
Yield:
借着上面的例子,再解释一下yield。yield和generator是相辅相成的一对儿,就像炸酱面和大蒜一样~ yield其实就是方程的关键字“return”。只不过generator的return叫“yield”。不光写法不一样,yield和return的机理也有点不同。
要想明白yield,首先要明白一点,就是当创建generator被叫的时候,方程的主体不会运行。如果你在前5行代码放breakpoint的话,在第6行结束时你会发现不会停在前5行。因为他只是返回了一个generator的object给变量。真正会运行函数主体的是第8行,在for-loop iterates generator的时候。这时候程序会一直运行,直到遇到第一个yield。这是方程会做一个标记(我猜啊,再详细的没看过),然后返回yield后面的东西给for-loop里的j用,然后返回方程,从上一次标记的下一行开始继续运行。。。如果上面的代码把“yield” 改成“return”,程序就会报错,因为for-loop 不能 iterate一个数字(3.3版以前的python不允许generator里面出现return的)。如果是yield就不会报错,因为这时候iterate的是generator object,这个object会根据里面的yield会把数值一个个喂给j。
With:
这个在stack overflow上查了好久,越看越懵逼。 最后还是在CSDN大神里找到了简单的解释。只要把他的运作流程和作用写出来就好了嘛,干嘛说那么一对不相干的,越看越懵。。。
简单来说,with就是个方便书写而加入的statement(计算机科学里叫syntactic sugar),它是用来代替try-except-finally的。或者说with可以帮助写代码的人处理try-except-finally的逻辑,因为try-except-finally写不好是容易出逻辑错误的,但是用了with就可以无脑一行解决。所以with其实是一种encapsulation。
try-except-finally是用于执行可能出现exception的语句的。比如打开了文件最后要保证关闭,那么就要写好长。用with语句就一行,
with xxx [as X]:
#执行啥啥啥
这个xxx是一个expression,expression是什么都行。如果是方程,返回的东西就赋值给as后面的变量。如果是个class,就会叫class里的__enter__函数,把它返回的值赋值给as后面的变量。整体的流程是这样的:
1,计算xxx,并获取一个上下文管理器。
2,上下文管理器的__exit__方法被保存起来用于之后的调用。
3,调用上下文管理器的__enter__方法
4,如果with表达式包含as X,那么xxx的返回值被赋值给X。
5,执行啥啥啥中的表达式
6,调永上下文管理器的__exit__方法。如果啥啥啥的执行过程中发生了一个异常导致程序退出,那么异常中的type、value、和traceback(也就是sys.exc_info()的返回值)将作为参数传递给__exit__方法,然后异常抛出在控制台。否则将传递三个None值。
那个上下文管理器在这里有解释。
这么一看是不是很清晰了? 如果你写一个自己的class,希望以后被with调用的话,那么也要加入__enter__和__exit__两个方程。总之with是个新的protocol,是用于那些需要后续处理的process的,它能够保证在执行完毕之后的清理工作可以顺利进行,并且不需要自己写一大堆繁琐的try-except-finally。如果还不明白,或者有陌生感的话,看下面的referece吧。
弄清了这些东西,现在我可以愉快的看tensorflow了~~~
generator的syntax:
https://www.cnblogs.com/hump/p/6287462.html
https://stackoverflow.com/questions/47789/generator-expressions-vs-list-comprehension
generator和yield:
https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do
with:
https://blog.csdn.net/yxwb1253587469/article/details/52248565
https://blog.csdn.net/u014745194/article/details/71424909
https://blog.csdn.net/zhuhai__yizhi/article/details/78095650
https://stackoverflow.com/questions/26342769/meaning-of-with-statement-without-as-keyword
https://stackoverflow.com/questions/3012488/what-is-the-python-with-statement-designed-for
网友评论