文件

 变量是把数据保存到内存中,如果程序重启/主机重启,内存中的数据就会丢失,要想能让数据被持久化储存,就可以把数据储存到硬盘中,也就是在文件中保存。

在ws中的目录名之间使用  \   来分割   但是使用   /   来分给也是可以的,但是我们一般用  /  ,因为 \  在编译语言的在字符串中有特定的含义,用来表示转义字符。如果我们想用反斜杠,那么就要用    \\   , \\    在字符串里面才表示字符   \   的含义。

变量就是在内存中,文件就是在外存中,我们要用python实现对文件操作。

文件操作

 打开文件

 在Python中我们使用   open  函数打开一个文件:

open('d:/Python环境/test.txt','r')

open函数中的第一个参数是我们要打开文件的绝对路径,第二个参数是我们打开这个文件的方式,‘r’ 表示read,按照读的方式打开;而open函数的返回值是一个文件对象,用来表示文件:

f = open('d:/Python环境/test.txt','r')
print(f)
print(type(f))

 输出:

<_io.TextIOWrapper name='d:/Python环境/test.txt' mode='r' encoding='cp936'>
<class '_io.TextIOWrapper'>

 这个 文件对象  --  class '_io.TextIOWrapper'   是python专门给这个类型取了个名字。

文件对象--  文件的内容是在硬盘上的,而我们的文件对象,则是内存上的一个变量,我们写的python程序是在内存上运行的,后序读写文件操作,要在内存中实现对硬盘中文件的修改,都是拿着这个文件对象来进行操作。此处的文件对象,就像一个遥控器一样,我们直接操作硬盘中的内存不好操作,我们就在内存中操作这个‘遥控器“,通过这个文件对象间接操作这个硬盘。

在计算机中,也把这样的远程操作的“遥控器:称为 ” 句柄 “ (handler)

当文件不存在的时候,我们再用open打开的话就会抛出异常:

 下面是关于  文件打开方式:

 关闭文件

 我们使用 close 方法来对文件进行删除:

f = open('d:/Python环境/test.txt','r')
print(f)
print(type(f))

f.close()

我们的文件打开 (open函数) 和文件关闭 (close 方法)是一对离不开的孪生兄弟,文件打开之后,是用完之后,必须把文件关闭。这时因为,打开文件其实是在申请一定的系统资源,当我们不在使用这个文件的时候,这个资源就该及时释放,如果不释放,以后其他人在打开文件就用不了。而且资源是有限的,我们打开文件的个数也是有限的。

flist = []
count = 0
while True:
     f = open('d:/Python环境/test.txt','r')
     flist.append(f)
     count += 1
     print(f'打开文件的个数:{count}')

 我们发现在打开一定数量的文件后,文件打开操作就失败了。(文件资源泄漏)

我们发现我们最终打开了8189 个文件个数,但是这不是我们这个程序所能打开的最大的文件个数,对于一个程序可以打开多少文件,我们在系统里面可以配置,但是无论配置多少,都不是无穷无尽的,因此我们要记得及时关闭文件。

我们的这个 8189 + 3 = 8192 => 2的13次方,计算机是使用二进制来表示数据的,因此计算机里的很多数据都是按照2的多少次方这样的方式来表示的。那么我们上述的这个  “3”  是怎么来的 呢?

我们每一个程序来启动的时候,都会默认打开三个文件:

  • 标准输入   键盘        input()  通过标准输入来读数据
  • 标准输出   显示器     print()  往标准输出文件里写数据
  • 标准错误   显示器

 这三个文件很特殊,这三个文件不是在我们的磁盘里,而是在我们的键盘,显示器当中的。

 文件资源泄漏,是很重要的问题,因为这个问题不会第一时间报错出来,我们在比较大的程序中,这问题很难发现,当程序报错的时候,不是在我们写的那一时间报错,可能会在后面的时候发生文件资源泄漏,这时候我们不好再去找这个问题了。

我们上述的代码中,还有一个地方,就是我打开文件之后,我是把这个open返回的文件对象用一个 flist 列表来存储,当我们不用这个列表来存储的时候,我们打开的文件就不止  8189 个了:

flist = []
count = 0
while True:
     f = open('d:/Python环境/test.txt','r')
     # flist.append(f)
     count += 1
     print(f'打开文件的个数:{count}')

 我们发现此时我们打开了1200000 + 以上都没有停止,这是为什么呢?

这是因为在Python中有一个重要的机制,叫做垃圾回收机制(GC):可以自动的把不使用的变量给进行释放~~

例如上面这个代码,我们在每一次循环 用 open打开函数创建了一个  f  的文件对象的时候,因为我们没有对他进行使用,所以Python认为他是一个垃圾,所以就把这个文件对象个释放掉了,也就是python自动的帮我们调用了close方法了。

当我们把这个文件对象保存到我们 flist 这个列表当中的时候,我们的垃圾回收就不知道我们后序会不会再次使用这个文件对象,就不会帮我们自动删除了。

但是这个垃圾回收机制还是不是很及时的反应释放,所以我们不能依赖这个机制,每一次使用完文件之后都要关闭文件。

写文件

 用  write  方法来写文件

 只写方式打开来用write 方法写:

f = open('d:/Python环境/test.txt','w')
f.write('hello')
f.close()

我们打开文件发现:

 ’hello‘字符串已经被我们写入了。

 需要注意的是,我们在open打开的时候是用  w  的方式打开的,如果我们在写文件的时候用 r 的方式打开的,则会抛出异常:

 报错:

 报错说:这是一个不支持的操作(UnsupportedOperation)不可写的(not table)

像 w 方法打开的话,会先把文件里面的内容给清空掉然后再执行其他的操作,如果我们用 w 方式open打开文件什么都不执行然后就 close 关闭文件的话,会出现下面的情况:

f = open('d:/Python环境/test.txt','w')
f.close()

 我们发现之前我们在曾 test.txt 文件中写入的 ’hello’字符串被清空了。

所以我们写文件还有第二种方式:

用 a 方式在文件中追加内容:

#用 w 的方式写文件
f = open('d:/Python环境/test.txt','w')
f.write('1111')
f.close()

#用 a 的方式写文件
f = open('d:/Python环境/test.txt','a')
f.write('2222')
f.close()

 我们发现此时在test.txt 中在 1111  的基础上追加了 2222 这个字符串。

我们发现我们之前在每一次打开open打开之后,都去 把这个文件关闭(close)了,如果文件对象已经被关闭,那么意味着系统中的和该文件相关的内存资源已经释放了,如果我们在文件关闭的情况下去强行去写的话就会出现异常:

#用 w 的方式写文件
f = open('d:/Python环境/test.txt','w')
f.write('1111')
f.close()

f.write('2222')

 编译器会提示你 这个 I/0 操作针对了一个被关闭的文件上,当然这操作是不能修改文件中的操作的:

 可以看到文件中只有第一次操作有效,2222没有被输入。

 读文件

  •  再读文件的时候读 open 函数需要使用 ‘r'的方式来打开文件;
  • 然后使用 read 方法来完成读操作,参数表示为“读取几个字符”

 

 我们要读取这个文件中的内容。

f = open('d:/Python环境/test.txt','r')
result = f.read(2)    #表示读前两个字符
print(result)
f.close()

 这个报错是关于字符编码的问题,我们文件中是中文的内容,中文和英文类似,在计算机中,都是使用“数字”来表示字符的,但是具体是哪个数字对应这个汉子,在计算机中国可以有多个版本,我们最主要的版本是  GBK  和  UTF-8  这两种版本。GBK  只能表示我们的一些简体中文,表示的范围有限,一些跟复杂的符号就无法翻译了;而UTF - 8是使用更广泛的编码方式,全世界任一一种语言都可以用这个编码方式来翻译。

在实际开发的时候,就需要保证我们文件内容的编码方式和代码中操作文件的编码方式,得匹配。如果不匹配就容易出现上面的问题。

在代码中是尝试按照gbk来进行解析,而我们查看我们的文件编码发现是:UTF - 8的编码方式。

解决方式就是,让我们的代码按照UTF-8的编码方式来进行度文件的操作,我们在使用open函数的时候在后面多调用一个  encoding 参数:

f = open('d:/Python环境/test.txt','r',encoding = 'utf8')

这个open函数里面的前两个参数叫做  位置参数   ;第三个参数叫做   关键字参数。对于encoding这样的参数,是有默认值的,不传这个参数,在windows系统中,我们不传encoding参数那么它默认值为  GBK  编码方式进行编码。

f = open('d:/Python环境/test.txt','r',encoding = 'utf8')
result = f.read(2)    #表示读前两个字符
print(result)
f.close()

#床前

现在我们可以读出并打印这个文件里面的前两个字符了,这时我们发现,打印了“床前"这两个汉字,说明在Python中,一个汉字对应这一个字符。

 利用for循环,按行读取文件内容

 之前我们用read方法,可以读取文件中任意字符的内容,现在我们用for循环来按行来读取文件内容,这个其实是我们更常见的读取文件内容的方法:

f = open('d:/Python环境/test.txt','r',encoding = 'utf8')
for line in f:
    print(f'line = {line}')
f.close()

#line = 床前明月光

#line = 疑似地上霜

#line = 举头望明月

#line = 低头思故乡

我们在for 循环中创建了一个临时变量--line,而这个for循环的意思是,在f 这个文件对象中,每一次循环,我读取一行的内容给给line这个变量;

我们在打印的时候发现,我们每输出一行就要空出一行,然后在输出下一行。

这是因为,我们本来读取到的文件内容(这一行内容,末尾就有一个 '/n')  ;  而此处每一次使用print() 函数打印的时候,就会自动多加一个换行符。

如果不想有print()自动添加换行的行为,可以给print函数多加一个参数:

print(f'line = {line}',end = ' ')

此处的  end = ‘ ’ 中我们在 ' ' 中放的是一个 空格 ,这个参数的意思是,我们每一次调用print  函数的时候,在打印的最后以  空格   的形式来结束这一次打印。同样,按照之前的说法,这个参数也是有默认值的,默认值为 ‘\n’  。

改进之后输出:

 使用   readlines   方法直接把整个文件所有内容都读出来,按照行组织到一个列表里

f = open('d:/Python环境/test.txt','r',encoding = 'utf8')
lines = f.readlines()
print(lines)    #['床前明月光\n', '疑似地上霜\n', '举头望明月\n', '低头思故乡']
f.close()

我们发现输出的是一个列表,这个列表里的每一个元素都是文件中每一行的内容,我们发现每一行的后面都有一个 ‘\n'  ,如此也就验证了我们之前的操作。

 这个方法相对于之前使用 for 循环来读取全部文件操作,有一个好处,就是我们这个  readlines  方法是一次性全部读取完的,而for 循环是每一次循环就读一行;当我们的文件小 的时候还好,当我们文件里内容很多的时候,使用for循环比较低效,因为他需要一行一行的一次一次循环读取。

当然,readlines 方法也有坏处,就是我们能一次使用这个方法来读完文件里全部内容,是因为这个文件本来就不大,如果这个文件里内容太多,我们的内存根本就装不下。

上下文管理器

 我们之前说过文件有打开就必须由关闭,但是我们不是每一次度可以考虑到文件 close  操作的,比如我定义一个函数:
 

def func()
    f = open('d:/xxxxxxx/xxxxxx','r')
    #中间来写其他的操作文件的代码
    #万一中间的代码,有条件判定,函数返回,就会导致我们的文件泄漏
    if 条件:
        #进行条件处理
        f.close()
        return 
    #代码
    # 代码
    # 代码
    # 代码
    if 条件:
        #进行条件处理
        return   #这里我就忘记写  f.close() 关闭文件了

我们在条件判读,函数返回的时候,可能不是每一次都可以注意到文件关闭,这样就会导致文件泄漏,那么接下来,我们的上下文管理器就派上用场了。

          

def func():
    with open('d:/Python环境/test.txt','r',encoding = 'utf8') as f:
        #进行文件处理逻辑
        lines = f.readlines()
        
        #我们在处理文件操作的时候,可能就是使用if return出函数了
        if 条件:
          return

我们此处之前是吧 open函数的返回值直接赋值给 f,现在我们就不再用 = 来赋值,我们用 with as :语句来进行赋值。我们的 with as :  语句的下面是一个代码块,这个代码块里面就是需要我们对文件的操作代码,最后无论我们在if条件判断,跳出函数,有没有 close 文件,当这个with as :语句执行完,就会自动的帮我们进行 文件关闭操作。

python中库的使用

 所谓库也就是别人已经写好的代码,我们拿来直接使用,在python中的库,使用模块的方式去体现的。在python中有python的标准库和其他人写的第三方库,python中的第三方库的种类和数量都是远远大于标准库的。

标准库

 我们在Python的官方文档里面就可以看到这些库的内容:
The Python Standard Library — Python 3.10.8 documentationhttps://docs.python.org/3.10/library/index.html

 当我们安装了python之后,这个文档也会自动的下载在我们的python目录里:

 这个文档是直接下载好的不需要加载。

 下面我们举例来讲解库的使用操作:

日期计算器

 给定两个日期,计算这两个日期之前相差多少天。

在python中有一个datetime这个库可以帮助我们实现我们的操作:

  • 根据日期构造出datetime类型的变量
  • 把两个变量进行相减,得到的结果纪委所求
import datetime
#先构造 datetime 变量

date1 = datetime.datetime(2012,2,14)
date2 = datetime.datetime(year = 2016,month = 2,day = 3)

print(date2 - date1)    #1450 days, 0:00:00

import语句用来导入其他python文件(称为模块module),使用该模块里定义的类、方法或者变量,函数。这个调用的模块可以是第三方库,标准库  或者是自己写的模块。上面的 import datetime  就是调用了  datetime  这个模块。

而datetime.datetime 意思是在datetime 模块中 创建了一个 datetime 这个类型(前面的为模块,后面的为模块中的类型)而后括号中的是这个类型的参数(按照左到右的顺序是  年 月 日 时 分 秒,这些参数的默认值都是 0 )

我们还可以这样写:

from datetime import datetime
#先构造 datetime 变量

date1 = datetime(2012,2,14)
date2 = datetime(year = 2016,month = 2,day = 3)

print(date2 - date1)    #1450 days, 0:00:00

最上面的 from datetime import datetime  意思是在  datetime  这个模块里  import 一下 datetime这个类型。这个时候我们发现 下面的 赋值操作就不需要在前面加一个  datetime.  (模块名. )  来说明这时datetime 模块里面的内容了。

还可以这样写:

import datetime as dt
#先构造 datetime 变量

date1 = dt.datetime(2012,2,14) #从前面的某一天到2012.2.14这天有多少天
date2 = dt.datetime(year = 2016,month = 2,day = 3)  #从前面某一天带2016.2.3这天有多少天

print(date2 - date1)    #1450 days, 0:00:00

import datetime as dt  的意思就是 我们先import出  datetime 这个模块,然后 as dt 就是把这个模块其一个别名叫做  dt。这个时候,我们在下面创建 datetime  类型的变量的时候,前面的模块申明就可以直接写成 dt  ,不用再写成 datetime了。

 字符串操作

 字符串是 Python 的内置类型,字符串的很多方法不需要导入额外的模块,即直接使用就行了。

翻转单词顺序

 输入一个英文句子,翻转句子中的英文单词的顺序,但是单词内字符串的顺序不变。为简单起见,标点符号和普通字母一样处理,例如:
输入字符串” I am a student,"  ,则输出 "student , a am I" 。我们使用空格来分割单词。

思路:

  • 针对我们输入的字符串,使用空格进行切分,字符串 split  方法,可以指定分隔符,把一个字符串分出多个部分,放到一个列表里。
  • 在针对刚才的切分列表,进行逆序 
  • 再把逆序后的列表,给组合起来, 用 join方法进行组合列表。

def reversWord(s):
    tokens = s.split(' ')  #用 空格 来进行分割字符串
    tokens.reverse()     #对这个列表进行逆序
    return ' '.join(tokens) #把 分割逆序之后 tokens里面的每一个字符串 重新拼接

print(reversWord('I am a student.'))   #student. a am I

 我们在pycharm中 在对某一变量后面输入 “  .  ” 之后他会把这个类型的方法给列出来方便我们的快捷输入:

 但是我们在输入上述代码的时候pycharm没有给这样的提示:

 

 我们对代码进行一下修改:

def reversWord(s: str):
    tokens = s.split(' ')  #用 空格 来进行分割字符串
    tokens.reverse()     #对这个列表进行逆序
    return ' '.join(tokens) #把 分割逆序之后 tokens里面的每一个字符串 重新拼接

print(reversWord('I am a student.'))   #student. a am I

其中我们对这个 s 变量进行类型声明,这是因为python是一个动态语言,在我们输入之前s的值之前,编译器是不知道这个 s 到底是什么类型,不知道什么类型也就不知道这个变量到底可以用什么方法,我们其中用的 split 方法是否可以用,编译器也是不知道的。所以我们在函数接收参数的时候,给这个变量声明一个 str 类型,告诉编译器这是一个字符串。

我们用以变量来接收 split 方法所传回来的 用空格分割好的字符串列表,这个列表的每个元素是 s 中字符串分割出的每一个单词。

然后用 reverse 方法来对字符串进行逆序。

然后再函数return 返回时候,我们用了  '  '.join(tokens)   ;其中   ‘  ’  是一个空格的字符串,而字符串里面有一个  join 方法,可以帮我们把  tokens 里面的内容填写进去。

最后我们打印这个函数的返回值就可以实现这个效果了。、

旋转字符串

 给定两个字符串,  s  和 goal  ,如果在若干次旋转操作之后,  s  能变成  goal  ,那么返回true

s  的 旋转操作就是将 s  最左边的字符移动到最右边。

例如:   s = 'abcde',在旋转一次之后,结果就是'bcdea'

思路:

我们想到字符串的拼接,假如:s = 'abcde'   我们让 s + s 得到一个新的字符串  =>  'abcdeabcde',然后我们在去下标为 [ 1 ]  到 [ 6 ]  的字符串出来,而这个取出来的字符串不就是我们将 s 旋转之后得到的字符串吗?

当然,以上情况是我们知道了 输入的 s 字符串里字符的个数,当我们不知道字符串里字符的个数的时候,那我们取出的下标应该是  [  1  ]  ~~  [  len - 1 ]   其中len的长度是 我们输入的 s  的字符串长度。

而我们发现,我们在 s + s  得到一个更大的字符串,这个字符串其实就包含了我们 s  是的所有旋转之后的结果。

def rotateSyring(s,goal):
    if len(s) != len(goal):   #先判断这两个字符串里的字符个数是否相等,不相等那么说明不管s怎么选旋转都不可能=goal
        return False
    return goal in s + s  #判断在 s + s 这个大字符串里面有没有和goal相等的子集

统计字符串前缀

 给定一个字符串列表 words 和一个字符串  s  ,其中 words[i]  和 s 只包括小写英文字母;请你返回words中是字符串 s 前缀的字符串数目。

注:一个字符串前缀是出现在字符串开头的子字符串。子字符串是一个字符串中的连续字符序列;

例:

 思路:

遍历一遍word,取出每个字符串,判定当前这个字符串,是否是s 的前缀就行了(s 是否是以曾字符串开头的)

在python中的字符串类型 有一个 startswith 方法判定某一个字符串,他的前缀是不是另一个字符串。是就返回 True  不是就返回 False。        

def countPrefixes(words: list, s: str):
    count = 0
    for word in words:    #用 for 循环 遍历 words 这个列表
        if s.startswith(word):   每一次遍历,只要这个元素是 s 的前缀就让 count + 1
            #s  是以word 开头
            count += 1
    return count

print(countPrefixes(['a','b','c','ab','bc','abc'],'abc'))    #3

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐