code: #! -*- coding: utf-8 -*-
  # 代码合集
  
  import os, sys, six, re, json
  import logging
  import numpy as np
  from collections import defaultdict
  from bert4keras.backend import K, keras, tf
  
  _open_ = open
  is_py2 = six.PY2
  
  if not is_py2:
      basestring = str
  
  
  def to_array(*args):
      """批量转numpy的array
      """
      results = [np.array(a) for a in args]
      if len(args) == 1:
          return results[0]
      else:
          return results
  
  
  def is_string(s):
      """判断是否是字符串
      """
      return isinstance(s, basestring)
  
  
  def strQ2B(ustring):
      """全角符号转对应的半角符号
      """
      rstring = ''
      for uchar in ustring:
          inside_code = ord(uchar)
          # 全角空格直接转换
          if inside_code == 12288:
              inside_code = 32
          # 全角字符（除空格）根据关系转化
          elif (inside_code >= 65281 and inside_code <= 65374):
              inside_code -= 65248
          rstring += unichr(inside_code)
      return rstring
  
  
  def string_matching(s, keywords):
      """判断s是否至少包含keywords中的至少一个字符串
      """
      for k in keywords:
          if re.search(k, s):
              return True
      return False
  
  
  def convert_to_unicode(text, encoding='utf-8', errors='ignore'):
      """字符串转换为unicode格式（假设输入为utf-8格式）
      """
      if is_py2:
          if isinstance(text, str):
              text = text.decode(encoding, errors=errors)
      else:
          if isinstance(text, bytes):
              text = text.decode(encoding, errors=errors)
      return text
  
  
  def convert_to_str(text, encoding='utf-8', errors='ignore'):
      """字符串转换为str格式（假设输入为utf-8格式）
      """
      if is_py2:
          if isinstance(text, unicode):
              text = text.encode(encoding, errors=errors)
      else:
          if isinstance(text, bytes):
              text = text.decode(encoding, errors=errors)
      return text
  
  
  class open:
      """模仿python自带的open函数
      作用：1.主要是为了同时兼容py2和py3；2.增加了索引功能，方便读取大文件。
      """
      def __init__(
          self, name, mode='r', encoding=None, errors='strict', indexable=False
      ):
          self.name = name
          if is_py2:
              self.file = _open_(name, mode)
          else:
              self.file = _open_(name, mode, encoding=encoding, errors=errors)
          self.encoding = encoding
          self.errors = errors
          self.iterator = None
          if indexable:
              if is_string(indexable) and os.path.exists(indexable):
                  self.offsets = json.load(_open_(indexable))
              else:
                  self.create_indexes()
                  if is_string(indexable):
                      json.dump(self.offsets, _open_(indexable, 'w'))
  
      def create_indexes(self):
          print('creating indexes ...')
          self.offsets, offset = [], 0
          pbar = keras.utils.Progbar(os.path.getsize(self.name))
          while self.readline():
              self.offsets.append(offset)
              offset = self.tell()
              pbar.update(offset)
          self.seek(0)
          print('indexes created.')
  
      def __getitem__(self, key):
          self.seek(self.offsets[key])
          l = self.readline()
          if self.encoding:
              l = convert_to_unicode(l, self.encoding, self.errors)
          return l
  
      def __len__(self):
          return len(self.offsets)
  
      def __iter__(self):
          if hasattr(self, 'offsets'):
              for i in range(len(self)):
                  yield self[i]
          else:
              for l in self.file:
                  if self.encoding:
                      l = convert_to_unicode(l, self.encoding, self.errors)
                  yield l
  
      def next(self):
          if self.iterator is None:
              self.iterator = self.__iter__()
          return next(self.iterator)
  
      def __next__(self):
          return self.next()
  
      def read(self):
          text = self.file.read()
          if self.encoding:
              text = convert_to_unicode(text, self.encoding, self.errors)
          return text
  
      def readline(self):
          text = self.file.readline()
          if self.encoding:
              text = convert_to_unicode(text, self.encoding, self.errors)
          return text
  
      def readlines(self):
          if self.encoding:
              return [
                  convert_to_unicode(text, self.encoding, self.errors)
                  for text in self.file.readlines()
              ]
          else:
              return self.file.readlines()
  
      def write(self, text):
          if self.encoding:
              text = convert_to_str(text, self.encoding, self.errors)
          self.file.write(text)
  
      def flush(self):
          self.file.flush()
  
      def close(self):
          self.file.close()
  
      def tell(self):
          return self.file.tell()
  
      def seek(self, offset=0):
          return self.file.seek(offset)
  
      def __enter__(self):
          return self
  
      def __exit__(self, type, value, tb):
          self.close()
  
  
  def parallel_apply(
      func,
      iterable,
      workers,
      max_queue_size,
      callback=None,
      dummy=False,
      random_seeds=True
  ):
      """多进程或多线程地将func应用到iterable的每个元素中。
      注意这个apply是异步且无序的，也就是说依次输入a,b,c，但是
      输出可能是func(c), func(a), func(b)。
      参数：
          callback: 处理单个输出的回调函数；
          dummy: False是多进程/线性，True则是多线程/线性；
          random_seeds: 每个进程的随机种子。
      """
      if dummy:
          from multiprocessing.dummy import Pool, Queue
      else:
          from multiprocessing import Pool, Queue
  
      in_queue, out_queue, seed_queue = Queue(max_queue_size), Queue(), Queue()
      if random_seeds is True:
          random_seeds = [None] * workers
      elif random_seeds is None or random_seeds is False:
          random_seeds = []
      for seed in random_seeds:
          seed_queue.put(seed)
  
      def worker_step(in_queue, out_queue):
          """单步函数包装成循环执行
          """
          if not seed_queue.empty():
              np.random.seed(seed_queue.get())
          while True:
              i, d = in_queue.get()
              r = func(d)
              out_queue.put((i, r))
  
      # 启动多进程/线程
      pool = Pool(workers, worker_step, (in_queue, out_queue))
  
      if callback is None:
          results = []
  
      # 后处理函数
      def process_out_queue():
          out_count = 0
          for _ in range(out_queue.qsize()):
              i, d = out_queue.get()
              out_count += 1
              if callback is None:
                  results.append((i, d))
              else:
                  callback(d)
          return out_count
  
      # 存入数据，取出结果
      in_count, out_count = 0, 0
      for i, d in enumerate(iterable):
          in_count += 1
          while True:
              try:
                  in_queue.put((i, d), block=False)
                  break
              except six.moves.queue.Full:
                  out_count += process_out_queue()
          if in_count % max_queue_size == 0:
              out_count += process_out_queue()
  
      while out_count != in_count:
          out_count += process_out_queue()
  
      pool.terminate()
  
      if callback is None:
          results = sorted(results, key=lambda r: r[0])
          return [r[1] for r in results]
  
  
  def sequence_padding(inputs, length=None, value=0, seq_dims=1, mode='post'):
      """Numpy函数，将序列padding到同一长度
      """
      if length is None:
          length = np.max([np.shape(x)[:seq_dims] for x in inputs], axis=0)
      elif not hasattr(length, '__getitem__'):
          length = [length]
  
      slices = [np.s_[:length[i]] for i in range(seq_dims)]
      slices = tuple(slices) if len(slices) > 1 else slices[0]
      pad_width = [(0, 0) for _ in np.shape(inputs[0])]
  
      outputs = []
      for x in inputs:
          x = x[slices]
          for i in range(seq_dims):
              if mode == 'post':
                  pad_width[i] = (0, length[i] - np.shape(x)[i])
              elif mode == 'pre':
                  pad_width[i] = (length[i] - np.shape(x)[i], 0)
              else:
                  raise ValueError('"mode" argument must be "post" or "pre".')
          x = np.pad(x, pad_width, 'constant', constant_values=value)
          outputs.append(x)
  
      return np.array(outputs)
  
  
  def truncate_sequences(maxlen, indices, *sequences):
      """截断总长度至不超过maxlen
      """
      sequences = [s for s in sequences if s]
      if not isinstance(indices, (list, tuple)):
          indices = [indices] * len(sequences)
  
      while True:
          lengths = [len(s) for s in sequences]
          if sum(lengths) > maxlen:
              i = np.argmax(lengths)
              sequences[i].pop(indices[i])
          else:
              return sequences
  
  
  def text_segmentate(text, maxlen, seps='\n', strips=None):
      """将文本按照标点符号划分为若干个短句
      """
      text = text.strip().strip(strips)
      if seps and len(text) > maxlen:
          pieces = text.split(seps[0])
          text, texts = '', []
          for i, p in enumerate(pieces):
              if text and p and len(text) + len(p) > maxlen - 1:
                  texts.extend(text_segmentate(text, maxlen, seps[1:], strips))
                  text = ''
              if i + 1 == len(pieces):
                  text = text + p
              else:
                  text = text + p + seps[0]
          if text:
              texts.extend(text_segmentate(text, maxlen, seps[1:], strips))
          return texts
      else:
          return [text]
  
  
  def is_one_of(x, ys):
      """判断x是否在ys之中
      等价于x in ys，但有些情况下x in ys会报错
      """
      for y in ys:
          if x is y:
              return True
      return False
  
  
  class DataGenerator(object):
      """数据生成器模版
      """
      def __init__(self, data, batch_size=32, buffer_size=None):
          self.data = data
          self.batch_size = batch_size
          if hasattr(self.data, '__len__'):
              self.steps = len(self.data) // self.batch_size
              if len(self.data) % self.batch_size != 0:
                  self.steps += 1
          else:
              self.steps = None
          self.buffer_size = buffer_size or batch_size * 1000
  
      def __len__(self):
          return self.steps
  
      def sample(self, random=False):
          """采样函数，每个样本同时返回一个is_end标记
          """
          if random:
              if self.steps is None:
  
                  def generator():
                      caches, isfull = [], False
                      for d in self.data:
                          caches.append(d)
                          if isfull:
                              i = np.random.randint(len(caches))
                              yield caches.pop(i)
                          elif len(caches) == self.buffer_size:
                              isfull = True
                      while caches:
                          i = np.random.randint(len(caches))
                          yield caches.pop(i)
  
              else:
  
                  def generator():
                      for i in np.random.permutation(len(self.data)):
                          yield self.data[i]
  
              data = generator()
          else:
              data = iter(self.data)
  
          d_current = next(data)
          for d_next in data:
              yield False, d_current
              d_current = d_next
  
          yield True, d_current
  
      def __iter__(self, random=False):
          raise NotImplementedError
  
      def forfit(self, random=True):
          while True:
              for d in self.__iter__(random):
                  yield d
  
      def to_dataset(self, types, shapes, names=None, padded_batch=False):
          """转为tf.data.Dataset格式
          如果传入names的话，自动把数据包装成dict形式。
          """
          if names is None:
  
              generator = self.forfit
  
          else:
  
              if is_string(names):
                  warps = lambda k, v: {k: v}
              elif is_string(names[0]):
                  warps = lambda k, v: dict(zip(k, v))
              else:
                  warps = lambda k, v: tuple(
                      dict(zip(i, j)) for i, j in zip(k, v)
                  )
  
              def generator():
                  for d in self.forfit():
                      yield warps(names, d)
  
              types = warps(names, types)
              shapes = warps(names, shapes)
  
          if padded_batch:
              dataset = tf.data.Dataset.from_generator(
                  generator, output_types=types
              )
              dataset = dataset.padded_batch(self.batch_size, shapes)
          else:
              dataset = tf.data.Dataset.from_generator(
                  generator, output_types=types, output_shapes=shapes
              )
              dataset = dataset.batch(self.batch_size)
  
          return dataset
  
  
  class ViterbiDecoder(object):
      """Viterbi解码算法基类
      """
      def __init__(self, trans, starts=None, ends=None):
          self.trans = trans
          self.num_labels = len(trans)
          self.non_starts = []
          self.non_ends = []
          if starts is not None:
              for i in range(self.num_labels):
                  if i not in starts:
                      self.non_starts.append(i)
          if ends is not None:
              for i in range(self.num_labels):
                  if i not in ends:
                      self.non_ends.append(i)
  
      def decode(self, nodes):
          """nodes.shape=[seq_len, num_labels]
          """
          # 预处理
          nodes[0, self.non_starts] -= np.inf
          nodes[-1, self.non_ends] -= np.inf
  
          # 动态规划
          labels = np.arange(self.num_labels).reshape((1, -1))
          scores = nodes[0].reshape((-1, 1))
          paths = labels
          for l in range(1, len(nodes)):
              M = scores + self.trans + nodes[l].reshape((1, -1))
              idxs = M.argmax(0)
              scores = M.max(0).reshape((-1, 1))
              paths = np.concatenate([paths[:, idxs], labels], 0)
  
          # 最优路径
          return paths[:, scores[:, 0].argmax()]
  
  
  def softmax(x, axis=-1):
      """numpy版softmax
      """
      x = x - x.max(axis=axis, keepdims=True)
      x = np.exp(x)
      return x / x.sum(axis=axis, keepdims=True)
  
  
  class AutoRegressiveDecoder(object):
      """通用自回归生成模型解码基类
      包含beam search和random sample两种策略
      """
      def __init__(self, start_id, end_id, maxlen, minlen=1):
          self.start_id = start_id
          self.end_id = end_id
          self.maxlen = maxlen
          self.minlen = minlen
          self.models = {}
          if start_id is None:
              self.first_output_ids = np.empty((1, 0), dtype=int)
          else:
              self.first_output_ids = np.array([[self.start_id]])
  
      @staticmethod
      def wraps(default_rtype='probas', use_states=False):
          """用来进一步完善predict函数
          目前包含：1. 设置rtype参数，并做相应处理；
                    2. 确定states的使用，并做相应处理；
                    3. 设置温度参数，并做相应处理。
          """
          def actual_decorator(predict):
              def new_predict(
                  self,
                  inputs,
                  output_ids,
                  states,
                  temperature=1,
                  rtype=default_rtype
              ):
                  assert rtype in ['probas', 'logits']
                  prediction = predict(self, inputs, output_ids, states)
  
                  if not use_states:
                      prediction = (prediction, None)
  
                  if default_rtype == 'logits':
                      prediction = (
                          softmax(prediction[0] / temperature), prediction[1]
                      )
                  elif temperature != 1:
                      probas = np.power(prediction[0], 1.0 / temperature)
                      probas = probas / probas.sum(axis=-1, keepdims=True)
                      prediction = (probas, prediction[1])
  
                  if rtype == 'probas':
                      return prediction
                  else:
                      return np.log(prediction[0] + 1e-12), prediction[1]
  
              return new_predict
  
          return actual_decorator
  
      def last_token(self, model):
          """创建一个只返回最后一个token输出的新Model
          """
          if model not in self.models:
              outputs = [
                  keras.layers.Lambda(lambda x: x[:, -1])(output)
                  for output in model.outputs
              ]
              self.models[model] = keras.models.Model(model.inputs, outputs)
  
          return self.models[model]
  
      def predict(self, inputs, output_ids, states=None):
          """用户需自定义递归预测函数
          说明：定义的时候，需要用wraps方法进行装饰，传入default_rtype和use_states，
               其中default_rtype为字符串logits或probas，probas时返回归一化的概率，
               rtype=logits时则返回softmax前的结果或者概率对数。
          返回：二元组 (得分或概率, states)
          """
          raise NotImplementedError
  
      def beam_search(self, inputs, topk, states=None, temperature=1, min_ends=1):
          """beam search解码
          说明：这里的topk即beam size；
          返回：最优解码序列。
          """
          inputs = [np.array([i]) for i in inputs]
          output_ids, output_scores = self.first_output_ids, np.zeros(1)
          for step in range(self.maxlen):
              scores, states = self.predict(
                  inputs, output_ids, states, temperature, 'logits'
              )  # 计算当前得分
              if step == 0:  # 第1步预测后将输入重复topk次
                  inputs = [np.repeat(i, topk, axis=0) for i in inputs]
              scores = output_scores.reshape((-1, 1)) + scores  # 综合累积得分
              indices = scores.argpartition(-topk, axis=None)[-topk:]  # 仅保留topk
              indices_1 = indices // scores.shape[1]  # 行索引
              indices_2 = (indices % scores.shape[1]).reshape((-1, 1))  # 列索引
              output_ids = np.concatenate([output_ids[indices_1], indices_2],
                                          1)  # 更新输出
              output_scores = np.take_along_axis(
                  scores, indices, axis=None
              )  # 更新得分
              end_counts = (output_ids == self.end_id).sum(1)  # 统计出现的end标记
              if output_ids.shape[1] >= self.minlen:  # 最短长度判断
                  best_one = output_scores.argmax()  # 得分最大的那个
                  if end_counts[best_one] == min_ends:  # 如果已经终止
                      return output_ids[best_one]  # 直接输出
                  else:  # 否则，只保留未完成部分
                      flag = (end_counts < min_ends)  # 标记未完成序列
                      if not flag.all():  # 如果有已完成的
                          inputs = [i[flag] for i in inputs]  # 扔掉已完成序列
                          output_ids = output_ids[flag]  # 扔掉已完成序列
                          output_scores = output_scores[flag]  # 扔掉已完成序列
                          end_counts = end_counts[flag]  # 扔掉已完成end计数
                          topk = flag.sum()  # topk相应变化
          # 达到长度直接输出
          return output_ids[output_scores.argmax()]
  
      def random_sample(
          self,
          inputs,
          n,
          topk=None,
          topp=None,
          states=None,
          temperature=1,
          min_ends=1
      ):
          """随机采样n个结果
          说明：非None的topk表示每一步只从概率最高的topk个中采样；而非None的topp
               表示每一步只从概率最高的且概率之和刚好达到topp的若干个token中采样。
          返回：n个解码序列组成的list。
          """
          inputs = [np.array([i]) for i in inputs]
          output_ids = self.first_output_ids
          results = []
          for step in range(self.maxlen):
              probas, states = self.predict(
                  inputs, output_ids, states, temperature, 'probas'
              )  # 计算当前概率
              probas /= probas.sum(axis=1, keepdims=True)  # 确保归一化
              if step == 0:  # 第1步预测后将结果重复n次
                  probas = np.repeat(probas, n, axis=0)
                  inputs = [np.repeat(i, n, axis=0) for i in inputs]
                  output_ids = np.repeat(output_ids, n, axis=0)
              if topk is not None:
                  k_indices = probas.argpartition(-topk,
                                                  axis=1)[:, -topk:]  # 仅保留topk
                  probas = np.take_along_axis(probas, k_indices, axis=1)  # topk概率
                  probas /= probas.sum(axis=1, keepdims=True)  # 重新归一化
              if topp is not None:
                  p_indices = probas.argsort(axis=1)[:, ::-1]  # 从高到低排序
                  probas = np.take_along_axis(probas, p_indices, axis=1)  # 排序概率
                  cumsum_probas = np.cumsum(probas, axis=1)  # 累积概率
                  flag = np.roll(cumsum_probas >= topp, 1, axis=1)  # 标记超过topp的部分
                  flag[:, 0] = False  # 结合上面的np.roll，实现平移一位的效果
                  probas[flag] = 0  # 后面的全部置零
                  probas /= probas.sum(axis=1, keepdims=True)  # 重新归一化
              sample_func = lambda p: np.random.choice(len(p), p=p)  # 按概率采样函数
              sample_ids = np.apply_along_axis(sample_func, 1, probas)  # 执行采样
              sample_ids = sample_ids.reshape((-1, 1))  # 对齐形状
              if topp is not None:
                  sample_ids = np.take_along_axis(
                      p_indices, sample_ids, axis=1
                  )  # 对齐原id
              if topk is not None:
                  sample_ids = np.take_along_axis(
                      k_indices, sample_ids, axis=1
                  )  # 对齐原id
              output_ids = np.concatenate([output_ids, sample_ids], 1)  # 更新输出
              end_counts = (output_ids == self.end_id).sum(1)  # 统计出现的end标记
              if output_ids.shape[1] >= self.minlen:  # 最短长度判断
                  flag = (end_counts == min_ends)  # 标记已完成序列
                  if flag.any():  # 如果有已完成的
                      for ids in output_ids[flag]:  # 存好已完成序列
                          results.append(ids)
                      flag = (flag == False)  # 标记未完成序列
                      inputs = [i[flag] for i in inputs]  # 只保留未完成部分输入
                      output_ids = output_ids[flag]  # 只保留未完成部分候选集
                      end_counts = end_counts[flag]  # 只保留未完成部分end计数
                      if len(output_ids) == 0:
                          break
          # 如果还有未完成序列，直接放入结果
          for ids in output_ids:
              results.append(ids)
          # 返回结果
          return results
  
  
  def insert_arguments(**arguments):
      """装饰器，为类方法增加参数
      （主要用于类的__init__方法）
      """
      def actual_decorator(func):
          def new_func(self, *args, **kwargs):
              for k, v in arguments.items():
                  if k in kwargs:
                      v = kwargs.pop(k)
                  setattr(self, k, v)
              return func(self, *args, **kwargs)
  
          return new_func
  
      return actual_decorator
  
  
  def delete_arguments(*arguments):
      """装饰器，为类方法删除参数
      （主要用于类的__init__方法）
      """
      def actual_decorator(func):
          def new_func(self, *args, **kwargs):
              for k in arguments:
                  if k in kwargs:
                      raise TypeError(
                          '%s got an unexpected keyword argument \'%s\'' %
                          (self.__class__.__name__, k)
                      )
              return func(self, *args, **kwargs)
  
          return new_func
  
      return actual_decorator
  
  
  def longest_common_substring(source, target):
      """最长公共子串（source和target的最长公共切片区间）
      返回：子串长度, 所在区间（四元组）
      注意：最长公共子串可能不止一个，所返回的区间只代表其中一个。
      """
      c, l, span = defaultdict(int), 0, (0, 0, 0, 0)
      for i, si in enumerate(source, 1):
          for j, tj in enumerate(target, 1):
              if si == tj:
                  c[i, j] = c[i - 1, j - 1] + 1
                  if c[i, j] > l:
                      l = c[i, j]
                      span = (i - l, i, j - l, j)
      return l, span
  
  
  def longest_common_subsequence(source, target):
      """最长公共子序列（source和target的最长非连续子序列）
      返回：子序列长度, 映射关系（映射对组成的list）
      注意：最长公共子序列可能不止一个，所返回的映射只代表其中一个。
      """
      c = defaultdict(int)
      for i, si in enumerate(source, 1):
          for j, tj in enumerate(target, 1):
              if si == tj:
                  c[i, j] = c[i - 1, j - 1] + 1
              elif c[i, j - 1] > c[i - 1, j]:
                  c[i, j] = c[i, j - 1]
              else:
                  c[i, j] = c[i - 1, j]
      l, mapping = c[len(source), len(target)], []
      i, j = len(source) - 1, len(target) - 1
      while len(mapping) < l:
          if source[i] == target[j]:
              mapping.append((i, j))
              i, j = i - 1, j - 1
          elif c[i + 1, j] > c[i, j + 1]:
              j = j - 1
          else:
              i = i - 1
      return l, mapping[::-1]
  
  
  class WebServing(object):
      """简单的Web接口
      用法：
          arguments = {'text': (None, True), 'n': (int, False)}
          web = WebServing(port=8864)
          web.route('/gen_synonyms', gen_synonyms, arguments)
          web.start()
          # 然后访问 http://127.0.0.1:8864/gen_synonyms?text=你好
      说明：
          基于bottlepy简单封装，仅作为临时测试使用，不保证性能。
          目前仅保证支持 Tensorflow 1.x + Keras <= 2.3.1。
          欢迎有经验的开发者帮忙改进。
      依赖：
          pip install bottle
          pip install paste
          （如果不用 server='paste' 的话，可以不装paste库）
      """
      def __init__(self, host='0.0.0.0', port=8000, server='paste'):
  
          import bottle
  
          self.host = host
          self.port = port
          self.server = server
          self.graph = tf.get_default_graph()
          self.sess = K.get_session()
          self.set_session = K.set_session
          self.bottle = bottle
  
      def wraps(self, func, arguments, method='GET'):
          """封装为接口函数
          参数：
              func：要转换为接口的函数，需要保证输出可以json化，即需要
                    保证 json.dumps(func(inputs)) 能被执行成功；
              arguments：声明func所需参数，其中key为参数名，value[0]为
                         对应的转换函数（接口获取到的参数值都是字符串
                         型），value[1]为该参数是否必须；
              method：GET或者POST。
          """
          def new_func():
              outputs = {'code': 0, 'desc': u'succeeded', 'data': {}}
              kwargs = {}
              for key, value in arguments.items():
                  if method == 'GET':
                      result = self.bottle.request.GET.getunicode(key)
                  else:
                      result = self.bottle.request.POST.getunicode(key)
                  if result is None:
                      if value[1]:
                          outputs['code'] = 1
                          outputs['desc'] = 'lack of "%s" argument' % key
                          return json.dumps(outputs, ensure_ascii=False)
                  else:
                      if value[0] is not None:
                          result = value[0](result)
                      kwargs[key] = result
              try:
                  with self.graph.as_default():
                      self.set_session(self.sess)
                      outputs['data'] = func(**kwargs)
              except Exception as e:
                  outputs['code'] = 2
                  outputs['desc'] = str(e)
              return json.dumps(outputs, ensure_ascii=False)
  
          return new_func
  
      def route(self, path, func, arguments, method='GET'):
          """添加接口
          """
          func = self.wraps(func, arguments, method)
          self.bottle.route(path, method=method)(func)
  
      def start(self):
          """启动服务
          """
          self.bottle.run(host=self.host, port=self.port, server=self.server)
  
  
  class Hook:
      """注入uniout模块，实现import时才触发
      """
      def __init__(self, module):
          self.module = module
  
      def __getattr__(self, attr):
          """使得 from bert4keras.backend import uniout
          等效于 import uniout （自动识别Python版本，Python3
          下则无操作。）
          """
          if attr == 'uniout':
              if is_py2:
                  import uniout
          else:
              return getattr(self.module, attr)
  
  
  Hook.__name__ = __name__
  sys.modules[__name__] = Hook(sys.modules[__name__])
  del Hook
  
apis: numpy.roll numpy.cumsum numpy.empty numpy.zeros numpy.concatenate numpy.repeat numpy.argmax numpy.exp numpy.take_along_axis numpy.arange numpy.power numpy.shape numpy.log numpy.apply_along_axis numpy.array numpy.pad