python学习笔记03

文件处理

读写文件是最常见的IO操作。Python内置了读写文件的函数,用法和C是兼容的。

读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

读文件

要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和标示符:

>>> f = open('/Users/michael/test.txt', 'r')

标示符'r'表示读,这样,我们就成功地打开了一个文件。

如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在:

>>> f=open('/Users/michael/notfound.txt', 'r')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: '/Users/michael/notfound.txt'

如果文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示:

>>> f.read()
'Hello, world!'

最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:

>>> f.close()

由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现:

try:
    f = open('/path/to/file', 'r')
    print f.read()
finally:
    if f:
        f.close()

但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法:

with open('/path/to/file', 'r') as f:
    print f.read()

这和前面的try ... finally是一样的,但是代码更佳简洁,并且不必调用f.close()方法。

调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便:

for line in f.readlines():
    print(line.strip()) # 把末尾的'\n'删掉

file-like Object

open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。

StringIO就是在内存中创建的file-like Object,常用作临时缓冲。

二进制文件

前面讲的默认都是读取文本文件,并且是ASCII编码的文本文件。要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件即可:

>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节

字符编码

要读取非ASCII编码的文本文件,就必须以二进制模式打开,再解码。比如GBK编码的文件:

>>> f = open('/Users/michael/gbk.txt', 'rb')
>>> u = f.read().decode('gbk')
>>> u
u'\u6d4b\u8bd5'
>>> print u
测试

如果每次都这么手动转换编码嫌麻烦(写程序怕麻烦是好事,不怕麻烦就会写出又长又难懂又没法维护的代码),Python还提供了一个codecs模块帮我们在读文件时自动转换编码,直接读出unicode:

import codecs
with codecs.open('/Users/michael/gbk.txt', 'r', 'gbk') as f:
    f.read() # u'\u6d4b\u8bd5'

写文件

写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件:

>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()

你可以反复调用write()来写入文件,但是务必要调用f.close()来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险:

with open('/Users/michael/test.txt', 'w') as f:
    f.write('Hello, world!')

要写入特定编码的文本文件,请效仿codecs的示例,写入unicode,由codecs自动转换成指定编码。

小结
image.png

函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。

你可以定义一个由自己想要功能的函数,以下是简单的规则:
1.函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
2.任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
3.函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
4.函数内容以冒号起始,并且缩进。
5.return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。

def functionname( parameters ):
   "函数_文档字符串"
   function_suite
   return [expression]
函数调用

定义一个函数只给了函数一个名称,指定了函数里包含的参数,和代码块结构。
这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从Python提示符执行。
如下实例调用了printme()函数:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
# 定义函数
def printme( str ):
   "打印任何传入的字符串"
   print str;
   return;
 
# 调用函数
printme("我要调用用户自定义函数!");
printme("再次调用同一函数");
参数

以下是调用函数时可使用的正式参数类型:
1.必备参数:
必备参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
2.关键字参数:
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
3.默认参数:
调用函数时,缺省参数的值如果没有传入,则被认为是默认值。
4.不定长参数:
你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述2种参数不同,声明时不会命名。

匿名函数

1.python 使用 lambda 来创建匿名函数。
2.lambda只是一个表达式,函数体比def简单很多。
3.lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
4.lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
5.虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。

递归

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:
fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。
于是,fact(n)用递归的方式写出来就是:

    if n==1:
        return 1
    return n * fact(n - 1)

上面就是一个递归函数。可以试试:

>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L

如果我们计算fact(5),可以根据函数定义看到计算过程如下:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000)

>>> fact(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in fact
  ...
  File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1num * product在函数调用前就会被计算,不影响函数调用。

fact(5)对应的fact_iter(5, 1)的调用如下:

===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

练习题

购物车程序

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Created by master on  2018/5/16 14:22.
import os
import pickle


class ShoppingCart(object):
    goods_list = [
        {"name": "电脑", "price": 1999, "no.": "0"},
        {"name": "鼠标", "price": 10, "no.": "1"},
        {"name": "游艇", "price": 20, "no.": "2"},
        {"name": "键盘", "price": 90, "no.": "3"},
        {"name": "显示器", "price": 600, "no.": "4"},
        {"name": "内存条", "price": 500, "no.": "5"},
        {"name": "SSD", "price": 300, "no.": "6"},
        {"name": "秋裤", "price": 100, "no.": "7"},
    ]

    users = [
        {"username": "a", "password": "123", "salary": 0, "bought": []},
        {"username": "b", "password": "123", "salary": 0, "bought": []}]
    current_user = {}

    file_path = "users"

    # 开始
    def start(self):
        self.load_users()
        if self.login():
            if not self.current_user["salary"] > 0:
                self.input_salary()
            else:
                self.print_goods_list()
                self.buy(self.search())
        else:
            self.start()

    # 输入薪资
    def input_salary(self):
        salary = int(input("输入您的工资:"))
        self.current_user["salary"] = salary
        self.print_goods_list()
        goods = self.search()
        self.buy(goods)

    # 购物
    def buy(self, goods):
        if goods:
            if goods["price"] > self.current_user["salary"]:
                print("余额不足!")
            else:
                self.current_user["salary"] = self.current_user["salary"] - goods["price"]
                print("购买成功,剩余余额:%s" % self.high_light(self.current_user["salary"]))
                self.current_user["bought"].append(goods)
                self.buy(self.search())
            self.save_bought_goods()

        else:
            print("所选商品不存在,请重新选择")
            self.buy(self.search())

    # 保存购买记录
    def save_bought_goods(self):
        if os.path.exists(self.file_path):
            with open(self.file_path, "rb") as users:
                saved_users = pickle.load(users)
                for user in saved_users:
                    if user["username"] == self.current_user["username"]:
                        user.update(self.current_user)
                with open(self.file_path, "wb") as users:
                    pickle.dump(saved_users, users)
        else:
            with open(self.file_path, "wb") as users:
                pickle.dump(self.users, users)

    # 加载用户列表
    def load_users(self):
        if os.path.exists(self.file_path):
            with open(self.file_path, "rb") as users:
                self.users = pickle.load(users)
        else:
            with open(self.file_path, "wb") as users:
                pickle.dump(self.users, users)

    # 查询商品
    def search(self):
        no = input("输入商品编号(no.):,按e退出。")
        if not no:
            print("输入有误,请重新输入!")
            return
        if no == "E" or no == "e":
            self.save_bought_goods()
            exit("退出购物")
        else:
            for goods in self.goods_list:
                if goods["no."] == no:
                    return goods

    # 输出商品列表
    def print_goods_list(self):
        for goods in self.goods_list:
            print(goods)

    # 用户登陆
    def login(self):
        username = input("请输入用户名:")
        password = input("请输入密码:")
        login_user = None
        if not username or not password:
            print("用户名或密码不能为空")
            return
        for user in self.users:
            if user["username"] == username and user["password"] == password:
                print("登录成功!%s" % username)
                self.current_user = user
                self.see_log()
                login_user = user
        if not login_user:
            print("用户不存在或密码错误")
        return login_user

    # 查看购买记录
    def see_log(self):
        print(self.high_light("------------------"))
        print(self.high_light("当前用户:%s" % self.current_user["username"]))
        print(self.high_light("已购买:%s" % self.current_user["bought"]))
        print(self.high_light("余额:%s%s" % (self.high_light(self.current_user["salary"]), "元")))
        print(self.high_light("------------------"))

    # 高亮显示
    @staticmethod
    def high_light(content):
        return '\033[0;31m' + str(content) + '\033[0m'


if __name__ == '__main__':
    cart = ShoppingCart()
    cart.start()

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容

  • 瑶仔九点多过来,说妈妈,我还有作业没写完,我拿给她,自己老老实实的在那里做作业,我说你离我远点,别靠我太近,别传染...
  • 文科没学好的孩子,羡慕人家谈古论今,天文地理无一不晓。于是她给自己注册了一个简书账号,写流水记事,写风花雪月,写小...
    圈x2阅读 265评论 1 2
  • 如果我是你,我便如你一般追逐黎明 如果我是你,我便如你一般身处黄昏 在早春时看桃花开 在中夏与夏虫共舞 在晚秋时与...
    m子非鱼阅读 122评论 0 3
  • 坚持早起真的是很棒的 妈妈煮的面超级好吃 早上又看了看学校 还是只能靠自己路总得自己一个人走 留守父女吃完饭接着写...
    珍惜眼前始为真阅读 152评论 0 0