专栏名称: 老齐Py
Data Science
目录
相关文章推荐
槽值  ·  网易沸点工作室多岗位实习生招聘中 ·  3 天前  
短线魔方君  ·  3.14 ... ·  3 天前  
短线魔方君  ·  3.14 ... ·  3 天前  
51好读  ›  专栏  ›  老齐Py

【译】Python如何实现重载函数

老齐Py  · 掘金  ·  · 2020-02-26 02:50

正文

阅读 17

【译】Python如何实现重载函数

作者:Arpit

翻译:老齐


重载函数,即多个函数具有相同的名称,但功能不同。例如一个重载函数 fn ,调用它的时候,要根据传给函数的参数判断调用哪个函数,并且执行相应的功能。

int area(int length, int breadth) {
  return length * breadth;
}

float area(int radius) {
  return 3.14 * radius * radius;
}
复制代码

上例是用C++写的代码,函数 area 就是有两个不同功能的重载函数,一个是根据参数length和breadth计算矩形的面积,另一个是根据参数radius(圆的半径)计算圆的面积。如果用 area(7) 的方式调用函数 area ,就会实现第二个函数功能,当 area(3, 4) 时调用的是第一个函数。

为什么Python中没有重载函数

Python中本没有重载函数,如果我们在同一命名空间中定义的多个函数是同名的,最后一个将覆盖前面的各函数,也就是函数的名称只能是唯一的。通过执行 locals() globals() 两个函数,就能看到该命名空间中已经存在的函数。

def area(radius):
  return 3.14 * radius ** 2

>>> locals()
{
  ...
  'area': <function area at 0x10476a440>,
  ...
}
复制代码

定义了一个函数之后,执行 locals() 函数,返回了一个字典,其中是本地命名空间中所定义所有变量,键是变量,值则是它的引用。如果有另外一个同名函数,就会将本地命名空间的内容进行更新,不会有两个同名函数共存。所以,Python不支持重载函数,这是发明这个语言的设计理念,但是这并不能阻挡我们不能实现重载函数。下面就做一个试试。

在Python中实现重载函数

我们应该知道Python怎么管理命名空间,如果我们要实现重载函数,必须:

  • 在稳定的虚拟命名空间管理所定义的函数
  • 根据参数调用合适的函数

为了简化问题,我们将实现具有相同名称的重载函数,它们的区别就是参数的个数。

封装函数

创建一个名为 Function 的类,并重写实现调用的 __call__ 方法,再写一个名为 key 的方法,它会返回一个元组,这样让就使得此方法区别于其他方法。

from inspect import getfullargspec

class Function:
  """Function is a wrap over standard python function.
  """
  def __init__(self, fn):
    self.fn = fn

  def __call__(self, *args, **kwargs):
    """when invoked like a function it internally invokes
    the wrapped function and returns the returned value.
    """
    return self.fn(*args, **kwargs)

  def key(self, args=None):
    """Returns the key that will uniquely identify
    a function (even when it is overloaded).
    """
    # if args not specified, extract the arguments from the
    # function definition
    if args is None:
      args = getfullargspec(self.fn).args

    return tuple([
      self.fn.__module__,
      self.fn.__class__,
      self.fn.__name__,
      len(args or []),
    ])

复制代码

在上面的代码片段中, key 方法返回了一个元组,其中的元素包括:

  • 函数所属的模块
  • 函数所属的类
  • 函数名称
  • 函数的参数长度

在重写的 __call__ 方法中调用作为参数的函数,并返回计算结果。这样,实例就如同函数一样调用,它的表现效果与作为参数的函数一样。

def area(l, b):
  return l * b

>>> func = Function(area)
>>> func.key()
('__main__', <class 'function'>, 'area', 2)
>>> func(3, 4)
12
复制代码

在上面的举例中,函数 area 作为 Function 实例化的参数, key() 返回的元组中,第一个元素是模块的名称 __main__ ,第二个是类 <class 'function'> ,第三个是函数的名字 area ,第四个则是此函数的参数个数 2

从上面的示例中,还可以看出,调用实例 func 的方式,就和调用 area 函数一样,提供参数 3 4 ,就返回 12 ,前面调用 area(3, 4) 也是同样结果。这种方式,会在后面使用装饰器的时候很有用。

构建虚拟命名空间

我们所构建的虚拟命名空间,会保存所定义的所有函数。

class Namespace(object):
  """Namespace is the singleton class that is responsible
  for holding all the functions.
  """
  __instance = None

  def __init__(self):
    if self.__instance is None:
      self.function_map = dict()
      Namespace.__instance = self
    else:
      raise Exception("cannot instantiate a virtual Namespace again")

  @staticmethod
  def get_instance():
    if Namespace.__instance is None:
      Namespace()
    return Namespace.__instance

  def register(self, fn):
    """registers the function in the virtual namespace and returns
    an instance of callable Function that wraps the
    function fn.
    "






请到「今天看啥」查看全文