Python入门第二次练习
纸上得来终觉浅,绝知此事要躬行。
光说不练假把式,所以还是需要找点题目来巩固一下自己博客中写到的基础知识,而且长时间不去复习的话很容易就会遗忘了。这样,就会让我们产生书到用时方恨少的感觉,所以练习和复习都是十分十分重要的。
1. 匹配 URL 地址
需求说明
- 支持如www.google.com、http://www.example/file.html等URL的匹配。
标准实现
import re exp = re.compile(r'''^(https?://)? ([da-z.-]+) .([a-z.]{2,6}) ([/w .-]*)/?$ ''', re.X) assert exp.match('www.google.com') is not None assert exp.match('http://www.example/file.html') is not None assert exp.match('https://douban.com/tag') is not None
2. 匹配 IP 地址
需求说明
- 支持如192.168.0.1,8.8.8.8等IP地址的匹配。
标准实现
import re exp = re.compile(r'''^(?:(?:25[0-5] |2[0-4][0-9] |[1]?[0-9][0-9]?).){3} (?:25[0-5] |2[0-4][0-9] |[1]?[0-9][0-9]?)$''', re.X) assert exp.match('192.168.1.1') is not None assert exp.match('8.8.8.8') is not None assert exp.match('256.0.0.0') is None
3. 正则表达式
需求说明
- 把字符串2018-01-01用正则转化成01/01/2018。
- 实现一个函数,把CamelCase字符串用正则转化成camel_case。
- 在slack中,存在uid和id的对应关系,如下面的变量ID_NAMES。通过Slack的API能获取聊天记录,但是内容用的是uid,请用正则表达式re.sub函数实现uid和id的转换。re.sub函数第二个参数是一个pattern,不仅可以是一个正则表达式,还可以是一个函数。
标准实现
import re date = '2018-01-01' assert re.sub('(d{4})-(d{2})-(d{2})', r'2/3/1', date) == '01/01/2018'
def convert(s): res = re.sub(r'(.)([A-Z][a-z]+)',r'1_2', s) return re.sub(r'([a-z])([A-Z])',r'1_2', res).lower() assert convert('CamelCase') == 'camel_case' assert convert('SimpleHTTPServer') == 'simple_http_server'
ID_NAMES = {'U1EAT8MG9': 'xiaoming', 'U0K1MF23Z': 'laolin'} s = '<@U1EAT8MG9>, <@U0K1MF23Z> 嗯 确实是这样的' exp = re.compile(r'<@.*?>') def id_to_name(match): content = match.group() name = ID_NAMES.get(content[2:-1]) return '@{}'.format(name) if name else content assert exp.sub(id_to_name, s) == '@xiaoming, @laolin 嗯 确实是这样的'
4. Fibonacci 函数
需求说明
- 实现Fibonacci(斐波那契数列)函数。
>>> [fib(n) for n in range(16)] [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
标准实现
from functools import lru_cache @lru_cache(maxsize=None) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) assert [fib(n) for n in range(16)] == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
5. 实现 tree 命令
需求说明
- 实现Python版本的tree命令,只支持--help(显示帮助信息)和-L参数即可(如果不指定-L参数则打印该目录前所有深度的目录和文件)。
❯ tree -L 1 . ├── setup.py ├── token.pkl ├── venv └── hello.py 1 directory, 3 files ~/test2 t* ❯ tree -L 2 . ├── setup.py ├── token.pkl ├── venv │ ├── bin │ ├── include │ ├── lib │ └── pip-selfcheck.json └── hello.py
标准实现
import os import argparse def tree(path, depth=1, level=0): items = os.listdir(path) for item in items: if item.startswith('.'): continue print('| ' * level, end='') print('├── ', item) item = os.path.join(path, item) if os.path.isdir(item) and level < depth - 1: tree(item, depth=depth, level=level+1) if __name__ == '__main__': parser = argparse.ArgumentParser( description='list contents of directories in a tree-like format.') parser.add_argument('-L', '--level', type=int, help='Descend only level directories deep.') parser.add_argument('path', metavar='PATH', type=str, help='directory path name') args = parser.parse_args() tree(args.path, depth=args.level)
6. 装饰器
需求说明
- 写一个inject装饰器,在__init__时自动给类注入参数。
In : class Test: ...: @injectArguments ...: def __init__(self, x, y, z): ...: pass ...: In : t = Test(x=4, y=5, z=6) In : t.x, t.y, t.z Out: (4, 5, 6)
标准实现
def inject(func): def deco(*args, **kwargs): args[0].__dict__.update(kwargs) func(*args, **kwargs) return deco class A: @inject def __init__(self, x, y, z): pass a = A(x=4, y=5, z=6) assert a.x == 4
7. 函数调用计时
需求说明
- 使用with写一个函数调用计时的上下文管理器。
- 再改写一个调用计时的装饰器,可以直接把一个with管理器转换成装饰器。
In : with Timed(): ...: sleep(2) ...: Cost: 2.0050339698791504
标准实现
from time import time, sleep from contextlib import ContextDecorator class Timed: def __enter__(self): self.start = time() return self def __exit__(self, type, value, traceback): self.end = time() cost = self.end - self.start print(f'Cost: {cost}') class Timed2(ContextDecorator): def __enter__(self): self.start = time() return self def __exit__(self, type, value, traceback): self.end = time() cost = self.end - self.start print(f'Cost: {cost}') with Timed(): sleep(2) @Timed2() def f(): sleep(2) f()
8. 类的链式调用
需求说明
- 实现一个类,可以完成链式调用。
In : Seq(1, 2, 3, 4) ...: .map(lambda x: x * 2) ...: .filter(lambda x: x > 4) ...: .reduce(lambda x, y: x + y) ...: Out: 14
标准实现
from functools import reduce class Seq: def __init__(self, *items): self.items = items def __repr__(self): return str(self.items) def map(self, func): return self._evaluate(map, func) def filter(self, func): return self._evaluate(filter, func) def reduce(self, func): return self._evaluate(reduce, func) def _evaluate(self, transform, func): self.items = transform(func, self.items) return self
9. 读取超大文件
需求说明
- 如果Python读取一个10G文件, 你觉得怎么样的方式更快,更省内存呢?
标准实现
- 这才是Pythonic最完美的方式,既高效又快速。with语句句柄负责打开和关闭文件(包括在内部块中引发异常时),for line in f将文件对象f视为一个可迭代的数据类型,会自动使用I/O缓存和内存管理,这样就不必担心大文件了。
- 面对百万行的大型数据使用with open是没有问题的,但是这里面参数的不同也会导致不同的效率。经过测试发先参数为rb时的效率是r的5-6倍,由此可知二进制读取依然是最快的模式。
with open('filename', 'r', encoding = 'utf-8') as f: for line in f: do_something(line)
- 另外,还有说可以通过mmap模块处理的。mmap是一种虚拟内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
- 普通文件被映射到虚拟地址空间后,程序可以向访问普通内存一样对文件进行访问,在有些情况下可以提高I/O效率。它占用物理内存空间少,可以解决内存空间不足的问题,适合处理超大文件。不同于通常的字符串对象,它是可变的,可以通过切片的方式更改,也可以定位当前文件位置m.tell()或m.seek()定位到文件的指定位置,再进行m.write(str)固定长度的修改操作。
import mmap # write a simple example file with open("hello.txt", "wb") as f: f.write("Hello Python!n") # write example file with mmap with open("hello.txt", "r+b") as f: # memory-map the file, size 0 means whole file mm = mmap.mmap(f.fileno(), 0) print mm.readline() # prints "Hello Python!" print mm[:5] # prints "Hello" mm[6:] = " world!n" mm.seek(0) print mm.readline() # prints "Hello world!" mm.close()