网站性能检测评分
注:本网站页面html检测工具扫描网站中存在的基本问题,仅供参考。
python中keys
解读Keras在ImageNet中应用:详解5种图像识别模型 流量视频课程
更多深度文章,请关注:https://yq.aliyun/cloud
几个月前,我写了一篇关于如何使用CNN(卷积神经网络)尤其是VGG16来分类图像的教程,该模型能够以很高的精确度识别我们日常生活中的1000种不同种类的物品。
那时,模型还是和Keras包分开的,我们得从free-standingGitHubrepo上下载并手动安装;现在模型已经整合进Keras包,原先的教程也已经不再适用,所以我决定写一篇新的教程。
在教程中,你将学习到如何编写一个Python脚本来分类你自己的图像。
博客结构
1.简要说明一下这几个网络架构;
2.使用Python编写代码:载入训练好的模型并对输入图像分类;
3.审查一些样本图片的分类结果。
Keras中最新的深度学习图像分类器
Keras提供了五种开箱即用型的CNN:
1.VGG16
2.VGG19
3.ResNet50
4.InceptionV3
5.Xception
什么是ImageNet
ImageNet曾是一个计算机视觉研究项目:(人工)打标签并分类成22000个不同物品种类。然而,当我们在讨论深度学习和CNN的时候,“ImageNet”意味着ImageNetLargeScaleVisualRecognitionChallenge,简写为ILSVRC。
ILSVRC的目的是训练一个能够正确识别图像并分类(1000种)的模型:模型使用约120万张图像用作训练,5万张图像用作验证,10万张图像用作测试。
这1000种分类涵盖了我们的日常生活接触到的东西,具体列表请点击。
在图像分类上,ImageNet竞赛已经是计算机视觉分类算法事实上的评价标准——而自2012年以来,排行榜就被CNN和其它深度学习技术所统治。
过去几年中ImageNet竞赛里表现优异的模型在Keras中均有收录。通过迁移学习,这些模型在ImageNet外的数据集中也有着不错的表现。
VGG16和VGG19
图1:VGG网络架构(source)
VGG网络架构于2014年出现在Simonyan和Zisserman中的论文中,《VeryDeepConvolutionalNetworksforLargeScaleImageRecognition》。
该架构仅仅使用堆放在彼此顶部、深度不断增加的3×3卷积层,并通过maxpooling来减小volume规格;然后是两个4096节点的全连接层,最后是一个softmax分类器。“16”和“19”代表网络中权重层的数量(表2中的D和E列):
在2014年的时候,16还有19层网络还是相当深的,Simonyan和Zisserman发现训练VGG16和VGG19很有难度,于是选择先训练小一些的版本(列A和列C)。这些小的网络收敛后被用来作为初始条件训练更大更深的网络——这个过程被称为预训练(pre-training)。
预训练很有意义,但是消耗大量时间、枯燥无味,在整个网络都被训练完成前无法进行下一步工作。
如今大部分情况下,我们已经不再使用预训练,转而采用Xaiver/Glorot初始化或者MSRA初始化(有时也被称作Heetal.初始化,详见《DelvingDeepintoRectifiers:SurpassingHuman-LevelPerformanceonImageNetClassification》)。如果你感兴趣,可以从这篇文章中理解到weightinitialization的重要性以及深度神经网络的收敛——《Allyouneedisagoodinit,MishkinandMatas(2015)》。
VGGNet有两个不足:
1.训练很慢;
2.weights很大。
由于深度以及全连接节点数量的原因,VGG16的weights超过533MB,VGG19超过574MB,这使得部署VGG很令人讨厌。虽然在许多深度学习图像分类问题中我们仍使用VGG架构,但是小规模的网络架构更受欢迎(比如SqueezeNet,GoogleNet等等)。
ResNet
与AlexNet、OverFeat还有VGG这些传统顺序型网络架构不同,ResNet的网络结构依赖于微架构模组(micro-architecturemodules,也被称为network-in-networkarchitectures)。
微架构模组指构成网络架构的“积木”,一系列的微架构积木(连同你的标准CONV,POOL等)共同构成了大的架构(即最终的网络)。
ResNet于2015年出现在Heetal的论文《DeepResidualLearningforImageRecognition》中,它的出现很有开创性意义,证明极深的网络也可以通过标准SGD(以及一个合理的初始化函数)来训练:
图3:Heetal.于2015年提出的残差模组
在2016年的著作《IdentityMappingsinDeepResidualNetworks》中,他们证实了可以通过更新残差模组(residualmodule)来使用标志映射(identifymappings),达到提高精度的目的。
图4:(左)原始残差模组(右)使用预激活(pre-activation)更新的残差模组
尽管ResNet比VGG16还有VGG19要深,weights却要小(102MB),因为使用了全局平均池化(globalaveragepooling),而不是全连接层。
InceptionV3
“Inception”微架构于2014年出现在Szegedy的论文中,《GoingDeeperwithConvolutions》。
图5:GoogleNet中使用的Inception模组原型
Inception模组的目的是扮演一个“多级特征提取器”,在网络相同的模组内计算1×1、3×3还有5×5的卷积——这些过滤器的输出在输入至网络下一层之前先被堆栈到channeldimension。
该架构的原型被称为GoogleNet,后继者被简单的命名为InceptionvN,N代表Google推出的数字。
Keras中的InceptionV3架构来自于Szegedyetal.的后续论文,《RethinkingtheInceptionArchitectureforComputerVision(2015)》,该论文打算通过更新inception模组来提高ImageNet分类的准确度。
InceptionV3比VGG还有ResNet都要小,约96MB。
Xception
图6:Xception架构
Xception是被FranoisChollet提出的,后者是Keras库的作者和主要维护者。
Xception是Inception架构的扩展,用depthwise独立卷积代替Inception标准卷积。
关于Xception的出版物《DeepLearningwithDepthwiseSeparableConvolutions》可以在这里找到。
Xception最小仅有91MB。
SqueezeNet
Figure7:“fire”模组,由一个“squeeze”和一个“expand”模组组成。(Iandolaetal.,2016)
仅仅4.9MB的SqueezeNet架构能达到AlexNet级别的精确度(~57%rank-1and~80%rank-5),这都归功于“fire”模组的使用。然而SqueezeNet的训练很麻烦,我会在即将出版的书——《DeepLearningforComputerVisionwithPython》——中介绍如何训练SqueezeNet来处理ImageNet数据集。
使用Python和Keras通过VGGNet,ResNet,Inception和Xception对图像分类
新建一个文件,命名为classify_image.py,编辑插入下列代码1#importthenecessarypackages2fromkeras.applicationsimportResNet503fromkeras.applicationsimportInceptionV34fromkeras.applicationsimportXception#TensorFlowONLY5fromkeras.applicationsimportVGG166fromkeras.applicationsimportVGG197fromkeras.applicationsimportimagenet_utils8fromkeras.applications.inception_v3importpreprocess_input9fromkeras.preprocessing.imageimportimg_to_array10fromkeras.preprocessing.imageimportload_img11importnumpyasnp12importargparse13importcv2
第2-13行导入需要的包,其中大部分都属于Keras。
第2-6行分别导入ResNet,InceptionV3,Xception,VGG16,还有VGG19——注意Xception只兼容TensorFlow后端。
第7行导入的image_utils包包含了一系列函数,使得对图片进行前处理以及对分类结果解码更加容易。
余下的语句导入其它有用的函数,其中NumPy用于数学运算,cv2用于与OpenCV结合。15#constructtheargumentparseandparsethearguments16ap=argparse.ArgumentParser()17ap.add_argument("-i","--image",required=True,18help="pathtotheinputimage")19ap.add_argument("-model","--model",type=str,default="vgg16",20help="nameofpre-trainednetworktouse")21args=vars(ap.parse_args())
--image为希望进行分类的图像的路径。
--model为选用的CNN的类别,默认为VGG16。23#defineadictionarythatmapsmodelnamestotheirclasses24#insideKeras25MODELS={26"vgg16":VGG16,27"vgg19":VGG19,28"inception":InceptionV3,29"xception":Xception,#TensorFlowONLY30"resnet":ResNet5031}3233#esnureavalidmodelnamewassuppliedviacommandlineargument34ifargs["model"]notinMODELS.keys():35raiseAssertionError("The--modelcommandlineargumentshould"36"beakeyinthe`MODELS`dictionary")
第25-31行定义了一个词典,将类映射到对应的模型名称。
如果没有在该词典中找到“--model”,就会报错。
输入一个图像到一个CNN中会返回一系列键值,包含标签及对应的概率。
ImageNet采用的图像尺寸一般为224×224,227×227,256×256,and299×299,但是并不是绝对。
VGG16,VGG19以及ResNet接受224×224的输入图像,而InceptionV3和Xception要求为299×299,如下代码所示:38#initializetheinputimageshape(224x224pixels)alongwith39#thepre-processingfunction(thismightneedtobechanged40#basedonwhichmodelweusetoclassifyourimage)41inputShape=(224,224)42preprocess=imagenet_utils.preprocess_input4344#ifweareusingtheInceptionV3orXceptionnetworks,thenwe45#needtosettheinputshapeto(299x299)[ratherthan(224x224)]46#anduseadifferentimageprocessingfunction47ifargs["model"]in("inception","xception"):48inputShape=(299,299)49preprocess=preprocess_input
这里我们初始化inputShape为224×224像素,初始化预处理函数为keras.preprocess_input——执行meansubtraction运算。
如果使用Inception或者Xception,inputShape需要改为299×299像素,预处理函数改为separatepre-processing函数。
下一步就是从磁盘载入网络架构的weights,并实例化模型:51#loadourthenetworkweightsfromdisk(NOTE:ifthisisthe52#firsttimeyouarerunningthisscriptforagivennetwork,the53#weightswillneedtobedownloadedfirst--dependingonwhich54#networkyouareusing,theweightscanbe90-575MB,sobe55#patient;theweightswillbecachedandsubsequentrunsofthis56#scriptwillbe*much*faster)57print("[INFO]loading{}...".format(args["model"]))58Network=MODELS[args["model"]]59model=Network(weights="imagenet")
注意:VGG16和VGG19的weights大于500MB,ResNet的约等于100MB,Inception和Xception的介于90-100MB之间。如果这是你第一次运行某个网络,这些weights会自动下载到你的磁盘。下载时间由你的网络速度决定,而且下载完成后,下一次运行代码不再需要重新下载。61#loadtheinputimageusingtheKerashelperutilitywhileensuring62#theimageisresizedto`inputShape`,therequiredinputdimensions63#fortheImageNetpre-trainednetwork64print("[INFO]loadingandpre-processingimage...")65image=load_img(args["image"],target_size=inputShape)66image=img_to_array(image)6768#ourinputimageisnowrepresentedasaNumPyarrayofshape69#(inputShape[0],inputShape[1],3)howeverweneedtoexpandthe70#dimensionbymakingtheshape(1,inputShape[0],inputShape[1],3)71#sowecanpassitthroughthenetwork72image=np.expand_dims(image,axis=0)7374#pre-processtheimageusingtheappropriatefunctionbasedonthe75#modelthathasbeenloaded(i.e.,meansubtraction,scaling,etc.)76image=preprocess(image)
第65行从磁盘载入输入图像,并使用提供的inputShape初始化图像的尺寸。
第66行将图像从PIL/Pillow实例转换成NumPy矩阵,矩阵的shape为(inputShape[0],inputShape[1],3)。
因为我们往往使用CNN来批量训练/分类图像,所以需要使用np.expand_dims在矩阵中添加一个额外的维度,如第72行所示;添加后矩阵shape为(1,inputShape[0],inputShape[1],3)。如果你忘记添加这个维度,当你的模型使用.predict时会报错。
最后,第76行使用合适的预处理函数来执行meansubtraction/scaling。
下面将我们的图像传递给网络并获取分类结果:78#classifytheimage79print("[INFO]classifyingimagewith'{}'...".format(args["model"]))80preds=model.predict(image)81P=imagenet_utils.decode_predictions(preds)8283#loopoverthepredictionsanddisplaytherank-5predictions+84#probabilitiestoourterminal85for(i,(imagenetID,label,prob))inenumerate(P[0]):86print("{}.{}:{:.2f}%".format(i+1,label,prob*100))
第80行调用.predict函数,并从CNN返回预测值。
第81行的.decode_predictions函数将预测值解码为易读的键值对:标签、以及该标签的概率。
第85行和86行返回最可能的5个预测值并输出到终端。
案例的最后一件事,是通过OpenCV从磁盘将输入图像读取出来,在图像上画出最可能的预测值并显示在我们的屏幕上。88#loadtheimageviaOpenCV,drawthetoppredictionontheimage,89#anddisplaytheimagetoourscreen90orig=cv2.imread(args["image"])91(imagenetID,label,prob)=P[0][0]92cv2.putText(orig,"Label:{},{:.2f}%".format(label,prob*100),93(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.8,(0,0,255),2)94cv2.imshow("Classification",orig)95cv2.waitKey(0)
VGGNet,ResNet,Inception,和Xception的分类结果
所有的例子都是使用2.0以上版本的Keras以及TensorFlow后台做的。确保你的TensorFlow版本大于等于1.0,否则会报错。所有例子也都使用Theano后端做过测试,工作良好。
案例需要的图片以及代码请前往原文获取。
使用VGG16分类:1$pythonclassify_image.py--imageimages/soccer_ball.jpg--modelvgg16
图8:使用VGG16来分类足球(source)
输出为:soccer_ball,精确度为93.43%。
如果要使用VGG19,只需要替换下--network参数。1$pythonclassify_image.py--imageimages/bmw.png--modelvgg19
图9:使用VGG19来分类汽车(source)
输出为:convertible(敞篷车),精确度为91.76%。然而,我们看一下其它的4个结果:sportscar(跑车),4.98%(也对);limousine(豪华轿车),1.06%(不正确,但也合理);carwheel(车轮),0.75%(技术上正确,因为图中确实出现了轮子)。
从下面的例子,我们可以看到类似的结果:1$pythonclassify_image.py--imageimages/clint_eastwood.jpg--modelresnet
图10:使用ResNet分类(source).
ResNet成功将图像分类为revolver(左轮手枪),精确度69.79%。有趣的是rifle(步枪)为7.74%,assaultrifle(突击步枪)为5.63%。考虑到revolver的观察角度还有相对于手枪来说巨大的枪管,CNN得出这么高的概率也是合理的。1$pythonclassify_image.py--imageimages/jemma.png--modelresnet
图11:使用ResNet对狗进行分类
狗的种类被正确识别为beagle(小猎兔狗),精确度94.48%。
然后我试着分类《加勒比海盗》中的图片:1$pythonclassify_image.py--imageimages/boat.png--modelinception
图12:使用ResNet对沉船进行分类(source)
尽管ImageNet中有“boat”(船)这个类别,Inception网络仍然正确地将该场景识别为“(ship)wreck”(沉船),精确度96.29%。其它的标签,比如“seashore”(海滩),“canoe”(独木舟),“paddle”(桨),还有“breakwater”(...
一篇关于机器学习中的稀疏矩阵的介绍 推广视频课程
在矩阵中,如果数值为0的元素数目远远多于非0元素的数目,并且非0元素分布无规律时,则称该矩阵为稀疏矩阵;与之相反,若非0元素数目占大多数时,则称该矩阵为稠密矩阵。
大的稀疏矩阵在一般情况下是通用的,特别是在应用机器学习中,例如包含计数的数据、映射类别的数据编码,甚至在机器学习的整个子领域,如自然语言处理(NLP)。
本教程将向你介绍稀疏矩阵所呈现的问题,以及如何在Python中直接使用它们。
教程概述
本教程分为5部分;分别为:
稀疏矩阵
稀疏的问题
机器学习中的稀疏矩阵
处理稀疏矩阵
在Python中稀疏矩阵
稀疏矩阵
稀疏矩阵是一个几乎由零值组成的矩阵。稀疏矩阵与大多数非零值的矩阵不同,非零值的矩阵被称为稠密矩阵。
如果矩阵中的许多系数都为零,那么该矩阵就是稀疏的。对稀疏现象有兴趣是因为它的开发可以带来巨大的计算节省,并且在许多大的实践中都会出现矩阵稀疏的问题。
—第1页,《稀疏矩阵的直接教学方法》(Direct Methods for Sparse Matrices),第二版,2017年。
矩阵的稀疏性可以用一个得分来量化,也就是矩阵中零值的个数除以矩阵中元素的总个数。
sparsity= count zeroelements/ totalelements
下面是一个小的3×6稀疏矩阵的例子。
1, 0, 0, 1, 0, 0
A = (0, 0, 2, 0, 0, 1)
0, 0, 0, 2, 0, 0
这个例子在矩阵中的18个元素中有13个零值,这个矩阵的得分是0.722或约72%。
稀疏的问题
稀疏矩阵会导致空间复杂度和时间复杂度的问题。
空间复杂度
非常大的矩阵需要大量的内存,而我们想要处理的一些非常大的矩阵是稀疏的。
在实践中,大多数大型矩阵都是稀疏的——几乎所有的项都为零。
—第465页,《线性代数介绍》(Introduction to Linear Algebra),第五版,2016年。
一个非常大的矩阵的例子是,因为它太大而不能存储在内存中,这是一个显示从一个网站到另一个网站的链接的链接矩阵。一个更小的稀疏矩阵的例子可能是一个单词或术语的出现矩阵,在一本书中与所有已知的英语单词对应。
在这两种情况下,所包含的矩阵都是稀疏的,其零值比数据值要多。将这些稀疏矩阵表示为稠密矩阵的问题是对内存的要求,并且必须为矩阵中的每个32位或64位零值做出分配。
这显然是对内存资源的浪费,因为这些零值不包含任何信息。
时间复杂度
假设一个非常大的稀疏矩阵可以适应内存,我们将需要对这个矩阵执行操作。
简单地说,如果矩阵包含了大部分零值,也就是没有数据,那么在这个矩阵中执行操作可能需要很长时间,其中的大部分计算都需要或将零值相加或相乘。
在这样的问题上使用线性代数的一般方法是很浪费的,因为大多数O(N^3)算术运算都用于求解方程组或反转(invert)包含零操作数的矩阵。
—第75页,《数值分析:科学计算的艺术》(Numerical Recipes: The Art of Scientific Computing),第三版,2007年。
这是矩阵运算的时间复杂度增加的问题,随着矩阵的大小而增加。
当我们考虑到即使是琐碎的机器学习方法可能需要对每一行、列甚至整个矩阵进行许多操作时,这个问题也会变得更加复杂,从而导致执行时间大大延长。
机器学习中的稀疏矩阵
稀疏矩阵在应用机器学习中经常出现。
在这一节中,我们将讨论一些常见的例子,以激发你对稀疏问题的认识。
数据
稀疏矩阵在某些特定类型的数据中出现,最值得注意的是记录活动的发生或计数的观察。
三个例子包括:
用户是否在一个电影目录中有曾经看过的电影。
用户是否在一个产品目录中有已经购买过的产品。
在一个歌曲目录中数出收听过的歌曲的数量。
数据准备
在准备数据时,稀疏矩阵会出现在编码方案中。
三种常见的例子包括:
独热编码,用来表示分类数据为稀疏的二进制向量。
计数编码,用于表示文档中词汇的频率。
TF-IDF编码,用于表示词汇中标准化的单词频率得分。
领域研究
机器学习中的一些领域必须开发专门的方法来解决稀疏问题,因为输入的数据几乎总是稀疏的。
三个例子包括:
用于处理文本文档的自然语言处理。
推荐系统在一个目录中进行产品使用。
当处理图像时计算机视觉包含许多黑色像素(black pixel)。
如果在语言模型中有100,000个单词,那么特征向量长度为100,000,但是对于一个简短的电子邮件来说,几乎所有的特征都是0。
—第22页,《人工智能:一种现代方法》(Artificial Intelligence: A Modern Approach),第三版,2009年。
处理稀疏矩阵
表示和处理稀疏矩阵的解决方案是使用另一个数据结构来表示稀疏数据。
零值可以被忽略,只有在稀疏矩阵中的数据或非零值需要被存储或执行。
多个数据结构可以用来有效地构造一个稀疏矩阵;下面列出了三个常见的例子。
Dictionary of Keys。在将行和列索引映射到值时使用字典。
List of Lists。矩阵的每一行存储为一个列表,每个子列表包含列索引和值。
Coordinate List。一个元组的列表存储在每个元组中,其中包含行索引、列索引和值。
还有一些更适合执行高效操作的数据结构;下面列出了两个常用的示例。
压缩的稀疏行。稀疏矩阵用三个一维数组表示非零值、行的范围和列索引。
压缩的稀疏列。与压缩的稀疏行方法相同,除了列索引外,在行索引之前被压缩和读取。
被压缩的稀疏行,也称为CSR,通常被用来表示机器学习中的稀疏矩阵,因为它支持的是有效的访问和矩阵乘法。
在Python中稀疏矩阵
SciPy提供了使用多种数据结构创建稀疏矩阵的工具,以及将稠密矩阵转换为稀疏矩阵的工具。
许多在NumPy阵列上运行的线性代数NumPy和SciPy函数可以透明地操作SciPy稀疏数组。此外,使用NumPy数据结构的机器学习库也可以在SciPy稀疏数组上透明地进行操作,例如用于一般机器学习的scikit-learn和用于深度学习的Keras。
存储在NumPy数组中的稠密矩阵可以通过调用csr_matrix函数将其转换为一个稀疏矩阵。
在下面的例子中,我们将一个3×6的稀疏矩阵定义为一个稠密数组,将它转换为CSR稀疏表示,然后通过调用todense函数将它转换回一个稠密数组。
# dense to sparse
fromnumpy importarray
from scipy.sparse import csr_matrix
# createdense matrix
A = array([[1, 0, 0, 1, 0, 0], [0, 0, 2, 0, 0, 1], [0, 0, 0, 2, 0, 0]])
print(A)
# convert to sparse matrix (CSR method)
S = csr_matrix(A)
print(S)
# reconstruct dense matrix
B = S.todense
print(B)
运行该示例首先打印已定义的稠密数组,接着是CSR表示,然后是重新构建的稠密矩阵。
[[1 0 0 1 0 0]
[0 0 2 0 0 1]
[0 0 0 2 0 0]]
(0, 0) 1
(0, 3) 1
(1, 2) 2
(1, 5) 1
(2, 3) 2
[[1 0 0 1 0 0]
[0 0 2 0 0 1]
[0 0 0 2 0 0]]
NumPy并没有提供一个函数来计算矩阵的稀疏性。
不过,我们可以很容易地计算出矩阵的密度,然后从一个矩阵中减去它。NumPy数组中的非零元素可以由count_nonzero函数给出,数组中元素的总数可以由数组的大小属性给出。因此,数组的稀疏性可以被计算为:
sparsity = 1.0 - count_nonzero(A) / A.size
下面的例子演示了如何计算数组的稀疏性。
# calculate sparsity
from numpy import array
fromnumpy importcount_nonzero
# createdense matrix
A =array([[1, 0, 0, 1, 0, 0], [0, 0, 2, 0, 0, 1], [0, 0, 0, 2, 0, 0]])
print(A)
# calculate sparsity
sparsity = 1.0 - count_nonzero(A) / A.size
print(sparsity)
运行这个例子首先打印出定义的稀疏矩阵,接着是矩阵的稀疏性。
[[1 0 0 1 0 0]
[0 0 2 0 0 1]
[0 0 0 2 0 0]]
0.7222222222222222
总结
在学习了这篇教程之后,你知道了:
稀疏矩阵几乎包含全部零值,并且与稠密矩阵不同。
你可能会在数据、数据准备和机器学习的子领域中遇到稀疏矩阵。
有许多有效的方法可以存储和使用稀疏矩阵,而SciPy提供了你可以直接使用的实现。
从零基础教你利用PyGame开发一个小游戏 企业视频课程
利用PyGame从零基础教你开发一个小游戏
PyGame是 SDL 库的 Python 包装器(wrapper)。SDL 是一个跨平台库,支持访问计算机多媒体硬件(声音、视频、输入等)。SDL 非常强大,但美中不足的是,它是基于 C 语言的,而 C 语言比较难懂,因此我们采用 PyGame 。
在本教程中,我们将介绍 PyGame 的基本逻辑和冲突检测,以及如何在屏幕上绘图和将外部文件导入到游戏中。
提示:教程假定你对 Python 的语法、文件结构和面向对象的程序设计已经有了基本的了解。
准备工作
打开PyGame下载页面,根据你的操作系统和 Python 版本下载合适的 PyGame 安装包。如果你使用的是 Python 3,那么请下载 1.9.2 版.
EarlGrey:在下载页面找不到 1.9.2 版的下载链接,但是 pip install 可以安装。
新建一个 .py 文件,然后输入以下代码:
import pygamefrom pygame.locals import *pygame.init()
与其他 Python 程序一样,我们首先导入想要使用的模块。这里,我们将导入 pygame 和 pygame.locals ,后续我们将使用其中的一些常量。最后一行会初始化所有导入的 PyGame 模块,在做其他操作之前必须执行调用该函数。
基础对象
屏幕对象
首先,我们需要一张画布,我们称之为“屏幕”,它是我们绘画的平台。为了创建一个屏幕,我们需要调用pygame.display 中的 set_mode 方法,然后向 set_mode() 传递包含屏幕窗口宽度和高度的元组(本教程中使用 800x600 尺寸)。
import pygamefrom pygame.locals import *pygame.init()screen = pygame.display.set_mode((800,600))
运行上述代码,将会弹出一个窗口,然后当程序退出后又立即消失。一点都不酷嘛,对吧?下一节,我们将介绍游戏的主循环,它将确保只有在我们给它正确的输入时程序才会退出。
游戏主循环
游戏主循环/事件循环是所有操作发生的地方。在游戏过程中,它不断的更新游戏状态,渲染游戏画面和收集输入指令。创建循环时,需要确保我们有办法跳出循环,退出应用。为此,我们将同时介绍一些基本的用户输入指令。所有的用户输入(和我们稍稍后提到的其他事件)都会进入 PyGame 的事件队列,通过调用 pygame.event.get() 可以访问该队列。这将返回一个包含队列里所有事件的列表,我们将循环这个列表,并根针对相应的事件类型做出反应。现在我们只关心 KEYDOWN 和 QUIT 事件:
# 用于保证主循环运行的变量runnning = True# 主循环!while running:
将上述代码添加到之前的代码下,并运行。你应该看到一个空的窗口。只有你按下 ESC 键 或者触发一个 QUIT 事,否则这个窗口不会消失。
Surface 和 Rects
Surface和 Rects是 PyGame 中的基本构件。可以将 Surface 看作一张白纸,你可以在上面随意绘画。我们的屏幕对象也是一个 Surface 。它们可以包含图片。Rects 是 Surface 中矩形区域的表示。
让我们创建一个 50x50 像素的 Surface,然后给它涂色。由于屏幕是黑色的,所以我们使用白色。 我们然后调用 get_rect() 在 Surface上 得到一个矩形区域和 Surface 的 x 轴 和 y 轴。
# 创建Surface 并用原则设定它的长度和宽度surf = pygame.Surface((50,50))# 设定Surface的颜色,使其和屏幕分离surf.fill((255,255,255))rect = surf.get_rect()
Blit 和 Flip
仅仅只是创建了 Surface 并不能在屏幕上看到它。为此我们需要将这个 Surface 绘制(Blit)到另一个 Surface 上。Blit 是一个专业术语,意思就是绘图。你仅仅只能从一个Surface Blit 到另一个Surface,我们的屏幕就是一个 Surface 对象。以下是我们如何将 surf 画到屏幕上:
# 这一行表示:将surf画到屏幕 x:400.y:300的坐标上screen.blit(surf,(400,300))pygame.display.flip()
blit() 有两个参数:要画的 Surface 和 在源 Surface 上的坐标。此处我们使用屏幕的中心,但是当你运行代码时,你会发现我们的 surf 并没有出现在屏幕的中心。这是因为 blit() 是从左上角开始画 surf 。
注意在 blit 之后的 pygame.display.filp() 的调用。Flip将会更新自上次 flip 后的整个屏幕,两次 flip 之间发生的修改都将在屏幕上显示。没有调用flip()那就什么也不会出现。
Sprites
什么是 Sprites ?从编程术语来讲,Sprites 是屏幕上事物的二维表达。本质上来讲,Sprite 就是一个图片。Pygame 提供一个叫做 Sprites 的基础类,它就是用来扩展的,可以包含想要在屏幕上呈现的对象一个或多个图形表示。我们将会扩展Sprite 类,这样可以使用它的内建方法。我们称这个新的对象为 Player 。Plyaer 将扩展 Sprite,现在只有两个属性:surf 和 rect。我们也会给 surf涂色(本教程使用白色),如之前 surface 例子,只是现在 Surface 属于 Player :
class Player(pygame.sprite.Sprite):
现在我们将上述代码整合在一起:
# 调用pygame模块import pygame# 调用 pygame.locals 使容易使用关键参数from pygame.locals import *# 定义Player对象 调用super赋予它属性和方法 # 我们画在屏幕上的surface 现在是player的一个属性class Player(pygame.sprite.Sprite):
运行上述代码,你将会在屏幕中心看到一个白色的矩形:
如果将 screen.blit(player.surf,(400,300)) 改成 screen.blit(player.surf,player.rect) ,你觉得会发生什么?修改之后,试着在控制台中打印 player.rect 。rect 的前两个属性分别是 rect 左上角的 x 和 y 轴坐标。当你将 rect 传递给 blit ,它将会根据这个坐标画 surface 。我们后续将使用它控制 player 移动。
用户输入
现在开始才是有趣的部分。我们要把 Player 变得可控制!之前我们提过,按键事件 pygame.event.get() 将把最新的事件从事件堆(event stack)中移除。Pygame 还有另外一个事件方法,pygame.event.get_pressed()。get_pressed()方法返回一个队列,其中包含了所有按键事件组成的字典,我们将把它放在主循环中,这样我们将在每一帧上的按键。
pressed_keys = pygame.event.get_presssed()
现在我们将写一个方法,接收上面那个字典,并且根据按下的键定义 sprite 的行为,代码如下:
def update(self,pressed_keys):
K_UP、K_DOWN、K_LEFT、K_RIGHT 对应键盘上的上、下、左 右方向键。我们判断这些键是否按下,如果它为真,那么我们就朝相应的方向移动 rect()。Rects 有两个内建的移动方法,此处我们使用 move in place move_ip() ,因为我们希望移动 rect 并且不用复制它。
将上述方法添加到 Player 类,将 get_pressed() 调用放在主循环中。整体代码现在应该是这样的:
现在你可以使用方向键移动矩阵块了。也许你注意到了,你可以将矩形块移出屏幕,这可能并不是你想要的。所以我们我们需要往 update 方法中添加一些逻辑,检测矩形的坐标是否移出了 800x600 的屏幕边界;如果出了边界,那么就将它放回在边界上:
上述代码没有使用 move 方法,我们只要修改 上下左右的相应坐标即可。
现在我们添加一些敌人!
首先我们创建一个新的 sprite 类,命名为 Enemy。依照创建 player 的格式创建:
class Enemy(pygame.sprite.Sprite):
以上有几点需要说明。首先,当我们在 surface 上调用 get_rect 时,我们将核心属性 x 设为 820 ,y 的坐标为一个随机数,由 random.randint() 生成。
在最终的代码中,我们会在文件的开头导入 Random 库(import random)。为什么选择随机数?因为我们希望敌人从屏幕右边(820)的随机位置(0-600)上出现。 我们还将使用 random 设置敌人的速度属性,这样敌人就会有快有慢。
敌人的 update() 方法没有参数限制(我们不关心敌人的输入),只要让它向着屏幕左边以一定的速度移动就可以了。update 方法中的最后一个 if 语句检测敌人右侧是否通过了屏幕左边边界(要确保它们不会一碰到屏幕的边界就消失)。当他们通过屏幕的边界后,我们调用 Sprite 的内建方法 kill() ,从 sprite 组中删除它们,这样它们就不会再被渲染出来。kill 不会释放被它们占用的内存, 需要你确保你不再引用它们,以便 Python 的垃圾回收器回收。
Groups
Pygame 提供的另一个很有用的对象是 Sprite 的 Groups。诚如其名,是 Sprite 的集合。为什么我们要使用 sprite.Group 而不是列表呢? 因为 sprite.Group 有一些内建的方法,有助于解决冲突和更新问题。那现在就创建一个 Group,用来包含游戏中的所有 Sprites 。创建完 Group 后,我们要将 Player 添加到里面,因为它是我们目前唯一的 Sprite 。我们也可以为敌人创建一个 group 。 当我们调用 Sprite 的 kill() 方法时,sprite 将会从其所在的全部 group 中删除。
enemies = pygame.sprite.Group()all_sprites = pygame.sprite.Group()all_sprites.add(player)
现在有了 all_sprites 的 group ,我们接着改变对象渲染方式,只要渲染 group 中的所有对象即可:
for entity in all_sprites:
现在,任何放到 all_sprites 中的对象都会被渲染出来。
自定义事件
现在我们为敌人创建了一个 sprite.Group ,但是并没有实际的敌人。那怎样才能在屏幕上出现敌人呢?我们当然可以在刚开始的时候创建一堆的敌人,但是这样游戏玩不了几秒。为此,我们创建一个自定义事件,它隔几秒钟就会触发创建一批敌人。我们要监听该事件,方式和监听按键或退出事件一样。创建自定义事件十分容易,只要命名即可:
ADDENEMY = pygame.USEREVENT +1
这样就可以了!现在,我们有了一个叫做 ADDENEMY的事件,可以在主程序中监听它。这里我们只需要注意一点,即自定义事件需要有一个独特的值,要比 USEREVENT 的值大,这就是我们为什么设定它为 USEREVENT + 1。这里说明一点:自定义事件本质上就是整数常量。又因为比 USEREVENT 小的数值已经被内置函数占据,所以创建的任何自定义事件都要比 USEREVENT 大。
定义好事件之后,我们需要将它插入事件队列中。因为整个游戏过程中都要创建它们,所以将设置一个计时器。可以通过 PyGame 的 time() 对象实现。
pygame.time.set_timer(ADDENEMY,250)
这行代码告诉 PyGame 每隔 250 毫秒(四分之一秒) 触发一次 ADDENEMY 事件。这是在主游戏循环之外执行的,不过在整个游戏中都处于执行状态。现在我们添加一些监听事件的代码:
while running:
谨记:set_timer() 只能用来将事件插入到 PyGame 事件队列中,不做其他任何事情。
现在我们会监听ADDENEMY事件,当它触发时,将创建一个 Enemy类的实例。然后我们将实例添加到enemies 这个 Sprite Group(后续用它来检测冲突)和 all_sprites Group(这样它会和其他对象一起渲染)。
冲突
这才是 PyGame 的魅力所在!写冲突代码(collision code)很难,但是 PyGame 提供了很多冲突检测方法,你可以在这里查看其中一部分。本次教程使用 spritecollideany。spritecollideany() 接受一个 Sprite 对象和一个 Sprite.Group ,检测 Sprite 对象是否和 Sprite Group 中的其他 Sprites 冲突。这样,我们可以拿 Player 和敌人所在的 Sprite Group 对比,检测 player 是否被敌人击中。代码实现如下:
if pygame.sprite.spritecollideany(player,enemies):
检测 player 是否 enemies 中的 Sprites 冲突,如果发生冲突了,那么调用 player Sprite 的 kill() 方法。因为我们只渲染了 all_sprites Group 中的 sprites ,kill() 方法将从其所在的全部 Groups 中移出 Sprite ,player就不再出现,算是“杀死它了”。目前的完整代码如下:
# 调用pygame模块import pygame# 调用random模块import random# 调用 pygame.locals 使容易使用关键参数from pygame.locals import *# 定义Player对象 调用super赋予它属性和方法 # 我们画在屏幕上的surface 现在是player的一个属性class Player(pygame.sprite.Sprite):
测试一下!
图片
现在游戏可以玩了,但是长得挺丑的。接下来,我们将白色方块变成有意思的图片,让游戏看上去有游戏的样子。
前面的代码示例中,我们使用了涂色的 Surface 对象表示游戏里的所有事物。虽然这样有助于理解什么是 Surface 和它如何工作,但是却让游戏变得很丑!现在我们要给 player 和 enemy 添加一些图片。我喜欢自己画图,我把 player 画成小飞机,enemy 是导弹,这些可以从我的代码库中下载。欢迎你使用我的作品,自己画或者者下载一些免费游戏素材。
修改对象的构造函数
下面是我们现在的 player 构造函数:
新的构造函数将会是这个样子的:
我们想用一张图片替代 Surface 对象。我们将使用 pygame.image.load() 导入图片的路径。load() 方法将会返回一个 Surface 对象。我们然后在这个 Surface 对象上调用 convert() 创建副本,这样可以更快地将它画在屏幕上。
接下来,我们在图片上调用 set_colorkey() 方法。set_colorkey用于设置图片的颜色,如果不设置 Pygame 会将图片设置为透明。这里我选用白色,因为和飞机的背景色一致。RLEACCEL 是一个可选参数,它有助于 PyGame 在非加速显示器上更快地渲染。
最后,我们和之前一样调用 rect() 对象:在图片上调用 get_rect()。
谨记:图片仍然是一个 surface 对象,只不过它上面画了一张图。
对 enemy 构造函数做同样的操作:
现在的游戏虽然和以前一样,但是比之前漂亮多啦!但是我仍然觉得它少了点什么东西。让我们加点不断漂浮的白云,这样会有飞机划过蓝天的感觉。为此,我们需要遵循之前用过的一些原则。首先,我们创建 cloud 对象,画上白云的照片,其 update() 方法让它不停地向着屏幕左边移动。然后,我们需要添加一个自定义事件,每隔一段时间就生成白云(我们还要将添...
手把手搭建个人博客(图文教程) 公司视频课程
本文系投稿,作者:illgo
知音专栏
程序员的出路
写程序时该追求什么,什么是次要的?
如何准备Java初级和高级的技术面试
算法的力量,李开复聊算法的重要性
搭建个人博客
首先我们谈一谈搭建个人博客必要性,个人认为在学习过程中,被动接受知识对你的提高是轻微的。比如你看网课学习,在听老师讲解的时候感觉简单易懂,代码逻辑明了清晰,当自己敲代码的时候,却无从下手,这也属于眼高手低的范畴。
当自己主动学习,主动思考其效率和对个人的提升无疑是高于被动接受的。然而使你提高最大的是主动说出自己认知,把自己的知识和理解传达给他人,这种方式是对你提升无疑是最显著的。
可见搭建个人博客是非常有必要的,很多读者很早就要求我发一篇搭建个人博客的文章,由于个人时间愿意本来想直接转载,但是在自己实际操作中遇到很多坑,浪费不少时间,都是把主要步骤给出且时间久远,没有详细的图文配合。
最终成果:
PC端:
移动端:
一. 前言
本篇文章将会使用最初始的虚拟机安装崭新的系统(Windows 7)和DigitalOcean购买的服务器(CentOS7.5)作为示例,注意并不是让大家在虚拟机上搭建,为了模拟崭新的环境,作者采取虚拟机的方式,你们直接在个人电脑操作即可。
本文的特点是细节多,想到什么补充什么,因此,大家碰到问题时,可以通过细节上的不同找到问题所在.
本篇文章的受众:
极客,想拥有一个漂亮的博客,想快速了解,但不想把时间花在由于细节导致的各种问题上.只是想快速拥有一个在自己服务器上的博客的朋友.二. 博客框架的选择
如果你是我所说的本篇文章的受众,当你要搭建一个博客时,你绝不会想自己从头到脚写一个框架出来.我在这里向大家介绍几款流行的Blog框架:
Jekyll (https://jekyllrb/)hugo(http://gohugo.org/)django(https://djangoproject/)hexo(https://hexo.io/)这里我考虑了一下,并没有将需要我们自己管理数据库的重型CMS(如:WordPress等)纳入.选择的标准有什么呢?
美观程度系统需求搭建难度可扩展性插件提供文档是否全面假如你是python学习者,请选择django,这几乎是每个学习python的小伙伴的必经之路.本篇文章我选择的是Hexo,主要是它拥有我最喜欢的主题NeXT,满足了我最大的需求:美观.
如果你也和我一样美观是第一位,请点开每个框架的官网,找到他们的Theme下的示例,找出你最喜欢的即可.那么,我们将会从Hexo开始,其他框架的请参照具体官网文档,和他们的GitHub issues.
注: 和Hexo官网的Getting Started并不冲突,本文从零开始,全程截图,与官方文档互为补充.
三. Hexo介绍
Hexo 特点
支持Markdown: 支持Markdown意味着你可以把经历从排版中解放出来.轻量: 无需拥有后台及数据库,专心写好你的文章一键部署: 可以通过Git或者ftp来将生成的静态页面部署到服务器或者主机空间中插件丰富: 丰富的插件可以满足你的各种需求.Hexo的工作机制
Hexo基于Node.js,将/source文件夹下的资源(文章,图片,模板),按照预定的配置文件,转换成静态页面放置到/public目录下.如果需要预览或者部署,hexo会把public作为web目录处理.具体的细节可以通过实践接下来的步骤,来逐渐明晓.
Node.js和npm
如果您之前接触过Node.js,可以略过此部分.Node.js是一个基于Chrome V8引擎的JavaScript运行环境,为我们的Hexo提供js脚本的运行环境.而npm则是一个JavaScript的包管理工具.主流的很多语言都会有自己的包管理器(们):
PHPComposerRubygemPythonpipeasy_installJavascriptbowernpmyarn包管理器可以帮助你管理依赖,比如我们要装的Hexo以及Hexo插件,Hexo和Hexo插件是其他开发者开发的代码(Package),包管理器可以帮你下载并管理这些代码.
Hexo官网教程中使用的是npm,但是由于网络问题以及便捷性,我选择用yarn来代替,当然,轻度使用的话只是在操作上大同小异.
四. 安装所需环境
注: 以下安装为本机客户端环境安装
安装Node.js
Node.js官网下载最新LTS版本(截至发稿v8.11.1)的Node.js并安装(如果你想同时管理多版本的Node.js请使用nvm)
Yarn官网下载最新版(截至发稿v1.5.1)并安装.
安装过程根据平台不同因人而异,在此略去不表.
在命令行中通过查看版本,确保我们的环境安装成功,并且可执行文件路径添加到了环境变量之中.
安装git
Git - Downloads(https://git-scm/downloads)下载 64-bit Git for Windows Portable ,双击选择依照你个人喜好指定的目录(注意介于权限问题,避免在c:/program files下),我安装在c:/some/git下右键开始(windows 10)-搜索,输入环境变量,编辑系统环境变量.
3. 选择环境变量
4. 在系统变量中选择Path点击编辑
5. 在末尾加上分号,然后将你安装目录下,git.exe所在路径填入(Windows 10更加简便,不再赘述)
6. 新开一个终端,然后查看git命令是否生效
7. 设置git设置本地用户的信息,引号内随意填
git config --global user.name "illgo"git config --global user.email "i@illgo"
安装Hexo
在这一步,我们通过Yarn来全局安装Hexo.在这里解释Yarn(npm相同)全局安装和本地安装的区别:
全局安装会把package存放在用户目录指定的目录下,本地安装则是存放在当前项目的node_module目录中.
全局安装使我们的二进制执行文件在操作系统内全局可用,比方说,命令行下输入命令即可运行.本地安装则是作为依赖供项目调用.我们安装hexo需要作为一个工具在命令行下可以直接运行,所以采用全局安装;而像hexo依赖的插件则可以在hexo项目目录下本地安装:
yarn globaladd hexo
如图即为安装成功!
五. 使用Hexo
你需要熟悉并修改两个配置文件
Hexo配置文件:myblog/_config.yml主题配置文件: myblog/themes/next/_config.yml创建站点目录
选择一个目录作为hexo站点目录,我选择在桌面新建一个myblog目录,作为hexo目录.
hexo init
安装主题NexT
在你的hexo站点目录下
git clone https://github/iissnan/hexo-theme-next themes/next
启用主题
用任意编辑器打开Hexo配置文件长成这样:
Ctrl + F 搜索 theme
theme: next
测试
hexo s --debug
按照提示,在浏览器中输入url,即可查看效果.
Hexo基本配置
我们可以通过Configuration | Hexo来了解_config.yml的基本配置方法.后面我们将会对其进行部分补充.
NexT主题配置
NexT主题的配置可以直接查看其配置文件中的注释,已经很详细了.
六.部署
部署是本文的重点,git部署有两种方式:
部署到自己的服务器上网上流行的GitHub Pages本文讲的是前者,服务器部署.
部署到服务器
我们的大体思路就是:hexo生成静态页面->git提交到git服务器->git服务器通过Hook运行脚本,在www目录下clone Git仓库->呈现页面
以临时创建的Centos 7.5服务器为示例
为SSH连接创建密钥对
由于使用git作为部署,所以无法避免的我们要使用密钥对的方式来连接,而不是口令.
1. 创建SSH密钥对:
打开刚才安装git目录下的git-bash.exe.git-bash已经提供了BASH环境,为了简单和快捷,我们使用这个git-bash作为ssh工具
ssh-keygen-trsa-C "i@illgo"
按照提示他会在当前用户目录下的.ssh目录下生成两个文件:
id_rsa 私钥id_rsa.pub 公钥
2. 在服务器中添加公钥
后将公钥添加到你在Centos的要登录的用户名下的,我们部署采用git用户,按道理应添加到服务器的/home/git/.ssh/authorized_keys文件中.但是本次采用DigitalOcean提供的添加公钥功能,在Web中操作,他会将公钥直接添加到/root/.ssh/authorized_keys中.如果你用其他的云服务器提供商,也会有类似的功能.此过程略去.
以下操作使用ssh连接服务器来操作.
在服务器上安装Web服务器
Hexo会根据你的_config.yml配置的source_dir下的资源文件,在public_dir下生成静态网页,部署这些静态文件.本次在服务器上安装Apache作为web服务器,通过Git和Git hook来部署.
安装Apache
yum -y install httpdsystemctl start httpd
然后访问该服务器的80端口即可看到Apache的测试页面:
在服务器上创建Git服务器
1. 服务器端安装Git
使用root账户
yum install -y git
2. 创建Git用户
useradd gitpasswd git
3. 创建空仓库
创建一个空仓库并把所有权给git用户
mkdir -p /home/git/repos/myblog.gitgit init --bare /home/git/repos/myblog.git
chown -R git:git /home/git/repos/myblog.git/
5. 在web目录下Clone
注意: 提交的用户要对/var/www/html有写权限
git clone /home/git/repos/myblog.git /var/www/htmlchown -R git:git /var/www/html/
4. 设置Git Hook
我们使用Git Hook的目的就是,在Hexo部署时,会把Hexo生成的静态web资源,自动部署到web目录下.我们需要一个post-receivew如下:
#!/bin/bash#判断是不是远端仓库IS_BARE=$(git rev-parse --is-bare-repository)if [ -z "$IS_BARE" ]; thenecho >&2 "fatal: post-receive: IS_NOT_BARE"exit 1fiunset GIT_DIRDeployPath="/var/www/html/"echo"==============================================="cd$DeployPathecho"deploying the myblog web"#git stash#git pull origin mastergit fetch --allgit reset --hard origin/mastertime=`date`echo"web server pull at webserver at time: $time."echo"================================================"
在DeployPath中填入你的www目录,这里我采用Apache Web服务器默认的位置.
在本地计算机中,我们可以利用刚git-bash.exe使用scp工具将这个文件上传到git服务器的hook目录下
配置git部署
添加 hexo-deployer-git 包依赖
yarn add hexo-deployer-git
设置_config.yml中的deploy:
- type: gitrepo: git@159.89.144.28:/home/repo/blog.git
测试
在Hexo站点目录下
hexo g --deploy
访问之前的url就会看到效果了!
七. SEO
关于SEO优化和其它内容我们单独一篇文章讲解,为了方便搜索引擎的检索,我们要尽量使自己文章的路径深度小,Google会收录你的网站,但是百度需要一定的浏览访问。你可以投稿原创文章到此平台,我们会注明你的博客链接,久而久之自己的博客会被收录。
问题记录
deploy的时候出现
mote: error: cannot run hooks/post-receive: No such file or directory
然而我的post-receive是存在且正确的,而且有执行权限.当直接运行post-receive的时候发现错误是这样的:
-bash: /home/git/repos/myblog.git/hooks/post-receive: /bin/bash^M: bad interpreter: No such file or directory
这是因为换行字符的原因,Unix下应该是LF,所以提示的”未找到文件”指的是/bin/bash末尾多了个字符未找到.使用编辑器切换一下换行方式并保存.
Git - remote: error: cannot run hooks/post-receive: No such file or directory报错查看下方链接:
https://stackoverflow/questions/11630433/git-remote-error-cannot-run-hooks-post-receive-no-such-file-or-directory/40355988
八、设置域名
首先去阿里云或者腾讯云购买域名,然后实名认证就可以了。
1. 打开域名管理,点击解析
2. 添加记录,选择 www
3. 记录值为自己服务器的ip地址
4. 输入 wang91
总结
以上便是完成个人博客搭建的教程了,如果你在操作过程中遇到了不懂的问题或者困难,请使用下方询问码,尽量问题说清楚。本来想从网上转载一篇,发现教程都不是面向基础的同学,如果搭建成功,请打赏原作者,原创不易,多多支持。
部署过程遇到困难可以咨询教程作者:
搭建成功、打赏作者
【Java知音】实时干货推荐栏
红黑树并没有我们想象的那么难(上)算法之单源最短路径稳定排序与不稳定排序算法之广度优先搜索
Python3中Selenium使用方法(连载) 企业视频课程
欢迎关注天善智能,我们是专注于商业智能BI,大数据,数据分析领域的垂直社区,学习,问答、求职一站式搞定!
登陆天善社区查看更多系列:
Python3中BeautifulSoup的使用方法
Python3中PyQuery的使用方法
Python3中正则表达式使用方法
基本使用
首先我们来大体看一下Selenium有一些怎样的功能,先用一段实例代码来演示一下:
运行代码之后,如果正确配置好了ChromeDriver,可以发现会自动弹出一个浏览器,浏览器首先会跳转到百度,然后在搜索框中输入Python进行搜索,然后跳转到搜索结果页,等待搜索结果加载出来之后,控制台分别会输出当前的URL,当前的Cookies还有网页源代码。
控制台输出结果:
源代码过长在此省略,那么当前的URL,Cookies,源代码都是浏览器中的真实内容。所以说,如果我们用Selenium来驱动浏览器加载网页的话,我们就可以拿到JavaScrit渲染的结果了。
下面我们来详细介绍一下Selenium的用法。
Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还有手机端的浏览器Android、BlackBerry等,另外无界面浏览器PhantomJS也同样支持。
我们可以用如下的方式初始化:
这样我们就完成了一个浏览器对象的初始化,接下来我们要做的就是调用browser对象,让其执行各个动作,就可以模拟浏览器操作了。
我们可以用get()方法来请求一个网页,参数传入链接URL即可,比如在这里我们用get()方法访问淘宝,然后打印出源代码,代码如下:
单个元素
Selenium可以驱动浏览器完成各种操作,比如填充表单,模拟点击等等,比如我们想要完成向某个输入框输入文字的操作,总得需要知道这个输入框在哪里吧?所以Selenium提供了一系列查找元素的方法,我们可以用这些方法来获取想要的元素,以便于下一步执行一些动作或者提取信息。
比如我们想要从淘宝页面中提取搜索框这个元素,首先观察它的源代码:
可以发现它的ID是q,Name也是q,还有许多其他属性,我们获取它的方式就有多种形式了,比如find_element_by_name()是根据Name值获取,ind_element_by_id()是根据ID获取,另外还有根据XPath、CSS Selector等获取。
我们代码实现一下:
在这里我们使用了三种方式获取输入框,根据ID,CSS Selector,和XPath获取,它们返回的结果是完全一致的。
运行结果:
可以看到三个元素都是WebElement类型,是完全一致的。
在这里列出所有获取单个元素的方法:
find_element_by_idfind_element_by_namefind_element_by_xpathfind_element_by_link_textfind_element_by_partial_link_textfind_element_by_tag_namefind_element_by_class_namefind_element_by_css_selector
另外Selenium还提供了通用的find_element()方法,它需要传入两个参数,一个是查找的方式By,另一个就是值,实际上它就是find_element_by_id()这种方法的通用函数版本,比如find_element_by_id(id)就等价于find_element(By.ID, id)。
我们用代码实现一下:
这样的查找方式实际上功能和上面列举的查找函数完全一致,不过参数更加灵活。
如果我们查找的目标在网页中只有一个,那么完全可以用find_element()方法,但如果有多个元素,再用find_element()方法查找就只能得到第一个元素了,如果要查找所有满足条件的元素,那就需要用find_elements()这样的方法了,方法名称中element多了一个s。
比如我们在这里查找淘宝导航条的所有条目就可以这样来写:
在此简化了一下输出结果,中间部分省略。
可以看到得到的内容就变成了list类型,list的每个元素都是WebElement类型。
也就是说,如果我们用find_element()方法,只能获取匹配的第一个元素,结果是WebElement类型,如果用find_elements()方法,则结果是list类型,listd每个元素是WebElement类型。
函数的列表如下:
find_elements_by_idfind_elements_by_namefind_elements_by_xpathfind_elements_by_link_textfind_elements_by_partial_link_textfind_elements_by_tag_namefind_elements_by_class_namefind_elements_by_css_selector
当然我们和刚才一样,也可可以直接find_elements()方法来选择,所以也可以这样来写:
结果是完全一致的。
Selenium可以驱动浏览器来执行一些操作,也就是说我们可以让浏览器模拟执行一些动作,比较常见的用法有:
输入文字用send_keys()方法,清空文字用clear()方法,另外还有按钮点击,用click()方法。
我们用一个实例来感受一下:
在这里我们首先驱动浏览器打开淘宝,然后用find_element_by_id()方法获取输入框,然后用send_keys()方法输入iPhone文字,等待一秒之后用clear()方法清空输入框,再次调用send_keys()方法输入iPad文字,之后再用find_element_by_class_name()方法获取搜索按钮,最后调用click()方法完成搜索动作。
通过上面的方法我们就完成了一些常见元素的动作操作,更多的操作可以参见官方文档的交互动作介绍:http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement
在上面的实例中,一些交互动作都是针对某个元素执行的,比如输入框我们就调用它的输入文字和清空文字方法,按钮就调用它的点击方法,其实还有另外的一些操作它是没有特定的执行对象的。比如鼠标拖拽,键盘按键等操作。所以这些动作我们有另一种方式来执行,那就是加到动作链中。
比如我们现在实现一个元素拖拽操作,将某个元素从一处拖拽到另外一处,我们用代码来感受一下:
首先我们打开网页中的一个拖拽实例,然后依次选中要被拖拽的元素和拖拽到的目标元素,然后声明了ActionChains对象赋值为actions变量,然后通过调用actions变量的drag_and_drop()方法,然后再调用perform()方法执行动作,就完成了拖拽操作。
更多的动作链操作可以参考官方文档的动作链介绍:http://selenium-python.readthedocs.io/api.html#module-selenium.webdrivermon.action_chains
另外对于某些操作,API没有提供的,如下拉进度条等,可以直接模拟运行JavaScript,使用execute_script()方法。
在这里我们就利用了execute_script()方法将进度条下拉到最底部,然后弹出alert提示框。
所以说有了这个,基本上API没有提供的所有的功能都可以用执行JavaScript的方式来实现了。
我们在前面说过通过page_source属性可以获取网页的源代码,获取源代码之后就可以使用解析库如正则、BeautifulSoup、PyQuery等来提取信息了。
不过既然Selenium已经提供了选择元素的方法,返回的是WebElement类型,那么它也有相关的方法和属性来直接提取元素信息,如属性、文本等等。这样的话我们能就不用通过解析源代码来提取信息了,非常方便。
那接下来我们就看一下可以通过怎样的方式来获取元素信息吧。
我们可以使用get_attribute()方法来获取元素的属性,那么这个的前提就是先选中这个元素。
我们用一个实例来感受一下:
运行之后程序便会驱动浏览器打开知乎的页面,然后获取知乎的LOGO元素,然后将它的class打印出来。
控制台输出结果:
我们通过get_attribute()方法,然后传入想要获取的属性名,就可以得到它的值了。
每个WebEelement元素都有text属性,我们可以通过直接调用这个属性就可以得到元素内部的文本信息了,就相当于BeautifulSoup的get_text()方法、PyQuery的text()方法。
我们用一个实例来感受一下:
在这里们依然是先打开知乎页面,然后获取提问按钮这个元素,再将其文本值打印出来。
控制台输出结果:
另外WebElement元素还有一些其他的属性,比如id属性可以获取元素id,location可以获取该元素在页面中的相对位置,tag_name可以获取标签名称 ,size可以获取元素的大小,也就是宽高,这些属性有时候还是很有用的。
我们用实例来感受一下:
在这里我们首先获得了提问按钮这个元素,然后调用其id、location、tag_name、size属性即可获取对应的属性值。
我们知道在网页中有这样一种标签叫做iframe,也就是子Frame,相当于页面的子页面,它的结构和外部网页的结构是完全一致的。Selenium打开页面后,它默认是在父级Frame里面操作,而此时如果页面中还有子Frame,它是不能获取到子Frame里面的元素的。所以这时候我们就需要使用switch_to.frame()方法来切换Frame。
我们首先用一个实例来感受一下:
我们还是以上文演示动作链操作的网页为实例,首先我们通过switch_to.frame()方法切换到子Frame里面,然后我们尝试获取父级Frame里的LOGO元素,是不能找到的,找不到的话就会抛出NoSuchElementException异常,异常被捕捉之后就会输出NO LOGO,接下来我们重新切换回父Frame,然后再次重新获取元素,发现就可以成功获取了。
所以,当页面中包含子Frame时,如果我们想获取子Frame中的元素,需要先调用switch_to.frame()方法切换到对应的Frame,然后再进行操作。
在Selenium中,get()方法会在网页框架加载结束之后就结束执行,此时如果获取page_source可能并不是浏览器完全加载完成的页面,如果某些页面有额外的Ajax请求,我们在网页源代码中也不一定能成功获取到。所以这里我们需要延时等待一定时间确保元素已经加载出来。
在这里等待的方式有两种,一种隐式等待,一种显式等待。
当使用了隐式等待执行测试的时候,如果Selenium没有在DOM中找到元素,将继续等待,超出设定时间后则抛出找不到元素的异常, 换句话说,当查找元素而元素并没有立即出现的时候,隐式等待将等待一段时间再查找 DOM,默认的时间是0。
我们用一个实例来感受一下:
在这里我们用implicitly_wait()方法实现了隐式等待。
隐式等待的效果其实并没有那么好,因为我们只是规定了一个固定时间,而页面的加载时间是受到网络条件影响的。
所以在这里还有一种更合适的显式等待方法,它指定好要查找的元素,然后指定一个最长等待时间。如果在规定时间内加载出来了这个元素,那就返回查找的元素,如果到了规定时间依然没有加载出该元素,则会抛出超时异常。
我们用一个实例来感受一下:
在这里我们首先引入了WebDriverWait这个对象,指定好最长等待时间,然后调用它的until()方法,传入要等待条件expected_conditions,比如在这里我们传入了presence_of_element_located这个条件,就代表元素出现的意思,其参数是元素的定位元组,也就是ID为q的元素搜索框。
所以这样可以做到的效果就是,在10秒内如果ID为q的元素即搜索框成功加载出来了,那就返回该元素,如果超过10秒还没有加载出来,那就抛出异常。
对于按钮,可以更改一下等待条件,比如改为element_to_be_clickable,也就是可点击,所以查找按钮时是查找CSS选择器为.btn-search的按钮,如果10秒内它是可点击的也就是成功加载出来了,那就返回这个按钮元素,如果超过10秒还不可点击,也就是没有加载出来,那就抛出异常。
运行代码,在网速较佳的情况下是可以成功加载出来的。
控制台输出:
可以看到控制台成功输出了两个元素,都是WebElement类型。
如果网络有问题,10秒内没有成功加载,那就抛出TimeoutException,控制台输出如下:
关于等待条件,其实还有很多,比如判断标题内容,判断某个元素内是否出现了某文字,在这里将所有的加载条件列举如下:
等待条件含义
title_is标题是某内容
title_contains标题包含某内容
presence_of_element_located元素加载出,传入定位元组,如(By.ID, 'p')
visibility_of_element_located元素可见,传入定位元组
visibility_of可见,传入元素对象
presence_of_all_elements_located所有元素加载出
text_to_be_present_in_element某个元素文本包含某文字
text_to_be_present_in_element_value某个元素值包含某文字
frame_to_be_available_and_switch_to_it frame加载并切换
invisibility_of_element_located元素不可见
element_to_be_clickable元素可点击
staleness_of判断一个元素是否仍在DOM,可判断页面是否已经刷新
element_to_be_selected元素可选择,传元素对象
element_located_to_be_selected元素可选择,传入定位元组
element_selection_state_to_be传入元素对象以及状态,相等返回True,否则返回False
element_located_selection_state_to_be传入定位元组以及状态,相等返回True,否则返回False
alert_is_present是否出现Alert
更多详细的等待条件的参数及用法介绍可以参考官方文档:http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions
我们平常使用浏览器都有前进和后退功能,使用Selenium也可以完成这个操作,使用back()方法可以后退,forward()方法可以前进。
我们用一个实例来感受一下:
在这里我们连续访问三个页面,然后调用back()方法就可以回到第二个页面,接下来再调用forward()方法又可以前进到第三个页面。
使用Selenium还可以方便地对Cookies进行操作,例如获取、添加、删除Cookies等等。
我们再用实例来感受一下:
首先我们访问了知乎,然后加载完成之后,浏览器实际上已经生成了Cookies了,我们调用get_cookies()方法就可以获取所有的Cookies,然后我们添加一个C...
在Python 2.7即将停止支持时,我们为你准备了一份3.x迁移指南 行业视频课程
机器之心编译
目前,Python 科学栈中的所有主要项目都同时支持 Python 3.x 和 Python 2.7,不过,这种情况很快即将结束。去年 11 月,Numpy 团队的一份声明引发了数据科学社区的关注:这一科学计算库即将放弃对于 Python 2.7 的支持,全面转向 Python 3。Numpy 并不是唯一宣称即将放弃 Python 旧版本支持的工具,pandas 与 Jupyter notebook 等很多产品也在即将放弃支持的名单之中。对于数据科学开发者而言,如何将已有项目从 Python 2 转向 Python 3 成为了正在面临的重大问题。来自莫斯科大学的 Alex Rogozhnikov 博士为我们整理了一份代码迁移指南。
Python 3 功能简介
Python 是机器学习和其他科学领域中的主流语言,我们通常需要使用它处理大量的数据。Python 兼容多种深度学习框架,且具备很多优秀的工具来执行数据预处理和可视化。
但是,Python 2 和 Python 3 长期共存于 Python 生态系统中,很多数据科学家仍然使用 Python 2。2019 年底,Numpy 等很多科学计算工具都将停止支持 Python 2,而 2018 年后 Numpy 的所有新功能版本将只支持 Python 3。
为了使 Python 2 向 Python 3 的转换更加轻松,我收集了一些 Python 3 的功能,希望对大家有用。
使用 pathlib 更好地处理路径
pathlib 是 Python 3 的默认模块,帮助避免使用大量的 os.path.joins:
from pathlib importPath
dataset ='wiki_images'
datasets_root =Path('/path/to/datasets/')
train_path = datasets_root / dataset /'train'
test_path = datasets_root / dataset /'test'
for image_path in train_path.iterdir():
with image_path.open()as f:# note, open is a method of Path object
# do something with an image
Python 2 总是试图使用字符串级联(准确,但不好),现在有了 pathlib,代码安全、准确、可读性强。
此外,pathlib.Path 具备大量方法,这样 Python 新用户就不用每个方法都去搜索了:
p.exists()
p.is_dir()
p.parts()
p.with_name('sibling.png')# only change the name, but keep the folder
p.with_suffix('.jpg')# only change the extension, but keep the folder and the name
p.chmod(mode)
p.rmdir()
pathlib 会节约大量时间,详见:
文档:https://docs.python.org/3/library/pathlib.html;
参考信息:https://pymotw/3/pathlib/。
类型提示(Type hinting)成为语言的一部分
PyCharm 中的类型提示示例:
Python 不只是适合脚本的语言,现在的数据流程还包括大量步骤,每一步都包括不同的框架(有时也包括不同的逻辑)。
类型提示被引入 Python,以帮助处理越来越复杂的项目,使机器可以更好地进行代码验证。而之前需要不同的模块使用自定义方式在文档字符串中指定类型(注意:PyCharm 可以将旧的文档字符串转换成新的类型提示)。
下列代码是一个简单示例,可以处理不同类型的数据(这就是我们喜欢 Python 数据栈之处)。
def repeat_each_entry(data):
""" Each entry in the data is doubled
"""
index = numpy.repeat(numpy.arange(len(data)),2)
return data[index]
上述代码适用于 numpy.array(包括多维)、astropy.Table 和 astropy.Column、bcolz、cupy、mxnet.ndarray 等。
该代码同样可用于 pandas.Series,但是方式是错误的:
repeat_each_entry(pandas.Series(data=[0,1,2], index=[3,4,5]))# returns Series with Nones inside
这是一个两行代码。想象一下复杂系统的行为多么难预测,有时一个函数就可能导致错误的行为。明确了解哪些类型方法适合大型系统很有帮助,它会在函数未得到此类参数时给出提醒。
def repeat_each_entry(data:Union[numpy.ndarray, bcolz.carray]):
如果你有一个很棒的代码库,类型提示工具如 MyPy 可能成为集成流程中的一部分。不幸的是,提示没有强大到足以为 ndarrays/tensors 提供细粒度类型,但是或许我们很快就可以拥有这样的提示工具了,这将是 DS 的伟大功能。
类型提示 → 运行时的类型检查
默认情况下,函数注释不会影响代码的运行,不过它也只能帮你指出代码的意图。
但是,你可以在运行时中使用 enforce 等工具强制进行类型检查,这可以帮助你调试代码(很多情况下类型提示不起作用)。
@enforce.runtime_validation
def foo(text: str)->None:
print(text)
foo('Hi')# ok
foo(5)# fails
@enforce.runtime_validation
def any2(x:List[bool])->bool:
return any(x)
any ([False,False,True,False])# True
any2([False,False,True,False])# True
any (['False'])# True
any2(['False'])# fails
any ([False,None,"",0])# False
any2([False,None,"",0])# fails
函数注释的其他用处
如前所述,注释不会影响代码执行,而且会提供一些元信息,你可以随意使用。
例如,计量单位是科学界的一个普遍难题,astropy 包提供一个简单的装饰器(Decorator)来控制输入量的计量单位,并将输出转换成所需单位。
# Python 3
from astropy import units as u
@u.quantity_input()
def frequency(speed: u.meter / u.s, wavelength: u.m)->u.terahertz:
return speed / wavelength
frequency(speed=300_000 * u.km / u.s, wavelength=555* u.nm)
# output: 540.5405405405404 THz, frequency of green visible light
如果你拥有 Python 表格式科学数据(不必要太多),你应该尝试一下 astropy。你还可以定义针对某个应用的装饰器,用同样的方式来控制/转换输入和输出。
通过 @ 实现矩阵乘法
下面,我们实现一个最简单的机器学习模型,即带 L2 正则化的线性回归:
# l2-regularized linear regression: || AX - b ||^2 + alpha * ||x||^2 ->min
# Python 2
X = np.linalg.inv(np.dot(A.T, A)+ alpha * np.eye(A.shape[1])).dot(A.T.dot(b))
# Python 3
X = np.linalg.inv(A.T @ A + alpha * np.eye(A.shape[1]))@(A.T @ b)
下面 Python 3 带有 @ 作为矩阵乘法的符号更具有可读性,且更容易在深度学习框架中转译:因为一些如 X @ W + b[None, :] 的代码在 numpy、cupy、pytorch 和 tensorflow 等不同库下都表示单层感知机。
使用 ** 作为通配符
递归文件夹的通配符在 Python2 中并不是很方便,因此才存在定制的 glob2 模块来克服这个问题。递归 flag 在 Python 3.6 中得到了支持。
import glob
# Python 2
found_images = \
glob.glob('/path*.jpg') \
+ glob.glob('/path*.jpg') \
+ glob.glob('/path***.jpg')
# Python 3
found_images = glob.glob('/path*.jpg', recursive=True)
python3 中更好的选择是使用 pathlib:
# Python 3
found_images = pathlib.Path('/path/').glob('**/*.jpg')
Print 在 Python3 中是函数
Python 3 中使用 Print 需要加上麻烦的圆括弧,但它还是有一些优点。
使用文件描述符的简单句法:
print>>sys.stderr,"critical error"# Python 2
print("critical error", file=sys.stderr)# Python 3
在不使用 str.join 下输出 tab-aligned 表格:
# Python 3
print(*array, sep='\t')
print(batch, epoch, loss, accuracy, time, sep='\t')
修改与重新定义 print 函数的输出:
# Python 3
_print =print# store the original print function
defprint(*args,**kargs):
pass# do something useful, e.g. store output to some file
在 Jupyter 中,非常好的一点是记录每一个输出到独立的文档,并在出现错误的时候追踪出现问题的文档,所以我们现在可以重写 print 函数了。
在下面的代码中,我们可以使用上下文管理器暂时重写 print 函数的行为:
@contextlib.contextmanager
def replace_print():
import builtins
_print =print# saving old print function
# or use some other function here
builtins.print=lambda*args,**kwargs: _print('new printing',*args,**kwargs)
yield
builtins.print= _print
with replace_print():
上面并不是一个推荐的方法,因为它会引起系统的不稳定。
print 函数可以加入列表解析和其它语言构建结构。
# Python 3
result = process(x)if is_valid(x)elseprint('invalid item: ', x)
f-strings 可作为简单和可靠的格式化
默认的格式化系统提供了一些灵活性,且在数据实验中不是必须的。但这样的代码对于任何修改要么太冗长,要么就会变得很零碎。而代表性的数据科学需要以固定的格式迭代地输出一些日志信息,通常需要使用的代码如下:
# Python 2
print('{batch:3} {epoch:3} / {total_epochs:3} accuracy: {acc_mean:0.4f}±{acc_std:0.4f} time: {avg_time:3.2f}'.format(
batch=batch, epoch=epoch, total_epochs=total_epochs,
acc_mean=numpy.mean(accuracies), acc_std=numpy.std(accuracies),
avg_time=time / len(data_batch)
))
# Python 2 (too error-prone during fast modifications, please avoid):
print('{:3} {:3} / {:3} accuracy: {:0.4f}±{:0.4f} time: {:3.2f}'.format(
batch, epoch, total_epochs, numpy.mean(accuracies), numpy.std(accuracies),
time / len(data_batch)
))
样本输出:
12012/300 accuracy:0.8180±0.4649 time:56.60
f-strings 即格式化字符串在 Python 3.6 中被引入:
# Python 3.6+
print(f'{batch:3} {epoch:3} / {total_epochs:3} accuracy: {numpy.mean(accuracies):0.4f}±{numpy.std(accuracies):0.4f} time: {time / len(data_batch):3.2f}')
另外,写查询语句时非常方便:
query = f"INSERT INTO STATION VALUES (13, '{city}', '{state}', {latitude}, {longitude})"
「true pision」和「integer pision」之间的明显区别
对于数据科学来说这种改变带来了便利(但我相信对于系统编程来说不是)。
data = pandas.read_csv('timing.csv')
velocity = data['distance']/ data['time']
Python 2 中的结果依赖于『时间』和『距离』(例如,以米和秒为单位)是否被保存为整数。
在 Python 3 中,结果的表示都是精确的,因为除法的结果是浮点数。
另一个案例是整数除法,现在已经作为明确的运算:
n_gifts = money // gift_price # correct for int and float arguments
注意,该运算可以应用到内建类型和由数据包(例如,numpy 或 pandas)提供的自定义类型。
严格排序
# All these comparisons are illegal in Python 3
3<'3'
2 (3,4)<(3,None) (4,5)<[4,5] # False in both Python 2 and Python 3 (4,5)==[4,5] 防止不同类型实例的偶然性的排序。 sorted([2,'1',3])# invalid for Python 3, in Python 2 returns [2, 3, '1'] 在处理原始数据时帮助发现存在的问题。 旁注:对 None 的合适检查是(两个版本的 Python 都适用): if a isnotNone: pass if a:# WRONG check for None pass 自然语言处理的 Unicode s ='您好' print(len(s)) print(s[:2]) 输出: Python 2: 6\n Python 3: 2\n 您好. x = u'со' x +='co'# ok x +='со'# fail Python 2 在此失败了,而 Python 3 可以如期工作(因为我在字符串中使用了俄文字母)。 在 Python 3 中 strs 是 Unicode 字符串,对非英语文本的 NLP 处理更加方便。 还有其它有趣的方面,例如: 'a'< type < u'a'# Python 2: True 'a'< u'a'# Python 2: False from collections importCounter Counter('Mbelstück') Python 2: Counter({'\xc3': 2, 'b': 1, 'e': 1, 'c': 1, 'k': 1, 'M': 1, 'l': 1, 's': 1, 't': 1, '\xb6': 1, '\xbc': 1}) Python 3: Counter({'M': 1, '': 1, 'b': 1, 'e': 1, 'l': 1, 's': 1, 't': 1, 'ü': 1, 'c': 1, 'k': 1}) 这些在 Python 2 里也能正确地工作,但 Python 3 更为友好。 保留词典和**kwargs 的顺序 在 CPython 3.6+ 版本中,字典的默认行为类似于 OrderedDict(在 3.7+版本中已得到保证)。这在字典理解(和其他操作如 json 序列化/反序列化期间)保持顺序。 import json x ={str(i):i for i in range(5)} json.loads(json.dumps(x)) # Python 2 {u'1':1, u'0':0, u'3':3, u'2':2, u'4':4} # Python 3 {'0':0,'1':1,'2':2,'3':3,'4':4} 它同样适用于**kwargs(在 Python 3.6+版本中):它们的顺序就像参数中显示的那样。当设计数据流程时,顺序至关重要,以前,我们必须以这样繁琐的方式来编写: from torch import nn # Python 2 model = nn.Sequential(OrderedDict([ ('conv1', nn.Conv2d(1,20,5)), ('relu1', nn.ReLU()), ('conv2', nn.Conv2d(20,64,5)), ('relu2', nn.ReLU()) ])) # Python 3.6+, how it *can* be done, not supported right now in pytorch model = nn.Sequential( conv1=nn.Conv2d(1,20,5), relu1=nn.ReLU(), conv2=nn.Conv2d(20,64,5), relu2=nn.ReLU()) ) 注意到了吗?名称的唯一性也会被自动检查。 迭代地拆封 # handy when amount of additional stored info may vary between experiments, but the same code can be used in all cases model_paramteres, optimizer_parameters,*other_params = load(checkpoint_name) # picking two last values from a sequence *prev, next_to_last, last = values_history # This also works with any iterables, so if you have a function that yields e.g. qualities, # below is a simple way to take only last two values from a list *prev, next_to_last, last = iter_train(args) 默认的 pickle 引擎为数组提供更好的压缩 # Python 2 import cPickle as pickle import numpy print len(pickle.dumps(numpy.random.normal(size=[1000,1000]))) # result: 23691675 # Python 3 import pickle import numpy len(pickle.dumps(numpy.random.normal(size=[1000,1000]))) # result: 8000162 节省 3 倍空间,而且速度更快。实际上,类似的压缩(不过与速度无关)可以通过 protocol=2 参数来实现,但是用户...
Python基础学习之常用六大数据类型 流量视频课程
刚开始学习一门编程语言,除了了解运行环境与语言类型之外,最基本还是从该语言的基本数据类型开始学起。
Python六大常用数据类型:
int 整数 float 浮点数 str 字符串 list 列表 tuple 元组 dict 字典
讲解这些先说一下python中的变量与变量名。
变量其实本质上是一个具有特殊格式的内存,变量名则是指向这个内存的别名。python中的变量不需要声明,所有的变量必须赋值了才能使用。赋值的步骤:
a = 100
第一步:准备好值100第二部:准备好变量名a第三部:将值与名字进行关联
一、整数python将其他一些静态语言中的int、long,也就是整型和长整型合并为了一个。python中的int是边长的,也就是说可以存储无限大的整数,但是这是不现实的,因为没有这么多的内存够分配。整型不仅支持十进制,还支持二进制、八进制、十六进制。可以通过下面的方式来互相转换:
print(bin(20)) #转换二进制print(oct(20)) #转换八进制print(hex(20)) #转换十六进制
二、浮点型浮点数也就是小数,如22.1,44.2,也可以使用科学计数法,比如:1.22e8。python支持对整数和浮点数直接进行四则混合运算。整数运算结果仍然是整数,浮点数运算结果仍然是浮点数,但整数和浮点数混合运算的结果就变成浮点数了。
a = 1b = 1.1print(type(a+b)) #
三、字符串字符串在任何编程语言中都是最常用的数据类型。字符串的创建很简单,也是上面所说的三步,但是要加上单引号或者双引号。
a = "hello python"
也可以使用 “”" 创建多行字符串:
a = """ hello python"""
字符串可以通过下面方式进行截取或者连接:
print (str[0:4]) 输出第一个到倒数第四个的所有字符 print (str[0]) 输出单字符 第1个字符print (str[3:]) 输出从第四个开始之后的所有字符print (str * 2) 输出字符串两次print (str + "bbbb") 连接字符串
字符串常用函数:str.strip() 消除字符串s左右两边的空白字符(包括’\t’,’\n’,’\r’,’’)len(str) 获取字符串长度str.upper() 转换为大写str.lower() 转换为小写str.title() 每个单词首字母大写str.capitalize() 首字母大写字符串翻转:
a = 'abcde'print(a[::-1])
字符串分割:
a = 'hello,python'print(a.split(',')) #['hello', 'python'] 返回一个列表
相对应的还有一个将列表元素连接成字符串:
a = ['hello', 'python']str = '-'print(str.join(a)) # hello-python
四、列表列表的写法是一个方括号内的值用逗号分隔。比如上面的[‘hello’, ‘python’]。列表的数据项不需要具有相同的类型,列表中的每个元素都分配一个数字索引,第一个索引是0,第二个索引是1,依此类推。访问列表中的值可以通过下面的方式:
list1 = [1, 2, 3, 4, 5, 6]print(list1[2])
同样可以通过索引截取
print ("list1[2:5]: ", list1[2:5])
列表常用操作:list1.append(‘7’) 追加一个元素在末尾,每次只能添加一个len(list1) 返回列表元素个数max(list1) 返回列表元素最大值min(list1) 返回列表元素最小值list1.count(obj) 统计某个元素在列表中出现的次数list1.index(obj) 从列表中找出某个值第一个匹配项的索引位置list1.reverse() 反向列表中元素list1.clear() 清空列表list1.extend(seq) 在列表末尾一次性追加另一个序列中的多个值,也就是扩充了列表 append与extend的区别:
A = ['a', 'b', 'c']A.append(['d', 'e'])print(A) # ['a', 'b', 'c', ['d', 'e']]B = ['a', 'b', 'c']B.extend(['d', 'e'])print(B) # ['a', 'b', 'c', 'd', 'e']
extend方法只能接收list类型,它是把这个列表中的每个元素添加到原list中。append可以接收任意类型,追加到原list的末尾。
五、元组元组的创建也很简单,和list类似,只是把’[]‘换成了’()’。
tup1 = ('hello', 'python')
元组中只有一个元素的时候要注意:
tup2 = (10)tup3 = ('a')print(type(tup2)) #
因为这样会被解释器当做运算符,所以正确的方法是在第一个元素后面添加逗号。
tup4 = ('a',)print(type(tup4)) #
元组同样可以使用下标索引来访问元组中的值:
tup5 = ('hello', 'python', 'hello', 'word')print(tup5[1]) #pythonprint(tup5[1:3]) #('python', 'hello')
注意:元组是不可以被修改的。
tup6 = ('hello', 'python', 'hello', 'word')tup6[2] = 'aaa'
上面会出现一个异常: TypeError: ‘tuple’ object does not support item assignment.但是元组中如果包含了一个列表,那么这个列表是可以被修改的。
tup7 = ('hello', 'python', 'hello', 'word', ['aa', 'bb', 'cc'])tup7[-1][1] = 'ddd'print(tup7) # ('hello', 'python', 'hello', 'word', ['aa', 'ddd', 'cc'])
元组运算符:len(tup) 计算元素个数tup1 + tup2 连接生成新元组tup * 4 元组复制num in tup 元素是否存在,返回True/False
六、字典python中的字典就是key,value的形式。使用大括号包含起来。字典中的成员的键是唯一的,如果出现多个同名的键,那么写在后面覆盖前面的值。形式如下:
dict1 = {'a' : 1, 'b' : 2}
字典的常用操作最基本的也就是增删改查:获取:直接通过键来获取。
dict['b'] # 2
dict.keys() 获取字典中所有的键dict.values() 获取字典中的所有的值增加:
dict1['c'] = 3 #{'a': 1, 'b': 2, 'c': 3} #如果键存在则更新对应的值
修改:直接给键进行再次赋值就可以修改键对应的值了。如果键不存在,则变成添加成员。还可以通过:
dict1.update({"a":"11"})dict1.setdefault("a", "222") # 已存在的键则修改无效dict1.setdefault("d","222") # 不存在的话则创建dict1.setdefault("e") # 没有设置值为None
删除:使用pop删除指定键对应的成员,同时返回该值
print(dict1) # {'a': '11', 'b': 2, 'c': 3, 'd': '222', 'e': None}print(dict1.pop("a")) # aprint(dict1) # {'b': 2, 'c': 3, 'd': '222', 'e': None}#在不设置默认值的情况下,使用pop删除不存在的键,会报错。print(dict1.pop("f")) # 报错 KeyError: 'f'
如果设置了默认值, print(dict1.pop(“f”, None)),则不会报错,返回这个默认值。判断是否删除成功可以通过下面方式来判断:
if dict1.pop("f", None) == None: print('键不存在')else: print('删除成功')
以上则是python中最基本的数据类型以及用法,当然还有其他的数据类型,作者暂时只列举了这些。
更过python文章,关注作者哦!干货多多!!
Python字典操作技巧 行业视频课程
一、如何让字典保持有序
可以使用collections模块中的OrderedDict类,OrderedDict内部维护了一个双向链表,它会根据元素加入的顺序来排列键的位置,大小是普通字典的2倍多,所以,在使用时要做好需求分析。
from collections import OrderedDictd=OrderedDict() d['a']=1d['b']=2d['c']=3d['d']=4for key in d:print(key,d[key])
输出结果:
a 1b 2 c 3 d 4 [Finished in 0.2s]
二、如何对字典求最大值、最小值:
为了对字典内容做有用的计算,通常会利用zip()将字典的键和值反转过来:
prices={'a':50.3,'b':12.3,'c':15.98,'d':10.75 }min_price=min(zip(prices.values(),prices.keys()))max_price=max(zip(prices.values(),prices.keys()))print(min_price)print(max_price)
输出结果:
(10.75, 'd')(50.3, 'a')[Finished in0.2s]
三、如何找到两个字典中相同的地方:
python字典keys()方法同items()方法,返回的是keys-view对象,它们支付常见的集合操作如并,交,差;但values()方法不支持。
x={ 'a':1, 'b':5, 'c':10}y={ 'd':2, 'b':5, 'c':8}re_a=x.keys() & y.keys()print('x,y中均有的键是:',re_a)re_b=x.keys() - y.keys()print('x中有但y中没有的键是:',re_b)re_c=x.items() & y.items()print('x,y中相同的键值对:',re_c)
输出结果:
x,y中均有的键是: {'c', 'b'}x中有但y中没有的键是: {'a'}x,y中相同的键值对: {('b', 5)}[Finished in0.1s]
五、如何通过公共键对字典列表排序
利用operator模块中的itemgetter函数排序非常简单假设我们从数据库中获得的数据如下:
rows=[{'fname':'Brian','lname':'Jones','age':18},{'fname':'David','lname':'Beazley','age':25},{'fname':'John','lname':'Clesse','age':19},{'fname':'Bim','lname':'Jones','age':23}]
我们对以上记录进行排序非常简单:
from operator import itemgetterrows_by_fname=sorted(rows,key=itemgetter('fname'))rows_by_age=sorted(rows,key=itemgetter('age'))print(rows_by_fname)print(rows_by_age)
输出结果:
[{'fname': 'Bim', 'lname': 'Jones', 'age': 23}, {'fname': 'Brian', 'lname': 'Jones', 'age': 18}, {'fname': 'David', 'lname': 'Beazley', 'age': 25}, {'fname': 'John', 'lname': 'Clesse', 'age': 19}][{'fname': 'Brian', 'lname': 'Jones', 'age': 18}, {'fname': 'John', 'lname': 'Clesse', 'age': 19}, {'fname': 'Bim', 'lname': 'Jones', 'age': 23}, {'fname': 'David', 'lname': 'Beazley', 'age': 25}]
[Finished in0.1s]