从2015年转行程序员至今也有两年多了,当初自学的java却误打误撞进了python的坑,入职之后一天java也没有写过,或许这可以理解成缘分吧,哈哈!使用python工作久了,随手写一些小工具再所难免,不知不觉,我的工具包也增长到了几千行代码。其中有的函数依照别人的代码改写,也有的源于自己的灵感,我自认为还是挺好用的。现在统一规整后做成pip包,拿出来分享给大家使用。
函数都很小巧,代码实现相对简单,风格比较自由,尽可能不依赖其它安装包,极个别的工具依赖一些特定的驱动程序,比如redis,kafka, psutil,如果自己的项目没有相关需求,那么可以将需要的方法,类复制放到自己的项目中,不必完全安装ShichaoMa/toolkit。放心,我是不会追究版权的。
· 正 · 文 · 来 · 啦 ·
安装toolkity
#python3测试有效 pip install toolkity
有朋友可能会想为什么不叫toolkit,我,也是那么想的。可是名字被别人占用了肿么办,只能在后面加个y,看起来正式而不失一点小俏皮。
安装完毕,来跟我学习几个常用函数的使用方法吧。
- Timer 简单好用的计时器
class Timer(object): Python学习交流群:1004391443 """ 计时器,对于需要计时的代码进行with操作: with Timer() as timer: ... ... print(timer.cost) ... """ def __init__(self, start=None): self.start = start if start is not None else time.time() def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.stop = time.time() self.cost = self.stop - self.start return exc_type is None
有时我们想对我们写的代码执行时间进行统计,通常我们会使用如下代码:
import time start = time.time() ...# 我们的代码 end = time.time() cost = end - start
现在有了timer,一切都变的简单啦
# 使用方法: In [2]: from toolkit.managers import Timer In [3]: with Timer() as timer: ...: num = 10**20 ...: In [4]: print(timer.cost) 3.6716461181640625e-05 In [5]: print(timer.start) 1512270309.2823365 In [6]: print(timer.stop) 1512270309.2823732
同时,你还可以指定开始时间
In [9]: import time In [10]: with Timer(start=time.time()-10) as timer: ...: num = 12**12 ...: In [11]: timer.cost Out[11]: 10.000059366226196
- ExceptContext 异常捕获上下文
class ExceptContext(object):
"""
异常捕获上下文
eg:
def test():
with ExceptContext(Exception, errback=lambda name, *args:print(name)):
raise Exception("test. ..")
"""
def __init__(self, exception=Exception, func_name=None,
errback=lambda func_name, *args: traceback.print_exception(*args) is None,
finalback=lambda got_err: got_err):
"""
:param exception: 指定要监控的异常
:param func_name: 可以选择提供当前所在函数的名称,回调函数会提交到函数,用于跟踪
:param errback: 提供一个回调函数,如果发生了指定异常,就调用该函数,该函数的返回值为True时不会继续抛出异常
:param finalback: finally要做的操作
"""
self.errback = errback
self.finalback = finalback
self.exception = exception
self.got_err = False
self.func_name = func_name or _find_caller_name(is_func=True)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return_code = False
if isinstance(exc_val, self.exception):
self.got_err = True
return_code = self.errback(self.func_name, exc_type, exc_val, exc_tb)
self.finalback(self.got_err)
return return_code
通常我们想捕获异常时,会写如下代码
got_err = False try: 1/0 except ZeroDivisionError: import traceback traceback.print_exc() got_err = True finally: print(got_err)
现在我们可以用ExceptContext简单实现
with ExceptContext(ZeroDivisionError, finalback=lambda got_err: print(got_err)): 1/0
其中ExceptContext接收4个参数
- :param exception: 指定要监控的异常, 默认为Exception:param func_name: 可以选择提供当前所在函数的名称,回调函数会提交到函数,用于跟踪,默认为None,自己判断调用函数名称:param errback: 提供一个回调函数,如果发生了指定异常,就调用该函数,该函数的返回值为True时不会继续抛出异常 默认打出异常信息,返回True:param finalback: finally要做的操作 默认返回是否发生异常。
通过自定义errback,我们可以对异常做任何想要的操作。
- debugger debug小工具
def debugger():
try:
debug = eval(os.environ.get("DEBUG"))
except Exception:
debug = False
if debug:
d = pdb.Pdb()
d.set_trace( sys._getframe().f_back)
有时我们可能会使用pdb来调试,经常会发生的情况是,我们在测试环境下调试,部署到生产之后,发现pdb.set_trace()忘记删除,导致代码在生产系统中卡住,这就很尴尬了。使用debugger,同时在测试环境中加入export DEBUG=True,可以轻松避免上述情况
import os os.environ["DEBUG"] = True from toolkit import debugger def fun(): debugger() .... fun()
- duplicate 保序去重函数
def duplicate(iterable, keep=lambda x: x, key=lambda x: x, reverse=False): """ 保序去重 :param iterable: :param keep: 去重的同时要对element做的操作 :param key: 使用哪一部分去重 :param reverse: 是否反向去重 :return: """ result = list() duplicator = list() if reverse: iterable = reversed(iterable) for i in iterable: keep_field = keep(i) key_words = key(i) if key_words not in duplicator: result.append(keep_field) duplicator.append(key_words) return list(reversed(result)) if reverse else result
我们经常会遇到去重问题,比如
In [2]: ls = [3,4,5,2,4,1] In [3]: ls = list(set(ls)) In [4]: ls Out[4]: [1, 2, 3, 4, 5]
上述列表中有2个4,我们想去掉多余的4,但是不想顺序乱掉,如果使用list(set(ls))的方式,顺序会乱掉,因此我们可以使用duplicate函数来做
In [5]: from toolkit import duplicate
In [6]: ls = [3, 4, 5, 2, 4, 1]
# 正序去重
In [7]: duplicate(ls)
Out[7]: [3, 4, 5, 2, 1]
# 逆序去重
In [8]: duplicate(ls, reverse=True)
Out[8]: [3, 5, 2, 4, 1]
# 指定规则去重
In [10]: ls = [{"a": 3, "b": 4}, {"a":3, "b": 5}]
In [11]: duplicate(ls, key=lambda x: x["a"])
Out[11]: [{'a': 3, 'b': 4}]
# 去重后仅保留部分数据
In [12]: duplicate(ls, key=lambda x: x["a"], keep=lambda x: x["b"])
Out[12]: [4]
- chain_all 连接多个可迭代对象
def chain_all(iter):
"""
连接多个序列或字典
:param iter:
:return:
"""
iter = list(iter)
if not iter:
return []
if isinstance(iter[0], dict):
result = {}
for i in iter:
result.update(i)
else:
result = reduce(lambda x, y: list(x)+list(y), iter)
return result
以前我们都使用Itertools.chain.from_iterable,但是这个函数无法连接字典同时返回的是可迭代器对象。
In [14]: chain_all([[1, 2], [1, 2]])
Out[14]: [1, 2, 1, 2]
In [15]: chain_all([{"a": 1}, {"b": 2}])
Out[15]: {'a': 1, 'b': 2}
- safely_json_loads 安全的将字符串变成json对象
# 代码实现 def safely_json_loads(json_str, defaulttype=dict, escape=True): """ 返回安全的json类型 :param json_str: 要被loads的字符串 :param defaulttype: 若load失败希望得到的对象类型 :param escape: 是否将单引号变成双引号 :return: """ if not json_str: return defaulttype() elif escape: data = replace_quote(json_str) return json.loads(data) else: return json.loads(json_str)
对于空字符串,返回默认类型,对于使用单引号包裹的字符串,将其转换成双引号
In [17]: json_str = """{'a': 1, "b": "i'm tom."}"""
In [18]: safely_json_loads(json_str)
Out[18]: {'a': 1, 'b': "i'm tom."}
# 上面的i'm tom中的单引号不会被转成双引号
- format_html_string 格式化html
def format_html_string(html): """ 格式化html, 去掉多余的字符,类,script等。 :param html: :return: """ trims = [(r' ',''), (r' ', ''), (r' ', ''), (r' ', ''), (r'\u2018', "'"), (r'\u2019', "'"), (r'\ufeff', ''), (r'\u2022', ":"), (r"<([a-z][a-z0-9]*) [^>]*>", '<g<1>>'), (r'<s*script[^>]*>[^<]*<s*/s*scripts*>', ''), (r"</?a.*?>", '')] return reduce(lambda string, replacement: re.sub(replacement[0], replacement[1], string), trims, html) 去掉多余的html属性
In [20]: format_html_string("<div class='color'></div>")
Out[20]: '<div></div>'
....
除以上工具以外,还有很多工具非常有用,由于例子相对复杂,就不一一举例了,可以多关注我的github, 如ShichaoMa/proxy_factory项目,就有很多用例出现。
下面简单介绍一些其它工具
- 重试器,包装函数对指定异常进行重试
def retry_wrapper(retry_times, exception=Exception, error_handler=None, interval=0.1): """ 函数重试装饰器 :param retry_times: 重试次数 :param exception: 需要重试的异常 :param error_handler: 出错时的回调函数 :param interval: 重试间隔时间 :return: """ def out_wrapper(func): @wraps(func) def wrapper(*args, **kwargs): count = 0 while True: try: return func(*args, **kwargs) except exception as e: count += 1 if error_handler: result = error_handler(func.__name__, count, e, *args, **kwargs) if result: count -= 1 if count >= retry_times: raise time.sleep(interval) return wrapper return out_wrapper
- 超时器,装饰函数并指定其超时时间
def timeout(timeout_time, default): """ Decorate a method so it is required to execute in a given time period, or return a default value. :param timeout_time: :param default: :return: """ class DecoratorTimeout(Exception): pass def timeout_function(f): def f2(*args): def timeout_handler(signum, frame): raise DecoratorTimeout() old_handler = signal.signal(signal.SIGALRM, timeout_handler) # triger alarm in timeout_time seconds signal.alarm(timeout_time) try: retval = f(*args) except DecoratorTimeout: return default finally: signal.signal(signal.SIGALRM, old_handler) signal.alarm(0) return retval return f2 return timeout_function
- 自实现groupby
def groupby(it, key): """ 自实现groupby,itertool的groupby不能合并不连续但是相同的组, 且返回值是iter :return: 字典对象 """ groups = dict() for item in it: groups.setdefault(key(item), []).append(item) return groups
- cookie解析
def parse_cookie(string):
"""
解析cookie
:param string:
:return:
"""
results = re.findall('([^=]+)=([^;]+);?s?', string)
my_dict = {}
for item in results:
my_dict[item[0]] = item[1]
return my_dict
- 查找字符串函数和类
def load_function(function_str):
"""
返回字符串表示的函数对象
:param function_str: module1.module2.function
:return: function
"""
mod_str, _sep, function_str = function_str.rpartition('.')
return getattr(load_module(mod_str), function_str)
load_class = load_function
- 查找字符串模块
def load_module(module_str):
"""
返回字符串表示的模块
:param module_str: os.path
:return: os.path
"""
return __import__(module_str, fromlist=module_str.split(".")[-1])
- 获取可用端口
def free_port():
"""
Determines a free port using sockets.
"""
free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
free_socket.bind(('0.0.0.0', 0))
free_socket.listen(5)
port = free_socket.getsockname()[1]
free_socket.close()
return port
- 线程安全装饰器
def thread_safe(lock): """ 对指定函数进行线程安全包装,需要提供锁 :param lock: 锁 :return: """ def decorate(func): @wraps(func) def wrapper(*args, **kwargs): with lock: return func(*args, **kwargs) return wrapper return decorate
- 慢保存或提交
def call_later(callback, call_args=tuple(), immediately=True,
interval=1):
"""
应用场景:
被装饰的方法需要大量调用,随后需要调用保存方法,但是因为被装饰的方法访问量很高,而保存方法开销很大
所以设计在装饰方法持续调用一定间隔后,再调用保存方法。规定间隔内,无论调用多少次被装饰方法,保存方法只会
调用一次,除非immediately=True
:param callback: 随后需要调用的方法名
:param call_args: 随后需要调用的方法所需要的参数
:param immediately: 是否立即调用
:param interval: 调用间隔
:return:
"""
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
self = args[0]
try:
return func(*args, **kwargs)
finally:
if immediately:
getattr(self, callback)(*call_args)
else:
now = time.time()
if now - self.__dict__.get("last_call_time", 0) > interval:
getattr(self, callback)(*call_args)
self.__dict__["last_call_time"] = now
return wrapper
return decorate
- 类中的线程安全装饰器
def thread_safe_for_method_in_class(func): """ 对类中的方法进行线程安全包装 :param func: :return: """ @wraps(func) def wrapper(*args, **kwargs): self = args[0] try: self.lock.acquire() return func(*args, **kwargs) finally: self.lock.release() return wrapper
- 获取ip
def get_ip(): """ 获取局域网ip :return: """ netcard_info = [] info = psutil.net_if_addrs() for k,v in info.items(): for item in v: if item[0] == 2 and not item[1]=='127.0.0.1': netcard_info.append((k,item[1])) if netcard_info: return netcard_info[0][1]
下面提供一些基础设施
common_stop_start_control :提供开,关,重启,状态等命令行服务
Singleton:单例元类Logger:提供日志服务
SettingsWrapper: 提供配置信息服务
ParallelMonitor: 使用Singleton构建, 多线程多进程统一管理器
LoggingMonitor:内建Logger和Settings
Service: 继承自ParallelMonitor,LoggingMonitor并实现common_stop_start_control 接口。编写微服务专用基类。
ProxyPool:基于redis的代理池,继承自Logger
ItemConsumer:kafka消费者,继承自Service
ItemProducer:kafka生产者,继承自Service
RedisQueue:基于redis的队列Fifo
DiskQueue:持久化 FIFO 队列
330

被折叠的 条评论
为什么被折叠?



