Python简介

From FreeCAD Documentation
Revision as of 03:04, 3 July 2019 by Wconly (talk | contribs)
Macros
Python scripting tutorial
(2018年 11月) FreeCAD原本的设计是配合Python 2协同工作。许多功能可通过Python 3来实现,有些却不能。令FreeCAD完全支持Python 3是一项正处于开发中的任务。本文描述的内容均使用的是Python 2,但大多代码也可同样用于Python 3。特别是print()函数要优于老式的print语句。

本简短教程可供Python新手参阅。Python是一种开源、支持多平台的 编程语言。Python有几种不同于其他常见语言的特性,使之与众不同,而且还易于对此毫无经验的人使用:

  • Python被设计为一种易于常人阅读的语言,因此方便学习且代码容易理解。
  • Python是一种解释型语言,也就是它并非C那样的编译型语言,在程序运行前无需进行编译。您所编写的代码将如您所愿即刻逐行执行。由于代码可放慢脚步逐行执行,因此更便于学习与查找代码中错误。
  • 可将Python嵌入其他程序用作脚本语言。FreeCAD中配有一个内嵌的Python解释器;您可以在FreeCAD中编写Python代码来控制FreeCAD的各种组件,比如用来创建一个几何图形等等。这是一种极其强大的工具,程序员可通过它编写代码,以此取代仅能点击标有"创建球体"按钮的机械式操作;您可以借此自由方便地构建自己的工具,创建精准的几何图形,但程序员可能无法预览待创建的图形。
  • Python具有可扩展性,您可以轻松地为它接入新的模块来拓展其功能。例如,您可以借助特定模块使Python可以读写jpg图片、与twitter进行交互,甚至调度任务在操作系统上的执行,等等。

我们墙裂鼓励您亲自将代码输入至Python解释器。经过我们的激烈讨论,认为重点在于令代码跑起来,并显示出结果。不运行代码根本不会理解Python。因此,快动手吧!以下是相关简介,而并非完整的教程。但是我们希望这能为您深入理解FreeCAD中的内部机制而提供相关的基础知识。

解释器

用户在写程序时通常会打开文本编辑器或特定的编程环境(一般是配有其他辅助工具的文本编辑器),借此编写代码、编译并执行。当代码中存在一个或多个错误时,程序往往就跑不起来了。这时,您可以根据报错信息定位错误的根源。再返回编辑器,纠正错误,再次运行,如此往复直到程序可按预期执行为止。

在整个处理流程中,Python解释器的内部工作原理对用户而言都是透明的。所谓的解释器是一种附有命令提示符的Python窗口,在此,您可以方便地输入Python代码。如果您在自己的计算机上安装了Python(如果系统为Windows或Mac,从Python website下载;如果系统是GNU/Linux,则借助软件包资料库(package repository)进行安装), 您可从开始菜单中开启Python解释器。但是,FreeCAD中的下方窗口中也集成了Python解释器:

(如果没有找到,请依次点选View --> Panels --> Python console。)

解释器中显示着Python的版本,a >>>即为命令提示符,您可以在此输入Python代码。在解释器中编写代码很简单:一行对应一个指令。在按下回车键时,将执行本行代码(此前将自动在背后进行代码编译)。例如,请尝试编写下列代码:

print "hello"

print是一种特殊的Python关键字,顾名思义,它负责在屏幕上打印内容。当您按下回车键时,将处理所输入的代码,最后打印"hello"这条消息。如果您得到了一个错误信息,例如,让我们来这样写:

print hello

Python将告知我们,它不知道hello是神马。利用"符号可方便地指定字符串内的多个字符,用编程术语来讲,即一段文本。若不使用"符号,则print命令将认为hello并非一段文本,而是一个特殊的Python关键字。而重点在于,您会立即得到一个因此而产生的错误消息。通过按“up”键(或者在FreeCAD解释器中按CTRL+up),您将返回上一条编写的命令,并对此进行修正。

Python解释器中还设有一个内建的帮助系统。尝试输入:

help

或者,例如,当我们不知道上述打印hello命令哪里出了问题,而希望得知与"print"命令有关的信息时:

help("print")

您将得到一条既长又完整的print命令功能的描述。

现在让我们来全面了解一下Python解释器,先从最关键的部分入手吧!

变量

实话实说,打印"hello"字符串没什么意思。更有趣的是打印我们没有提前得知的内容,或令Python来为我们求出所需的内容。这便是引入变量这一概念的原因。简而言之,变量即为:存有特定值的名称。例如,输入下列内容:

a = "hello"
print a

我猜您已经知道这段代码的背后究竟发生了什么,我们将"hello"存在了"a"名下。现在,"a"在Python环境中已经不再是一个未知的名称了!我们可以在各处运用它,例如,将其用于上述打印命令中。我们几乎可以使用能想到的任意名称,仅需遵循一些简单的规则即可,比如名称中不能夹带空格与特定的标点符号。例如,我们可以这样写:

hello = "my own version of hello"
print hello

看到了吗?现在的hello是一个有定义的单词。但是,当我们无意中选取了Python中已经存在的名称作为变量名时会发生什么呢?这里,我们选取"print"作为变量名来保存字符串:

print = "hello"

Python很机智,告诉我们:没戏。它自己有一些"保留"的关键字是用户不能修改的。但是我们自己的变量却可以任意修改,这就是它们为什么被称为变量:内容可变。例如:

myVariable = "hello"
print myVariable
myVariable = "good bye"
print myVariable

我们可以改变myVariable的值。也可以复制变量:

var1 = "hello"
var2 = var1
print var2

请注意,关键的是要给变量起个有意义的名称。这是因为,经过一段时间后您可以会忘记变量"a"表示的是什么。例如,将某个变量命名myWelcomeMessage后,您可以轻松回忆起设立此变量的目的。这样便可以使您的代码直观地表达出自身的意义。

字母的大小写也非常重要。myVariable与myvariable是两个截然不同的变量,区别就在于其中字母v的大小写上。如果您输入print myvariable,系统就会返回一个“未定义”错误。

数字

当然,您一定知道编程的过程中要处理各种数据,不仅仅是前面提到的字符串,还有数字等等。有一点十分重要,Python必须知道它要处理的数据类型。在我们此前示范的打印hello的示例中,print命令知道我们提供的"hello"是个字符串。这是因为我们利用了引号"通知了print命令,它接下来要处理的是个文本字符串。

我们可以通过Python中的关键字type来检测一个变量的数据类型:

myVar = "hello"
type(myVar)

在这里,它将告诉我们,变量myVar中的内容是一个'str',也就是Python术语中字符串的简写。我们也可以使用Python提供的其他类型数据,如整数与浮点数:

firstNumber = 10
secondNumber = 20
print firstNumber + secondNumber
type(firstNumber)

这就更有趣了,不是吗?这就是说,我们现在拥有了一个强大的计算器了!来看看她是怎么跑起来的。Python知道10与20窦唯整数。所以他们在内部被存为"int",这样Python就可以把它们看作是整数并加以处理。再来看看下面的计算结果:

firstNumber = "10"
secondNumber = "20"
print firstNumber + secondNumber

看到了吗?在此,我们强制Python把这两个变量看作是文本而非数字。Python可以把两条文本合二为一,但是却不会计算两者之和。只不过我们现在要讨论的其实是整数。Python还支持浮点数。整数与浮点数的区别在于前者没有小数部分,而浮点数却有:

var1 = 13
var2 = 15.65
print "var1 is of type ", type(var1)
print "var2 is of type ", type(var2)

可以肆无忌惮地进行整数与浮点数的混合运算:

total = var1 + var2
print total
print type(total)

当然,最后的计算结果依旧存在小数部分,细不细?在此,Python会自动将结果记作一个浮点数。包括上述情况在内的若干情景中,Python会自动推测到底使用哪种类型。但在另外一些场景中却不是这样,例如:

varA = "hello 123"
varB = 456
print varA + varB

执行上述命令后,我们将得到一个错误信息。varA是一个字符串,而varB则是一个整数,Python不知道该如何处理了。但是,我们可以强制Python将数据的类型互相转化:

varA = "hello"
varB = 123
print varA + str(varB)

两个变量现在都是字符串,可以顺利执行后续操作了!请注意,变量varB的“字符串化”仅发生在打印过程中,但是其自身并没有发生任何改变。如果希望将varB永久转换为字符串,我们需要这样做:

varB = str(varB)

我们也可以通过int()与float()将数据转换为整数与浮点数类型:

varA = "123"
print int(varA)
print float(varA)

关于Python命令的备注

您一定注意到了,我们在本部分中用打印命令显示了几种不同的数据。我们以逗号作为分隔符打印了变量、所求之和以及其他内容,甚至还打印了像type()这样的命令所返回的结果。您可能会发现,若执行下列两种命令:

type(varA)
print type(varA)

将得到同样的结果。这是因为我们的执行环境处于解释器中,每个结果都会被自动打印出来。而当我们编写了更为复杂的程序,却在解释器外执行时,便不会自动打印任何内容了,这就要靠自己加入print命令。自此,为了让进度加快,我们就不再使用print命令了。这样,就可以将代码简单地写作:

myVar = "hello friends"
myVar

您一定有看到,大多Python命令(或关键字)如type()、int()、str()等等都是用括号来包围命令内容的。唯一的例外则是print命令,这其实也不算是个“例外”,因为它也可以按这种形式来执行: print("hello")。但是,由于其使用频率过高,因此Python设计者们决定针对它为用户提供一种简化写法。

列表

另一种有趣的数据类型为列表(list),它是一种简单的其他类型的数据集合。与定义字符串使用" "相仿,我们用[ ]来定义列表:

myList = [1,2,3]
type(myList)
myOtherList = ["Bart", "Frank", "Bob"]
myMixedList = ["hello", 345, 34.567]

不难看出,列表可容纳其他类型的数据。列表的用处体现在可以把多个变量“打包”在一起。您可以对列表里面的东东执行任意类型的操作,比如统计其中元素的数量:

len(myOtherList)

或者取出列表中的任意一项:

myName = myOtherList[0]
myFriendsName = myOtherList[1]

可以看到,len()命令返回的是列表中元素的总数,且它们在列表中的“位置”是以0开始计算的。列表中第一项的位置总为0,因此,在myOtherList列表中,"Bob"位居第2。我们可以对列表进行更多的操作,例如对元素进行排序、增减其中的元素,这可以参考这里(已废?)

其实这里有个有趣的事情:文本字符串与字符构成的列表极其相似!试着这样做一下:

myvar = "hello"
len(myvar)
myvar[2]

一般而言,您对字符串所执行的操作也同样可以应用于列表上。事实上,列表与字符串都是顺序结构。

除了字符串、整数、浮点数与列表之外,Python中还有更多其他的内建数据类型,例如dictionaries,或者,您甚至可以通过创建自己的数据类型。

缩进

列表的一种极其酷炫用法是遍历每一项,并对其依次进行处理。例如,来看下面这个示例:

alldaltons = ["Joe", "William", "Jack", "Averell"]
for dalton in alldaltons:
   print dalton + " Dalton"

我们利用"for ... in ..."命令迭代(iterate,编程术语)列表中的每一项,并针对每项执行特定操作。注意到这特殊的语法:for命令以:作为结尾,表示下面为单个或多个命令所构成的执行块。 在解释器中,在输入以:结尾的命令行后,命令提示符将变为...这意味着Python知道(:)行后必有更多的输入内容。

那末,Python是如何知道for...in语句中还有多少行命令要执行呢?这是因为Python采用了缩进策略。即,在for...in语句后,您是不会直接输入下一行内容的。而是要以一个(多个)空格符,或一个(多个)tab符作为开头。其他编程语言则采用其他方式,如将所有内容都置于括号内等等。 只要您按上述同样的缩进编写后续代码,Python就会认为它们都是for-in块的组成部分。如果代码块中第一行以2个空格作为缩进,而下一行以4个空格作为缩进,便会产生一个错误。 如果代码块中的内容编写结束,只需令下一行无缩进,或简单地按下回车键退出for-in块即可。

缩进策略这么吊炸天,究其原因是为了程序的可读性。如果用了较大的索引(比如用tab取代空格,因为tab比空格占用空间更大), 在编写规模很大的程序时,其逻辑看起来就会清晰不少。我们随后会看到,for-in以外的不少命令也可以附有缩进代码块。

for-in命令可用于将某些操作执行多次的情况。例如,它可以搭配range()命令使用:

serie = range(1,11)
total = 0
print "sum"
for number in serie:
   print number
   total = total + number
print "----"
print total

(如果您通过复制、粘贴的方式在解释器中运行上述代码示例,会发现其中的文本块抛出了一个错误。在这种情况下,我们可以从头一直复制到缩进块的结尾,即total = total + number行的结尾,并将其粘贴至解释器中。这时在解释器中(连续)输入<enter>,直到解释器不再显示三个点的符号,并执行上述代码。接着再将最后两行代码复制到解释器中以上换行符<enter>后。现在,解释器应该能显示出程序的输出结果。)

如果您在解释器宏输入help(range),将会看到:

range(...)
    range(stop) -> list of integers
    range(start, stop[, step]) -> list of integers

方括号表示可选参数。不难看出,range()的所有参数都为整数。在以下示例中,我们将通过int()把所有range()的参数都强制转换为整数。

decimales = 1000                     # for 3 decimales 
#decimales = 10000                   # for 4 decimales ...
for i in range(int(0 * decimales),int(180 * decimales),int(0.5 * decimales)):
    print float(i) / decimales

或用range()实现更为复杂的逻辑:

alldaltons = ["Joe", "William", "Jack", "Averell"]
for n in range(4):
   print alldaltons[n], " is Dalton number ", n

可以发现,range()命令也有一些奇怪的特殊性,即它以0开始计数(在没有指定起始编号的情况下),且最后的编号要比您所指定的值小1。当然,这样也可以令它与其他Python命令配合得很顺利。例如:

alldaltons = ["Joe", "William", "Jack", "Averell"]
total = len(alldaltons)
for n in range(total):
   print alldaltons[n]

缩进搭配if命令也是另一种有趣的用法。如果满足了特定条件,则执行对应的if代码块,例如:

alldaltons = ["Joe", "William", "Jack", "Averell"]
if "Joe" in alldaltons:
   print "We found that Dalton!!!"

当然啦,根据上述条件则一定会打印第一条语句,但是现在来以下列代码加以取代:

if "Lucky" in alldaltons:

这样就不会打印任何内容了。我们也可以使用一个else:语句(字符串有问题?(Dalton->Lucky?)):

alldaltons = ["Joe", "William", "Jack", "Averell"]
if "Lucky" in alldaltons:
   print "We found that Dalton!!!"
else:
   print "Such Dalton doesn't exist!"

函数

这里列出了 Python的标准命令。在Python的最新版本中,大约存在30条命令,我们刚刚尝试了其中的一部分。但是,您是否曾想过“发明”自己的命令?答案是:是的,我们可以,而且还相当简单。事实上,向Python添加其他的模块做的就是这件事:这些模块向Python增添了可供您使用的命令。自定义命令在Python中被称为函数,实现方法如下:

def printsqm(myValue):
   print str(myValue)+" square meters"
 
printsqm(45)

(如果直接复制、粘贴则又会出现一次错误,处理方式与上次类似:第一次仅需复制到缩进部分的结尾,也就是将开头至" square meters"复制到解释器中,(连续)按回车符<enter>,直到不再显示三点符号,这时再复制、粘贴剩余部分,也就是此程序中的最后一行。)

看吧,很简单:利用def()命令即可定义一个新的函数。您可以为它起名,在括号里定义其参数,在函数中处理这些参数。参数即将传入函数中的数据。例如,现在来考察len()命令。如果仅写len(),则Python将提示您需要为之提供一个参数。即,您需要用len()来做些什么,对吧?比如说,您可以用len(myList)来获取myList的长度。此时,myList便是您传入len()函数的参数。len()函数的定义便是如此,使之知道如何处理传入的参数。而我们刚刚做的也正是如此。

"myValue"可以为任意有效名称,仅用于定义它的目标函数之中。它只是您为参数起的名字,同时还负责告知函数所需的参数数量。例如,您可以试着这样写:

printsqm(45,34)

这将导致一个错误。原因是此函数实际仅接收一个参数,但是却为它输入了两个参数:45与34。我们可以编写下列函数:

def sum(val1,val2):
   total = val1 + val2
   return total

sum(45,34)
myTotal = sum(45,34)

在这段代码中,我们令函数接收两个参数,求其和,并返回其值。函数返回的数据很有用,因为我们可以继续对其结果进行处理,例如,这里将函数返回的结果存于myTotal变量之中。当然,由于我们身处解释器之中,所有内容都可打印出来,于是就可以这样做:

sum(45,34)

这将在屏幕中打印求和结果,但是若没有处于解释器中,由于函数里没有任何打印语句,所以在屏幕中不会看到任何内容。我们就需要这么做:

print sum(45,34)

借此来打印所需内容。关于python函数的更多信息,可参见于

模块

现在我们已对Python的工作方式的些许认识,这里来讨论最后一样东西:如何运用文件及模块。

到目前为止,我们一直在解释器中逐行地编写Python指令。我们是否可以将多行代码集合在一起,并一次性执行完毕呢?这可是编写更复杂逻辑的基础啊。而且,我们还可以借此保存自己的工作进度。是的,方法当然有,而且还相当简单。只需打开文本编辑器(如windows的记事本,Linux下的gedit、emacs或vi),并像在解释器里那样,在其中借助缩进等编写Python代码。再将数据存作以.py为拓展名的文件。这样,您就可以一次性编写完整的Python程序了。当然,还有比notepad更优秀的编辑器,它会以Python程序模式助你编写代码,而不仅仅是将其看作一个文本文件。

有n种方式可以使Python执行上述程序。在windows中,只需对文件按下右键,以Python打开,再执行即可。此外,您可以用Python解释器来执行代码文件。对此,解释器可以识别出.py程序。在FreeCAD中,最简单的方式是将文件放置在FreeCAD中Python解释器可识别的默认位置处,例如FreeCAD的bin文件夹,或任意Mod文件夹下。(在Linux中,所用的文件夹可能为/home/<username>/.FreeCAD/Mod,我们可在此添加一个名为scripts的子文件来存放代码文本文件。) 假设我们在文件中写入下列代码:

def sum(a,b):
    return a + b

print "myTest.py succesfully loaded"

并将其保存为FreeCAD/bin(或Linux中/home/<username>/.FreeCAD/Mod/scripts)下的myTest.py文件。此时,我们开启FreeCAD,并在解释器窗口中输入:

import myTest

请注意,并无.py扩展名。这将像在解释器中所做的那样,逐行执行文件中的代码。执行过程中将创建sum函数,并打印代码中的对应信息。 这里有一个较大的不同点:import命令不仅执行文件中的程序,还会加载其中的函数,因此就可以在解释器中使用这些函数。而包含函数的文件则被称作模块。

如果在解释器中编写sum()函数,可以这样方便地执行:

sum(14,45)

这就是我们此前的做法。当我们导入包含sum()函数的模块时,语法则稍有不同:

myTest.sum(14,45)

即,可将导入的模块看作是个“容器”,其中所存均为其函数。这个设计极其有用,因为我们可以导入大量模块,并令所有的代码都有一定的组织结构。因此,基本上来讲,到处您都可看到 something.somethingElse的形式,以点作为隔断,表示somethingElse位于something中。

我们可以在主解释器空间中导入自己编写的sum()函数,就像这样:

from myTest import *
sum(12,54)

基本上,所有模块的形式都是如此。导入一个模块,使用其中的函数:module.function(argument)。大多数的模块是这样编写的:由于您可以在自己的模块中导入其他模块,因此可以在模块中定义您要在解释器或自己的Python模块中使用的函数、新数据类型与类。

这里要提及最后一样极有用的东东。我们如何得知有哪些模块可用?模块里有哪些函数?而这些函数如何使用(即,这些函数使用哪些参数)?我们此前曾使用过Python的help()函数。现在输入:

help()
modules

这将为我们列举出所有可用的模块。随后,我们可以按q来退出当前的交互帮助系统,并按需导入这些模块。甚至可以用dir()命令来浏览模块中的内容

import math
dir(math)

此时,我们将看到math模块中的所有函数,以及一些陌生的名字:__doc__, __file__, __name__。其中,__doc__用处很大,它是描述性的文档文本。(质量高的)模块中的每个函数都有对应的__doc__,用来解释它们的用法。例如,我们可以从math模块中看到有个sin函数。想知道怎样使用吗?

print math.sin.__doc__

(可能不太明显,但是doc左右两端各有两条下划线。)

最后再来点儿饭后甜点吧:当使用一个新的模块或已存在的模块时,最好以py作为文件扩展名,例如:myModule.FCMacro => myModule.py。我们通常会希望测试这些模块,遂以上述方式来加载它。

import myModule
myModule.myTestFunction()

但是,您是否发现myTestFunction()其实无法正常工作。我们回到编辑器,并作出适当更改。即,以python解释器关闭并重新打开上述模块,可以略作如下修改:

reload(myModule)

这时我们会发现:需要对此文件进行重新命名,这是因为Python并不能识别出FCMacro这种扩展名。

然而,这里还有两种方式来打开其他扩展名的模块:在宏中使用Python的exec或execfile函数。

f = open("myModule","r")
d = f.read()
exec d

或者

execfile "myModule"

对于Python 3.xxx版而言,以下列代码替代:

exec(open("C:/PathToMyMacro/myMacro.FCMacro").read())

为了跨多个宏共享代码,您可以访问FreeCAD或FreeCADGui模块(或任意其他Python模块)并为之设置属性。这将保证宏的执行。

import FreeCAD
if hasattr(FreeCAD,"macro2_executed"):
    ...
else:
    FreeCAD.macro2_executed = True # you can assign any value because we only check for the existence of the attribute
    ... execute macro2

在FreeCAD中使用Python

好的,我认为您现在已经了解了Python工作的基本原理,并可以开始查看FreeCAD为您提供了哪些相应设施。FreeCAD的Python函数都很妙地组织在不同的模块中。其中的一部分已经在FreeCAD开启时得到加载(导入)。因此,只需执行

dir()

并阅读FreeCAD脚本基础

当然,我们刚刚仅游览了Python世界中的极小范围。因此还有许多关键的概念并没有提及。在网上有3种非常重要的Python参考文献:

请确保将它们加入您的书签!