BOO大全/输入与输出
输入与输出
编辑读取终端机文字
编辑Boo 已经内建了用来处理输入的函数,名字是prompt。它会提示使用者并等待输入,只适用于终端机应用程序:
>>> prompt('enter some text:') enter some text:here we go again 'here we go again'
也可以改使用 .NET 内建的 Console:
>>> import System >>> Console.In.ReadLine() here we go again 'here we go again'
我们也可以读取输入,作处理之后,再进行输出。下面就是一个用来将输入的字串转换为大写的代码:
# uppercase.boo
text = System.Console.In.ReadToEnd()
print text.ToUpper()
在编译之后,就可以像其他指令一样使用。使用方法有两种,第一种方法,你可以将输入档案指定为程式的标准输入,然后将程式的标准输出导向到一个输出档案。第二种方法,可以使用 pipe ( 就是 | ),将 sort 的输出导向到 uppercase,再输出到 myfile-upper-sorted.txt。
uppercase < myfile.txt > myfile-upper.txt sort myfile.txt | uppercase > myfile-upper-sorted.txt
当然也可以一次读取一行,下面程式就是在读取每行之后,再加上行号并输出:
import System
k = 0
while (line = Console.In.ReadLine()) != null:
print ++k,line
我觉得这程式有点笨拙,感觉上就是直接以 Boo 改写 C# 程式。所以下面改用 for 循环取代复杂的 while 循环,来迭代文字流里的每一行,这是一个很棒的特性,不是吗?(注意,Boo 的变数指派是一个运算式,而非述句,会有值传出。译注,C/C++/C# 也很常看到同样的用法。)
import System
k = 0
for line in Console.In:
print ++k,line
这是只有两行的版本:
for k,line in enumerate(System.Console.In):
print k,line
内建的 enumerate 函式就是为了这种情况产生的,它被设计来迭代集合并保有索引值。
最后再介绍一个把文字档里的每行放到阵列里的方法,只有一行:
lines = array(string,line for line in System.Console.In)
寻找档案
编辑.NET 在处理档案与目录上,提供了丰富的函式。这些函式都在 System.IO 命名空间里。举例来说,Directory.GetFiles 回传一个包含某目录下所有档案的阵列。这里, "." 通常用来表示当前的目录,这儿并使用 GetFullPath 取得完整路径。
>>> import System.IO >>> files = Directory.GetFiles(".","*.boo") >>> files ('.\\ast.boo', '.\\AttemptMacro.boo', '.\\PlotMacro.boo', '.\\SampleMacro.boo', '.\\TestAttemptMacro.boo', '.\\TestPlotMacro.boo', '.\\TestSampleMacro.boo') >>> Path.GetFullPath(files[0]) 'C:\\net\\sciboo\\examples\\macros\\ast.boo'
读取档案
编辑常常我们会想要存取任意的档案﹔读取的方法都一样。与前面用过的 System.Console.In 的不同点,在于输入文字流必须要先打开,使用完则必须关闭。
>>> import System.IO >>> fin = File.OpenText("tmp.txt") System.IO.StreamReader >>> fin.ReadLine() 'Here are ' >>> fin.ReadLine() 'a few lines' >>> fin.Close()
这边把前面加上行号的例子再改写,让它可以依据程式参数来读取指定档案,并且加上一些基本的必要检查:
import System.IO
if len(argv) == 0:
print "usage: <text file>"
return
if not File.Exists(argv[0]):
print "file does not exist: ",argv[0]
return
fin = File.OpenText(argv[0])
k = 0
for line in fin:
print ++k,line
fin.Close()
(我知道在一个程式里使用 return 来离开显得很奇怪,但别忘了,其实这里面有个隐性的 main 函式。)
我建议你改用 using 述句。不只是因为它看起来比较简洁 (把读取的动作放到程式区块里,看来很清楚),而是因为不管发生什么事,它保证你的档案一定会关闭。所以最后五行可以再改写为:
using fin = File.OpenText(argv[0]):
k = 0
for line in fin:
print ++k,line
读取数值
编辑当我开始学 C# 时,我很惊讶,C# 居然没有一个简单的方法用以读取数值。(也许对一个新语言来说,这太过时了,显然,Java 也有此问题。) 你必须要分开输入,并将字串转为数值。这里有个程式,它从档案里读取所有数值,并计算其平均值:
import System
import System.IO
sum = 0.0
k = 0
for line in Console.In:
words = line.Split(char(' '),char('\t'))
for w in words:
if len(w) > 0:
try:
x = double.Parse(w)
sum += x
++k
except:
print w,"was not a number"
return
print "numbers " + k + " average was " + sum/k
这不是个漂亮的程式,字串的处理与累加被绑在一起了。
这里使用 Generator 方法(使用 yield),将字串处理与累加的逻辑分开,
import System
import System.IO
def Numbers():
for line in Console.In:
words = line.Split(char(' '),char('\t'))
for w in words:
if len(w) > 0:
yield double.Parse(w)
sum = 0.0
k = 0
try:
for x in Numbers():
sum += x
++k
except e as Exception:
print e.Message
return
print "numbers " + k + " average was " + sum/k
如果我们在 double.Parse 失败时,假设结果为 0 的话,可以让程式更简单些。前面我们在 Parse 发生错误时提出例外,虽然这看来很愚蠢,不过这却是某些 script 语言,如 AWK,所作的事情。这边我们再加上 SafeParse 来专注于解析数值的任务。(当然我们可以假设档案的格式都是正确的,不做处理,但难保不会遇到不正常的状况,你说是吗?)
def SafeParse(w as string):
try:
return double.Parse(w)
except:
return 0.0
def Numbers():
for line in Console.In:
words = line.Split(char(' '),char('\t'))
for w in words:
if len(w) > 0:
yield SafeParse(w)
主要的循环现在看来更简单了。
sum = 0.0
k = 0
for x in Numbers():
sum += x
++k
print "numbers " + k + " average was " + sum/k
下一节会提到将字串与数值分开的方法,请参考读取字串。
(如果想再把主要循环作成函式的话,可以参考其他的作法)。
读取字串
编辑TextReader 类别并不具有读取有分隔符号文字的能力,它只能读取行。你可以逐字元读取,然后自行收集。Read 回传一个整数,可以转为字元。在遇到档案结尾时,则传回 -1(-1 不是个合法的字元)。所以我们可以一次读取一个字元,并将其附加到 StringBuilder 物件里,直到发现非空白字元或是遇到 -1 时,再把结果传回。使用StringBuilder来组字串的原因是因为它比较有效率。
import System.Text
import System.IO
def ReadString(tr as TextReader):
ch = tr.Read()
while ch != - 1 and char.IsWhiteSpace(cast(char,ch)):
ch = tr.Read()
# 結束了,傳回 null
if ch == -1:
return null
# 遇到非空白字元!
sb = StringBuilder()
while ch != -1 and not char.IsWhiteSpace(cast(char,ch)):
sb.Append(cast(char,ch))
ch = tr.Read()
return sb.ToString()
s = """
1 2.3 hello
2 5.6 dolly
"""
sr = StringReader(s)
s = ReadString(sr)
while s:
print s
s = ReadString(sr)
print "that's all, folks!"
如果要处理整数或浮点数的话,可以这样用:
def ReadInt(tr as TextReader):
return int.Parse(ReadString(tr))
def ReadDouble(tr as TextReader):
return double.Parse(ReadString(tr))
其他的作法
编辑这儿有个方法可以用来计算序列的加总,并回传元素总数与加总值。这儿宣告了 duck 型别的序列,然后迭代这个序列并加总,所以这个函式可以处理任何序列,序列里的所有元素都将被视作 double 型别再加总。
def sum(seq as duck):
y = 0.0
k = 0
for x as duck in seq:
y += x
++k
return k,y
import Tokens
print "array sum is ",sum((2.3,4.2,5.5))[1]
print "integers ",sum((10,20,30,40,50))[1]
y = sum(Tokenizer("numbers.txt").Numbers())
print "num ",y[0],"sum is ",y[1]
我们可以做的更好。这个版本可以支援所有型别,只要型别支援 + 运算子:
def sum(seq as duck):
y as duck
k = 0
for x as duck in seq:
if k == 0:
y = x
else:
y += x
++k
return k,y