拒绝重复造轮子!python实用工具类及函数大推荐!

拒绝重复造轮子!python实用工具类及函数大推荐!

夏洛之枫夏洛之枫

从2015年转行程序员至今也有两年多了,当初自学的java却误打误撞进了python的坑,入职之后一天java也没有写过,或许这可以理解成缘分吧,哈哈!使用python工作久了,随手写一些小工具再所难免,不知不觉,我的工具包也增长到了几千行代码。其中有的函数依照别人的代码改写,也有的源于自己的灵感,我自认为还是挺好用的。现在统一规整后做成pip包,拿出来分享给大家使用。

函数都很小巧,代码实现相对简单,风格比较自由,尽可能不依赖其它安装包,极个别的工具依赖一些特定的驱动程序,比如redis,kafka, psutil,如果自己的项目没有相关需求,那么可以将需要的方法,类复制放到自己的项目中,不必完全安装ShichaoMa/toolkit。放心,我是不会追究版权的。

正文开始

安装toolkity

#python3测试有效
pip install toolkity

有朋友可能会想为什么不叫toolkit,我,也是那么想的。可是名字被别人占用了肿么办,只能在后面加个y,看起来正式而不失一点小俏皮。

安装完毕,来跟我学习几个常用函数的使用方法吧。

  • Timer 简单好用的计时器
class Timer(object):
    """
    计时器,对于需要计时的代码进行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个参数

  1. :param exception: 指定要监控的异常, 默认为Exception
  2. :param func_name: 可以选择提供当前所在函数的名称,回调函数会提交到函数,用于跟踪,默认为None,自己判断调用函数名称
  3. :param errback: 提供一个回调函数,如果发生了指定异常,就调用该函数,该函数的返回值为True时不会继续抛出异常 默认打出异常信息,返回True
  4. :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'\n',''),
             (r'\t', ''),
             (r'\r', ''),
             (r'  ', ''),
             (r'\u2018', "'"),
             (r'\u2019', "'"),
             (r'\ufeff', ''),
             (r'\u2022', ":"),
             (r"<([a-z][a-z0-9]*)\ [^>]*>", '<\g<1>>'),
             (r'<\s*script[^>]*>[^<]*<\s*/\s*script\s*>', ''),
             (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的队列
  • FifoDiskQueue:持久化 FIFO 队列
  • Translate:翻译器,继承自ProxyPool,安装即可用的翻译器见ShichaoMa/translate_html

除以上工具之外,还有一些小工具,小函数,如果有兴趣的话,自己去源码里发现吧。

喜欢的可以给我点赞哦!

不懂的可以给我留言哦!

欢迎光临我的github 里面有十几个开源项目,总有一款适合你!

github: ShichaoMa

欢迎光临我的小博客,博客源码在github上有托管,一键安装,一键使用,博客中汇集了工作中常用问题的解决方案,希望能够为您提供帮助哦!

个人博客:夏洛之枫的个人博客

「真诚赞赏,手留余香」
还没有人赞赏,快来当第一个赞赏的人吧!
文章被以下专栏收录
39 条评论
推荐阅读