跟着官网学Python(8):输入输出

“Python输入、输出、文件读写以及异常知识。”

01 面临问题

继续跟着官网学Python,第7章输入输出。
前面已经基本学完Python的语法部分,也学会如何使用轮子,但是编程语言不能只在虚拟世界玩,需要与现实世界进行交互。
需要接受输入,理解用户指令,需要美好输出,让用户更好理解。
前面学习字符串的时候使用了print,也强调了格式化输出。
那么本章在更全面的分享一下Python的输入与输出。

02 怎么办

有多种方法可以显示程序的输出,数据可以以人类可读的形式打印出来,或者写入文件以供将来使用。

更漂亮的输出的格式

前面遇到两种输出值的方法:表达式语句和print()。比如输入1+1,会直接输出2。
很多时候,我们需要控制输出的格式,比如小数点后位数、首字母是否大写、对齐、日期格式等等,这里列出一些常见的格式化方法:

  • 使用格式化字符串字面值,在字符串引号前加fF,然后使用{变量或者表达式}

这个真第一次用

>>> f'1+1 = {1+1}'
'1+1 = 2'
>>> yes_votes = 42_572_654
>>> no_votes = 43_132_495
>>> percentage = yes_votes / (yes_votes + no_votes)
>>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
' 42572654 YES votes  49.67%'

原来数字赋值还可以用下划线分割,增加可读性。

  • 最后,可以使用字符串切片和连接操作完成所有的字符串处理,创建可以想象的任何布局。比如符号标签、图形文字等等。字符串类型有一些方法可以将字符串填充到给定列宽的有用操作。

如果你不需要这些花哨的输出,只是想快速显示某些变量进行调式时,可以使用repr()str()将任何函数转为字符串,第一个没用过。
str()函数返回返回人类可读的值,repr()是用于生成程序可读的值(可能会出现语法错误SyntaxError),如果没有人类可读性表示的对象,他们返回一样的值。

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"   #注意看,单引号还在

顺便问一句,什么是没有人类可读性对象?
还记得string模块吗,他有一个Template类,人如其名,模板类,也可以将值替换为字符串,使用占位符$x,用字典中的值替换他们,但是对格式控制不多。

格式化字符串文字
格式化字符串字面值(简称f-字符串),能让你在字符串前加上 f 和 F 并将表达式写成 {expression} 来在字符串中包含 Python 表达式的值。
说是3.6才新增的功能,更新详细可以查阅Python语言参考。
可选的格式说明符可以跟在表达式后面。这样可以更好地控制值的格式化方式。以下示例将pi舍入到小数点后三位:

>>> import math
>>> print(f'The value of pi is approximately {math.pi:.3f}.')
The value of pi is approximately 3.142.

可以在:后跟一个整数实现快速对齐

>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
...     print(f'{name:10} ==> {phone:10d}')
...
Sjoerd     ==>       4127
Jack       ==>       4098
Dcab       ==>       7678

还支持其他修饰符,可以在格式化前转换值,如'!a' 应用 ascii() ,'!s' 应用 str(),还有 '!r' 应用 repr():
再看下示例

>>> animals = 'eels'
>>> print(f'My hovercraft is full of {animals}.')
My hovercraft is full of eels.
>>> print(f'My hovercraft is full of {animals!r}.')
My hovercraft is full of 'eels'.

注意到!r对应repr(),正如前面所说,保留了字符串的单引号。
字符串的format()方法
再次看一下str.format()方法的基本用法:

>>> print('I like {}!'.format('Python'))
I like Python!

花括号和其中的字符(称为格式字段)将替换为传递给 str.format() 方法的对象。
花括号中的数字可用来表示传递给 str.format() 方法的对象的位置,还可以使用关键字参数(**变量可以作为关键字参数传递),以及两者的组合。

>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',other='Georg'))
The story of Bill, Manfred, and Georg.

比如下面几行代码生成一组整齐的列,其中包含给定的整数和它的平方以及立方:

>>> for x in range(1, 6):
...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125

手动格式化字符串
同样平方和立方的表,手动格式化需要自己控制格式:

>>> for x in range(1, 11):
...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')
...     # Note use of 'end' on previous line
...     print(repr(x*x*x).rjust(4))
...
 1   1    1
 2   4    8
 3   9   27
 4  16   64
 5  25  125

字符串对象的str.rjust()方法通过在左侧填充空格来对给定宽度的字段中的字符串进行右对齐。类似str.ljust() 和 str.center() ,如果字符串太长,不会被截断,如果想截断需要人为切片。
旧的字符串格式化方法%
% 运算符也可用于字符串格式化

>>> import math
>>> print('The value of pi is approximately %.3f.' % math.pi)
The value of pi is approximately 3.142.

读写文件

前面这么久,我们都没涉及文件的读写,其实文件读写又是一门语言的必备要素。
python内置函数open()返回一个file object,最常用的两个参数open(filename,mode),如
>>> f = open('workfile', 'w')
第一个参数filename是包含文件名的字符串,可以是相对路径或绝对路径;
第二个参数mode是指打开文件的方式,可选参数,默认为r,参数含义如下:

  • 'r'表示文件只能读取
  • 'w' 表示只能写入(已存在的同名文件会被删除)
  • 'a' 表示打开文件以追加内容;任何写入的数据会自动添加到文件的末尾。
  • 'r+' 表示打开文件进行读写
  • 't' 默认值,以 text mode(文本方式)打开,以指定的编码方式进行编码或平台默认的编码,返回str对象
  • 'b' 以 binary mode(二进制模式) 打开文件,不进行任何解码,返回bytes对象

注意在文本模式下读写\n会转换为平台换行符,直接修改可能会破坏二进制格式的数据,要小心。
文件处理完毕后,应该调用f.close()关闭文件并立即释放它使用的所有系统资源。虽然就算你忘记关闭,Python的垃圾回收器最终将销毁该对象并为你关闭打开的文件,但是这就不知道什么时候的事了,总之不好。
更简单是使用with关键字,后面子句执行完毕会自动关闭:

>>> with open('workfile') as f:
...     read_data = f.read()
>>> f.closed
True

通过 with 语句或者调用 f.close() 关闭文件对象后,尝试使用该文件对象将自动失败。
文件对象file object的方法
f.read(size)可以读取文件内容,在文本模式下返回size个字符,在二进制模式下返回size个字节。
size可选,为空或负数时返回整个文件内容,如果已到达文件末尾,f.read() 将返回一个空字符串''

>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

比如第一个read()已经读取全部内容了,第二个read()已经到尾部,只能返回''
f.readline() 从文件中读取一行,换行符\n留在字符串的末尾,注意如果最后一行没有\n,则无法读取。
还可以直接用in关键字变量文件对象f,这是内存高效,快速的,并简化的代码

>>> for line in f:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

如果想以列表形式读取文件中的所有行,可以使用 list(f) 或 f.readlines()
f.write(string) 会把 string 的内容写入到文件中,并返回写入的字符数。

>>> f.write('This is a test\n')
15

在写入其他对象时,需要先转换为字符串str(文本模式)或字节对象bytes(二进制模式)。
同样的f.writelines()可以接受列表形式参数,一次性写入多行。
f.tell()返回一个整数,给出文件对象在文件中的当前位置,二进制模式下从文件开始的字节数,文本模式下意义不明的数字。

f.seek(offset, whence)可以改变文件对象的位置,通过向一个参考点(whence)添加offset来计算文件对象位置,其中whence 的 0 值表示从文件开头起算,1 表示使用当前文件位置,2表示使用文件末尾作为参考点,可选参数,默认为0。

>>> f = open('workfile', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)      #去到文件的第6个字节
5
>>> f.read(1)      #读取一个字节内容,果然为b'5'
b'5'
>>> f.seek(-3, 2)  #去到倒数第3个字节
13
>>> f.read(1)
b'd'

在文本模式下,只允许相对于文件开头搜索(使用 seek(0, 2)搜索到文件末尾是个例外)并且唯一有效的 offset 值是那些能从 f.tell() 中返回的或者是。其他 offset 值都会产生未定义的行为
文件对象还要一些不常用的方法,如isatty() 和 truncate()等,可以查询Python库参考。
使用JSON保存结构化数据
字符串可以很轻松地写入文件并从文件中读取出来。
数字可能会费点劲,因为 read() 方法只能返回字符串,然后用int()函数处理。
当你想保存诸如嵌套列表和字典这样更复杂的数据类型时,手动解析和序列化会变得复杂。
Python可以使用JSON而不是让用户不断的编写和调试代码以将复杂的数据类型保存到文件中。
有一个标准模块json实现Python数据结构到字符串的转换,称为序列化(serializing)。同样从字符串中重建数据称为反序列化(deserializing)
在序列化和反序列化之间,表示对象的字符串可能已存储在文件或数据中,或通过网络连接发送到某个远程机器

>>> import json
>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'

json.dumpsjson.loads实现对Python数据结构的编码和解码,就是与字符串之间的变换。
还有变体组合dump()load(),更多需要参考标准库。
Python还有一个特有的pickle封存模块,允许对任意复杂 Python 对象进行序列化的协议,但是是不安全的,如果数据是由熟练的攻击者精心设计的,则反序列化来自不受信任来源的 pickle 数据可以执行任意代码。也就是CTF中渗透攻击,其实反序列化是CTF中一个常见题型,只是我不会。


内容好像不够多,继续学习第8章错误和异常。
虽然一直跟着官网走,但是相信你的代码也遇到一些Python的语法错误或异常

语法错误

语法错误又称解析错误,可能是你在学习Python 时最容易遇到的错误:

>>> while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
                   ^
SyntaxError: invalid syntax

只要遇到SyntaxError: invalid syntax,那么就是代码写错了,检查中英文,冒号、缩进、括号闭合等。
会显示出现语法错误文件名和行号,并显示一个“箭头”,请检查前后行,很多时候是前面没有冒号或者括号没有正常闭合等。

异常

即使语句或表达式在语法上是正确的,但在尝试执行时,它仍可能会引发错误。
在执行时检测到的错误被称为异常,异常不一定会导致严重后果,比如除零异常:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

错误信息显示异常类型和详细信息,比如NameError: name 'spam' is not defined,变量spam没有定义,同时错误信息还以堆栈回溯的形式显示发生异常时的上下文调用关系,非常直观。
除了系统内置异常,用户可以自定义异常类型。

异常处理

写一个简单的代码,实现如下功能:
要求用户一直输入,直到输入的是一个有效的整数。

>>> while True:
...     try:
...         x = int(input("Please enter a number: "))
...         break
...     except ValueError:
...         print("Oops!  That was no valid number.  Try again...")

try except语句可以用来处理异常,当你认为某段代码可能发生异常时,可以用try except包围它,并在except子句实现异常逻辑。
try语句的工作原理如下:

  • 首先,执行 try 子句(try 和 except 关键字之间的(多行)语句,需要缩进)。

  • 如果没有异常发生,则跳过except子句 并完成 try 语句的执行。

  • 如果在执行try子句时发生了异常,则跳过该子句中剩下的部分。然后,如果异常的类型和 except 关键字后面的异常匹配,则执行 except 子句,然后继续执行后续语句,代码不会中断。

  • 如果发生的异常和 except 子句中指定的异常不匹配,则将其传递到外部的 try 语句中;如果没有找到处理程序,则它是一个未处理异常,执行将停止并显示如上所示的消息。

所以很多时候我不except具体异常,所以异常都处理。但是官网不建议这样做,很容易掩盖真正的编程错误
原生的try语句支持多个except子句以指定不同异常的处理程序,最多会执行一个处理程序,类似只执行一次if elif。同时一个except子句可以将多个异常命名为带括号的元组,如:

... except (RuntimeError, TypeError, NameError):
...     pass

如果发生的异常和 except 子句中的类是同一个类或者是它的基类,则异常和 except 子句中的类是兼容的,但反过来则不成立
try ... except 语句有一个可选的 else 子句,在使用时必须放在所有的 except 子句后面。
对于在try子句不引发异常时必须执行的代码来说很有用

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

else子句避免了意外捕获由try ... except 语句保护的代码未引发的异常,比如上面的f.close(),因为你不确定前面的文件是否被打开,只有放在else语句。

发生异常时,它可能具有关联值,也称为异常参数 。参数的存在和类型取决于异常类型。
except子句可以在异常名称后面指定一个变量。这个变量和一个异常实例绑定,它的参数存储在 instance.args 中。
为了方便起见,异常实例定义了 __str__() ,因此可以直接打印参数而无需引用 .args 。
也可以在抛出之前首先实例化异常,并根据需要向其添加任何属性

>>> try:
...     raise Exception('spam', 'eggs')
... except Exception as inst:
...     print(type(inst))    #异常实例
...     print(inst.args)     #.args返回参数类别
...     print(inst)          # __str__ 可以直接打印
...     x, y = inst.args     # 参数解包
...     print('x =', x)
...     print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

如果异常有参数,则它们将作为未处理异常的消息的最后一部分('详细信息')打印。
同时异常处理程序不仅处理 try 子句中遇到的异常,还处理 try 子句中调用(即使是间接地)的函数内部发生的异常。
抛出异常
raise 语句允许程序员强制发生指定的异常,如

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: HiThere

raise唯一的参数就是要抛出的异常。
这个参数必须是一个异常实例或者是一个异常类(派生自Exception的类)。
如果传递的是一个异常类,它将通过调用没有参数的构造函数来隐式实例化.
如果你需要确定是否引发了异常但不打算处理它,则可以使用更简单的 raise 语句形式重新引发异常

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: HiThere

用户自定义的异常

程序可以通过创建新的异常类来命名它们自己的异常(有关Python 类的知识,我们下周分享)。
异常通常应该直接或间接地从Exception类派生。

class Error(Exception):
    pass

class InputError(Error):
    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

大多数异常都定义为名称以“Error”结尾,类似于标准异常的命名。

定义清理操作

try语句有另一个可选子句,用于定义必须在所有情况下执行的清理操作,如:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>

finally 子句将作为 try 语句结束前的最后一项任务被执行。 finally 子句不论 try 语句是否产生了异常都会被执行。

  • 如果在执行 try 子句期间发生了异常,该异常可由一个 except 子句进行处理。 如果异常没有被某个 except 子句所处理,则该异常会在 finally 子句执行之后被重新引发

  • 异常也可能在 except 或 else 子句执行期间发生。该异常会在 finally 子句执行之后被重新引发

  • 如果在执行 try 语句时遇到一个 breakcontinue 或 return 语句,则 finally 子句将在执行 breakcontinue 或 return 语句之前被执行

  • 如果 finally 子句中包含一个 return 语句,则返回值将来自 finally 子句的某个 return语句的返回值,而非来自 try 子句的 return 语句的返回值。如:

>>> def bool_return():
...     try:
...         return True
...     finally:
...         return False
...
>>> bool_return()
False

官网提供了一个更复杂的例子如下:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

可以发现finally 子句在任何情况下都会被执行。 两个字符串相除所引发的 TypeError 不会由 except 子句处理,因此会在 finally 子句执行后被重新引发
在实际应用程序中,finally 子句对于释放外部资源(例如文件或者网络连接)非常有用,无论是否成功使用资源。

预定义的清理操作
某些对象定义了在不再需要该对象时要执行的标准清理操作,无论使用该对象的操作是成功还是失败。比如前面我们学过文件读入,

for line in open("myfile.txt"):
    print(line, end="")

代码执行后会使文件在一段不确定的时间内处于打开状态,对于较大的应用程序来说可能是个问题。
正如前面所使用的,with语句允许像文件这样的对象能够以一种确保它们得到及时和正确的清理的方式使用

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

执行完语句后,即使在处理行时遇到问题,文件f也始终会被关闭。
和文件一样,提供预定义清理操作的对象将在其文档中指出这一点,请学会使用。

03 为什么

为什么要这么做?

首先任何东西的官方文档都是最全面最权威的教程。
以前只是受限于英语水平,
对官方网站敬而远之,
遇到问题都百度,
很多答案讲的都不到位,
没有说明为什么?
越到后面,收获越大。
输入输出用的比较多,文件读写也是,以前不喜欢用wiht,以后要改。
第一次知道f-字符串用法,很方便,第一次接触repr()函数,需要了解。
对读写文件的理解更加深刻,学会了一些以前没用过的方法,如seek()等。
又比如虽然经常try except,但是从未深究,也没用过else字句和finally子句。
收获满满

04 更好的选择

有没有更好的选择

还是那句话,多敲代码,结合案例,偶然看看官方源代码,加深理解。
比如我们维护大型项目是需要用到配置文件,如何以json形式保存、读取配置文件呢?看一个简单的示例:

import json
with open("setting.json", "r", encoding="utf-8") as file:
    settings = json.load(file)

然后可以进行处理,也可以封装为函数。

一句话

格式化输出想要的信息,str()reprstr.format()f-string灵活运用;读写文件先打开再关闭,with语句很好用,open() 模式要分清,文本模式与二进制模式,是否可写,read()write()要会用。
代码语法错误要避免,异常要处理,try ... except ... else ... finally语句要会用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值