纸上得来终觉浅,绝知此事要躬行。
光说不练假把式,所以还是需要找点题目来巩固一下自己博客中写到的基础知识,而且长时间不去复习的话很容易就会遗忘了。这样,就会让我们产生书到用时方恨少的感觉,所以练习和复习都是十分十分重要的。
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()