Python语法学习篇(三)【py3】

发布于:2025-09-14 ⋅ 阅读:(21) ⋅ 点赞:(0)

测试篇

此部分承接上一篇文章,在发现错误以及调试后,需要对一个模块或是函数或类进行检验

单元测试

为了保证代码质量,方便后续修改和设计,我们采用单元测试:

要测试的Dict类:

# mydict.py
class Dict(dict):
    """支持属性访问的字典"""
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(f"'Dict' object has no attribute '{key}'")
    
    def __setattr__(self, key, value):
        self[key] = value

测试代码:(引入 Python 自带的 unittest 模块)

# test_mydict.py
import unittest
from mydict import Dict

class TestDict(unittest.TestCase):
    
    def test_init(self):
        """测试初始化"""
        d = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)          # 属性访问
        self.assertEqual(d['b'], 'test')   # 字典访问
        self.assertTrue(isinstance(d, dict))
    
    def test_key(self):
        """测试字典方式设置和获取"""
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')   # 可以通过属性访问
    
    def test_attr(self):
        """测试属性方式设置和获取"""
        d = Dict()
        d.key = 'value'
        self.assertEqual(d['key'], 'value')  # 可以通过字典访问
        self.assertIn('key', d)              # 确实在字典中
    
    def test_keyerror(self):
        """测试访问不存在的key时抛出KeyError"""
        d = Dict()
        with self.assertRaises(KeyError):    # 期待抛出KeyError
            value = d['nonexistent']
    
    def test_attrerror(self):
        """测试访问不存在的属性时抛出AttributeError"""
        d = Dict()
        with self.assertRaises(AttributeError):  # 期待抛出AttributeError
            value = d.nonexistent

# 运行测试
if __name__ == '__main__':
    unittest.main()

编写时测试类继承自unittest.TestCase,test开头的为测试方法,继承的类提供了内置的条件判断,调用他们通过assert断言输出是否为我们期望输出的值,如code中的assertEquals()

self.assertEquals(abs(-1), 1) # 断言函数返回的结果与 1 相等
另一种重要的断言就是期待抛出指定类型的 Error,比如通过 d['empty']访问不存在的 key
时,断言会抛出 KeyError
with self.assertRaises(KeyError):
value = d['empty']
而通过 d.empty 访问不存在的 key 时,我们期待抛出 AttributeError
with self.assertRaises(AttributeError):
value = d.empty

运行测试

# 方法1:直接运行测试文件
python test_mydict.py

# 方法2:使用unittest模块(推荐)
python -m unittest test_mydict

# 输出:
# .....
# ----------------------------------------------------------------------
# Ran 5 tests in 0.001s
# OK

setUp 和 tearDown

这是两个特殊方法,会在每调用一个测试方法的前后分别被执行

这种什么时候可能会用到呢,一般可用于连接:连接数据库,连接通信协议

setUp()里可以用于连接,tearDown()里可以用来释放。这样就省去此类每个测试中重复的代码部分。

class TestDict(unittest.TestCase):
    
    def setUp(self):
        """每个测试方法前执行"""
        print("准备测试环境...")
        self.test_dict = Dict(a=1, b=2)
    
    def tearDown(self):
        """每个测试方法后执行"""
        print("清理测试环境...")
        del self.test_dict
    
    def test_something(self):
        """测试方法"""
        self.assertEqual(self.test_dict.a, 1)

文档测试

把测试写在文档字符串里,既是文档又是测试

# mydict_with_doctest.py
class Dict(dict):
    '''
    支持属性访问的字典类
    
    示例:
    >>> d = Dict(a=1, b=2)
    >>> d.a
    1
    >>> d['b']
    2
    >>> d.c = 3
    >>> d['c']
    3
    >>> d['nonexistent']
    Traceback (most recent call last):
    ...
    KeyError: 'nonexistent'
    >>> d.nonexistent
    Traceback (most recent call last):
    ...
    AttributeError: 'Dict' object has no attribute 'nonexistent'
    '''
    
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(f"'Dict' object has no attribute '{key}'")
    
    def __setattr__(self, key, value):
        self[key] = value

if __name__ == '__main__':
    import doctest
    doctest.testmod()  # 自动运行文档中的测试

python mydict_with_doctest.py
# 如果测试通过,没有任何输出
# 如果测试失败,会显示详细的错误信息

测试小结

常见的软件工程项目都有测试环节,做好测试需要考虑很多,输入组合;边界条件;各类异常等等,是推进项目的保证和必要条件。

1. 先写测试(测试驱动开发)
def test_add_user():
    先写测试,再实现功能
    pass

2. 运行测试(应该失败)
3. 实现功能代码
4. 再次运行测试(应该通过)
5. 重构优化(确保测试仍然通过)

IO  Py编程


cite 廖老师:

IO 在计算机中指 Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留, CPU 这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需IO 接口。
比如你打开浏览器,访问新浪首页,浏览器这个程序就需要通过网络 IO 获取新浪的网页。
浏览器首先会发送数据给新浪服务器,告诉它我想要首页的 HTML,这个动作是往外发数
据,叫 Output,随后新浪服务器把网页发过来,这个动作是从外面接收数据,叫 Input。所
以,通常,程序完成 IO 操作会有 Input Output 两个数据流。当然也有只用一个的情况,
比如,从磁盘读取文件到内存,就只有 Input 操作,反过来,把数据写到磁盘文件里,就
只是一个 Output 操作。
IO 编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管
里的水,但是只能单向流动。Input Stream 就是数据从外面(磁盘、网络)流进内存,
Output Stream 就是数据从内存流到外面去。对于浏览网页来说,浏览器和新浪服务器之间
至少需要建立两根水管,才可以既能发数据,又能收数据。
由于 CPU 和内存的速度远远高于外设的速度,所以,在 IO 编程中,就存在速度严重不匹
配的问题。举个例子来说,比如要把 100M 的数据写入磁盘,CPU 输出 100M 的数据只需
0.01 秒,可是磁盘要接收这 100M 数据可能需要 10 秒,怎么办呢?有两种办法:
第一种是 CPU 等着,也就是程序暂停执行后续代码,等 100M 的数据在 10 秒后写入磁盘,
再接着往下执行,这种模式称为同步 IO
另一种方法是 CPU 不等待,只是告诉磁盘,您老慢慢写,不着急,我接着干别的事去了
于是,后续代码可以立刻接着执行,这种模式称为异步 IO同步和异步的区别就在于是否等待 IO 执行的结果。好比你去麦当劳点餐,你说来个汉堡
服务员告诉你,对不起,汉堡要现做,需要等 5 分钟,于是你站在收银台前面等了 5 分钟,
拿到汉堡再去逛商场,这是同步 IO
你说来个汉堡,服务员告诉你,汉堡需要等 5 分钟,你可以先去逛商场,等做好了,我
们再通知你,这样你可以立刻去干别的事情(逛商场),这是异步 IO
很明显,使用异步 IO 来编写程序性能会远远高于同步 IO,但是异步 IO 的缺点是编程模型
复杂。想想看,你得知道什么时候通知你汉堡做好了,而通知你的方法也各不相同。如果
是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手
机,这是轮询模式。总之,异步 IO 的复杂度远远高于同步 IO
操作 IO 的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级 C
口封装起来方便使用,Python 也不例外。

文件读写

文件读写是最常用的IO操作。python内置的文件读写函数用法兼容c语言,本质是os提供接口打开一个文件对象(文件描述符),从接口中读取数据或写入数据。

读文件

python内置读文件函数open(),’r’默认是读取文本文件且是ASCII编码的文本,若要读取二进制文件(例如常见的图片,视频等)采用‘rb’模式

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

f = open('D:\图片\OIP-C.jpg', 'rb')
print(f.read())

b'RIFF\x8cf\x00\x00WEBPVP8...'

如果非ASCII码的文本,就必须以二进制模式打开再解码。以GBK编码文件为例:

f = open('/Users/michael/gbk.txt', 'rb')
u = f.read().decode('gbk')
print(u)

这样转码很麻烦,py提供了一个codecs模块帮助读文件时自动转换编码,直接读出Unicode:

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

文件不存在,open()函数会抛出IOError的错误,返回错误码和错误信息。

Traceback (most recent call last):
  File "D:\Python Codelib\Code\pylearning.py", line 672, in <module>
    f = open('/Users/code/test.txt', 'r')
FileNotFoundError: [Errno 2] No such file or directory: '/Users/code/test.txt

文件打开成功,就可以直接调用read()方法一次读取文件的全部内容,py把内容读到内存中转化成str对象表示:

>>>f.read()

'Hello ,world'

关闭文件

>>> f.close()

文件读写时很容易出现读写错误返回IOError,那么close()就不会调用,所以可以结合学会的try...finally实现

try:
 f = open('/Users/code/test.txt', 'r')
 print (f.read())
finally:
 if f:
 f.close()

但Python有更方便的语句来自动调用close()方法,那就是with语句

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

和上一个try的意思一致还更简洁,不用每次去记得加close。

在前面操作时还有一个问题:调用read()一次性读取文件内容到内存,那如果文件内容几十G,岂不是一下子把内存干爆了?所以保守一下,采用反复调用的方法read(size),每次只会读最多size个字节的内容。这种控制读取的方法在Java等语言也见过。Python调用readline()可以每次读取一行,调用readlines()则可以读取所有内容并按行返回list

总结:文件小,直接read();不确定文件大小,用read(size)试探就行;配置文件(类似于json格式,适用于读取json数据集的情况)就用readlines()

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

这里还需要补充一点:

只要对一个对象使用open()函数,其返回中有read()方法的对象,我们统称它为File-like Object。其寓意也很简单:“像文件的对象”。

它的核心特征就是必须有read()方法,通常也有write(),seek(),close()等方法

且不要求从特定的类继承。

StringIO就是内存中的创建的File-like Object常作为临时缓冲。让你在内存中操作字符串像操作文件一样,下面只举一个小栗子方便理解

from io import StringIO

# 创建内存中的"文件"
memory_file = StringIO()

# 像写入文件一样写入内容
memory_file.write("Hello, World!\n")
memory_file.write("这是第二行内容")

# 移动到文件开头(就像操作真实文件一样)
memory_file.seek(0)

# 读取内容
content = memory_file.read()
print("读取的内容:")
print(content)

# 关闭"文件"
memory_file.close()

写文件

与读文件很是类似,写默认文本就是’r‘,二进制文本就是’rb‘

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

反复写反复调用write即可,但是close()不要忘。os不会把我们写入的东西立刻写入磁盘而是放入内存中缓存,空闲时慢慢写。调用close(),才能保证让os都写好。所以不加的后果就是可能写了一部分剩下的没了。所以with更保险些:

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

换码的操作仿照读就可以,这里不再另外举例了。

操作文件和目录

这部分在操作系统(下简称os)课中都会提及,不过本节根据python做适应。

操作文件和目录,可以在命令行里输入os的各种命令,dir或cp等等

import os

# 查看操作系统类型
print("操作系统:", os.name)  # 'posix' (Linux/Mac) 或 'nt' (Windows)

# 获取详细系统信息(Windows不可用)
if os.name == 'posix':
    print("系统详情:", os.uname())
#说明os模块的一些函数和os息息相关

目录操作

操作文件和目录的函数一部分放在 os 模块中,一部分放在 os.path 模块中,这一点要注意
一下
import os

# 获取当前工作目录
current_dir = os.getcwd()
print("当前目录:", current_dir)

# 切换目录
os.chdir('/tmp')  # 切换到 /tmp 目录
print("切换后目录:", os.getcwd())

# 切换回原目录
os.chdir(current_dir)

# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/michael'
# 在某个目录下创建一个新目录,
# 首先把新目录的完整路径表示出来:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')
操作 方法 说明
目录操作 os.listdir()os.mkdir() 列出、创建目录
文件操作 os.rename()os.remove() 重命名、删除文件
路径处理 os.path.join()os.path.exists(),
os.path.split(),os.path.splitext()
路径拼接和检查
信息获取 os.stat()os.getcwd() 获取文件和系统信息

tips:但是你会发现竟然没有复制操作,os中没提供系统调用,但其实通过读写可以实现,就是麻烦了些。

幸运的是 shutil 模块提供了 copyfile()的函数,你还可以在 shutil 模块中找到很多实用
函数,它们可以看做是 os 模块的补充

环境变量

os中定义的环境变量都保存在os.environ这个dict中

environ({'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': 'WKTAN', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'CONDA_DEFAULT_ENV': 'base', 'CONDA_PREFIX': ...})

获得某个环境变量的值,调用os.getenv()

>>>print(os.getenv('WINDIR'))
>>>C:\Windows


网站公告

今日签到

点亮在社区的每一天
去签到