NumPy 子类化 ndarray

2021-09-01 15:09 更新

介绍

对 ndarray 进行子类化相对简单,但与其他 Python 对象相比,它有一些复杂性。在此页面上,我们解释了允许您对 ndarray 进行子类化的机制,以及实现子类的含义。

ndarrays 和对象创建

由于 ndarray 类的新实例可以通过三种不同的方式产生,因此子类化 ndarray 很复杂。这些是:

  1. 显式构造函数调用 - 如MySubClass(params). 这是创建 Python 实例的常用方法。
  2. 视图转换 - 将现有的 ndarray 转换为给定的子类
  3. New from template - 从模板实例创建一个新实例。示例包括从子类数组返回切片、从 ufunc 创建返回类型以及复制数组。有关更多详细信息,请参阅 从模板创建新内容

最后两个是 ndarrays 的特性 - 为了支持数组切片之类的东西。子类化 ndarray 的复杂性是由于 numpy 必须支持后两种实例创建路径的机制。

查看投射

视图转换是标准的 ndarray 机制,您可以通过它获取任何子类的 ndarray,并将数组的视图作为另一个(指定的)子类返回:

  1. >>> import numpy as np
  2. >>> # create a completely useless ndarray subclass
  3. >>> class C(np.ndarray): pass
  4. >>> # create a standard ndarray
  5. >>> arr = np.zeros((3,))
  6. >>> # take a view of it, as our useless subclass
  7. >>> c_arr = arr.view(C)
  8. >>> type(c_arr)
  9. <class 'C'>

从模板创建新

ndarray 子类的新实例也可以通过与View cast非常相似的机制产生,当 numpy 发现它需要从模板实例创建一个新实例时。这必须发生的最明显的地方是当您获取子类数组的切片时。例如:

  1. >>> v = c_arr[1:]
  2. >>> type(v) # the view is of type 'C'
  3. <class 'C'>
  4. >>> v is c_arr # but it's a new instance
  5. False

切片是原始数据的视图c_arr。因此,当我们从 ndarray 中查看时,我们返回一个新的 ndarray,属于同一类,指向原始数据。

使用 ndarrays 的其他点我们需要这样的视图,例如复制数组 ( c_arr.copy())、创建 ufunc 输出数组(另请参阅__array_wrap__ 以了解 ufuncs 和其他函数)和减少方法(如 c_arr.mean())。

视图转换和新模板的关系

这些路径都使用相同的机器。我们在这里进行区分,因为它们会导致对您的方法的不同输入。具体来说, 视图转换意味着您已经从 ndarray 的任何潜在子类创建了数组类型的新实例。 从模板 创建新实例意味着您已经从预先存在的实例创建了类的新实例,例如,允许您跨子类特定的属性进行复制。

子类化的含义

如果我们继承 ndarray,我们不仅需要处理数组类型的显式构造,还需要处理视图转换或 从模板创建新的。NumPy 具有执行此操作的机制,正是这种机制使子类化略微非标准。

ndarray 用于支持子类中的视图和新模板的机制有两个方面。

首先是使用该ndarray.__new__方法进行对象初始化的主要工作,而不是更常用的__init__ 方法。第二个是使用该__array_finalize__方法允许子类在从模板创建视图和新实例后进行清理。

关于__new__和的简短 Python 入门__init__

__new__是一个标准的 Python 方法,如果存在,__init__在我们创建类实例之前调用。有关更多详细信息,请参阅python new 文档。

例如,考虑以下 Python 代码:

  1. class C:
  2. def __new__(cls, *args):
  3. print('Cls in __new__:', cls)
  4. print('Args in __new__:', args)
  5. # The `object` type __new__ method takes a single argument.
  6. return object.__new__(cls)
  7. def __init__(self, *args):
  8. print('type(self) in __init__:', type(self))
  9. print('Args in __init__:', args)

这意味着我们得到:

  1. >>> c = C('hello')
  2. Cls in __new__: <class 'C'>
  3. Args in __new__: ('hello',)
  4. type(self) in __init__: <class 'C'>
  5. Args in __init__: ('hello',)

当我们调用 时C('hello'),该__new__方法获取自己的类作为第一个参数,以及传递的参数,即字符串 'hello'。在 python 调用之后__new__,它通常(见下文)调用我​​们的__init__方法,将 的输出__new__作为第一个参数(现在是一个类实例),然后是传递的参数。

可以看到,对象可以在__new__ 方法中初始化,也可以在方法中初始化__init__,或者两者都可以,而实际上ndarray是没有__init__方法的,因为所有的初始化都是在__new__方法中完成的。 为什么要使用__new__而不仅仅是通常的__init__?因为在某些情况下,对于 ndarray,我们希望能够返回某个其他类的对象。考虑以下:

  1. class D(C):
  2. def __new__(cls, *args):
  3. print('D cls is:', cls)
  4. print('D args in __new__:', args)
  5. return C.__new__(C, *args)
  6. def __init__(self, *args):
  7. # we never get here
  8. print('In D __init__')

意思是:

  1. >>> obj = D('hello')
  2. D cls is: <class 'D'>
  3. D args in __new__: ('hello',)
  4. Cls in __new__: <class 'C'>
  5. Args in __new__: ('hello',)
  6. >>> type(obj)
  7. <class 'C'>

的定义C与之前相同,但对于D,该 __new__方法返回类的实例C而不是 D。请注意,不会调用的__init__方法D。通常,当该__new__方法返回定义它的类以外的类的对象时,__init__ 不会调用该类的方法。 这就是 ndarray 类的子类如何能够返回保留类类型的视图。在查看时,标准的 ndarray 机制会使用以下内容创建新的 ndarray 对象:

  1. obj = ndarray.__new__(subtype, shape, ...

subdtype子类在哪里。因此,返回的视图与子类属于同一类,而不是属于 class ndarray。 这解决了返回相同类型视图的问题,但现在我们有一个新问题。ndarray 的机制可以在其用于获取视图的标准方法中以这种方式设置类,但是 ndarray __new__方法对我们在自己的__new__方法中为设置属性所做的一切一无所知 ,等等。(另外 - 为什么不调用呢?因为我们可能没有具有相同调用签名的方法)。obj = subdtype.__new__(...``__new__

__array_finalize__的作用

__array_finalize__ 是 numpy 提供的机制,允许子类处理创建新实例的各种方式。

请记住,子类实例可以通过以下三种方式产生:

  1. 显式构造函数调用 ( )。这将调用then (如果存在) 的通常序列。obj = MySubClass(params)``MySubClass.__new__``MySubClass.__init__
  2. 查看演员表
  3. 从模板创建新

我们的MySubClass.__new__方法只在显式构造函数调用的情况下被调用,所以我们不能依赖MySubClass.__new__或 MySubClass.__init__处理视图转换和新模板。事实证明,MySubClass.__array_finalize__ 不被调用对象创建的所有三种方法,所以这是我们的对象创建看家一般无二。

  • 对于显式构造函数调用,我们的子类需要创建它自己的类的新 ndarray 实例。在实践中,这意味着我们,代码的作者,需要调用 ndarray.__new__(MySubClass,...),类层次结构准备调用 ,或者对现有数组进行视图转换(见下文)super().__new__(cls, ...)
  • 对于视图转换和新模板ndarray.__new__(MySubClass,...,在 C 级别调用等效项 。

__array_finalize__上述三种实例创建方法接收的参数不同。 以下代码允许我们查看调用序列和参数:

  1. import numpy as np
  2. class C(np.ndarray):
  3. def __new__(cls, *args, **kwargs):
  4. print('In __new__ with class %s' % cls)
  5. return super().__new__(cls, *args, **kwargs)
  6. def __init__(self, *args, **kwargs):
  7. # in practice you probably will not need or want an __init__
  8. # method for your subclass
  9. print('In __init__ with class %s' % self.__class__)
  10. def __array_finalize__(self, obj):
  11. print('In array_finalize:')
  12. print(' self type is %s' % type(self))
  13. print(' obj type is %s' % type(obj))

现在:

  1. >>> # Explicit constructor
  2. >>> c = C((10,))
  3. In __new__ with class <class 'C'>
  4. In array_finalize:
  5. self type is <class 'C'>
  6. obj type is <type 'NoneType'>
  7. In __init__ with class <class 'C'>
  8. >>> # View casting
  9. >>> a = np.arange(10)
  10. >>> cast_a = a.view(C)
  11. In array_finalize:
  12. self type is <class 'C'>
  13. obj type is <type 'numpy.ndarray'>
  14. >>> # Slicing (example of new-from-template)
  15. >>> cv = c[:1]
  16. In array_finalize:
  17. self type is <class 'C'>
  18. obj type is <class 'C'>

的签名__array_finalize__是:

  1. def __array_finalize__(self, obj):

可以看到,到 的super调用 ndarray.__new__传递__array_finalize__了我们自己的类 ( self) 以及从中获取视图的对象( )的新对象obj。从上面的输出可以看出,self总是我们子类的一个新创建的实例,obj 三种实例创建方法的类型不同:

  • 当从显式构造函数调用时,objNone
  • 当从视图转换调用时,obj可以是 ndarray 的任何子类的实例,包括我们自己的。
  • 当在 new-from-template 中调用时,obj是我们自己子类的另一个实例,我们可能会用它来更新新self实例。

因为__array_finalize__它是唯一能始终看到新实例被创建的方法,所以它是为新对象属性填充实例默认值以及其他任务的明智之选。 举个例子可能更清楚。

简单示例 - 向 ndarray 添加额外属性

  1. import numpy as np
  2. class InfoArray(np.ndarray):
  3. def __new__(subtype, shape, dtype=float, buffer=None, offset=0,
  4. strides=None, order=None, info=None):
  5. # Create the ndarray instance of our type, given the usual
  6. # ndarray input arguments. This will call the standard
  7. # ndarray constructor, but return an object of our type.
  8. # It also triggers a call to InfoArray.__array_finalize__
  9. obj = super().__new__(subtype, shape, dtype,
  10. buffer, offset, strides, order)
  11. # set the new 'info' attribute to the value passed
  12. obj.info = info
  13. # Finally, we must return the newly created object:
  14. return obj
  15. def __array_finalize__(self, obj):
  16. # ``self`` is a new object resulting from
  17. # ndarray.__new__(InfoArray, ...), therefore it only has
  18. # attributes that the ndarray.__new__ constructor gave it -
  19. # i.e. those of a standard ndarray.
  20. #
  21. # We could have got to the ndarray.__new__ call in 3 ways:
  22. # From an explicit constructor - e.g. InfoArray():
  23. # obj is None
  24. # (we're in the middle of the InfoArray.__new__
  25. # constructor, and self.info will be set when we return to
  26. # InfoArray.__new__)
  27. if obj is None: return
  28. # From view casting - e.g arr.view(InfoArray):
  29. # obj is arr
  30. # (type(obj) can be InfoArray)
  31. # From new-from-template - e.g infoarr[:3]
  32. # type(obj) is InfoArray
  33. #
  34. # Note that it is here, rather than in the __new__ method,
  35. # that we set the default value for 'info', because this
  36. # method sees all creation of default objects - with the
  37. # InfoArray.__new__ constructor, but also with
  38. # arr.view(InfoArray).
  39. self.info = getattr(obj, 'info', None)
  40. # We do not need to return anything

使用对象如下所示:

  1. >>> obj = InfoArray(shape=(3,)) # explicit constructor
  2. >>> type(obj)
  3. <class 'InfoArray'>
  4. >>> obj.info is None
  5. True
  6. >>> obj = InfoArray(shape=(3,), info='information')
  7. >>> obj.info
  8. 'information'
  9. >>> v = obj[1:] # new-from-template - here - slicing
  10. >>> type(v)
  11. <class 'InfoArray'>
  12. >>> v.info
  13. 'information'
  14. >>> arr = np.arange(10)
  15. >>> cast_arr = arr.view(InfoArray) # view casting
  16. >>> type(cast_arr)
  17. <class 'InfoArray'>
  18. >>> cast_arr.info is None
  19. True

这个类不是很有用,因为它具有与裸 ndarray 对象相同的构造函数,包括传入缓冲区和形状等。我们可能更希望构造函数能够从通常的 numpy 调用中获取一个已经形成的 ndarraynp.array并返回一个对象。

稍微更现实的例子 - 添加到现有数组的属性

这是一个类,它采用已经存在的标准 ndarray,转换为我们的类型,并添加了一个额外的属性。

  1. import numpy as np
  2. class RealisticInfoArray(np.ndarray):
  3. def __new__(cls, input_array, info=None):
  4. # Input array is an already formed ndarray instance
  5. # We first cast to be our class type
  6. obj = np.asarray(input_array).view(cls)
  7. # add the new attribute to the created instance
  8. obj.info = info
  9. # Finally, we must return the newly created object:
  10. return obj
  11. def __array_finalize__(self, obj):
  12. # see InfoArray.__array_finalize__ for comments
  13. if obj is None: return
  14. self.info = getattr(obj, 'info', None)

所以:

  1. >>> arr = np.arange(5)
  2. >>> obj = RealisticInfoArray(arr, info='information')
  3. >>> type(obj)
  4. <class 'RealisticInfoArray'>
  5. >>> obj.info
  6. 'information'
  7. >>> v = obj[1:]
  8. >>> type(v)
  9. <class 'RealisticInfoArray'>
  10. >>> v.info
  11. 'information'

3 __array_ufunc__对于 ufunc 

1.13 版中的新功能。

子类可以通过覆盖默认ndarray.__array_ufunc__方法来覆盖在其上执行 numpy ufuncs 时发生的情况。执行此方法而不是ufunc 并且应该返回操作的结果,或者NotImplemented如果请求的操作没有实现。

的签名__array_ufunc__是:

  1. def __array_ufunc__(ufunc, method, *inputs, **kwargs):
  2. - *ufunc* is the ufunc object that was called.
  3. - *method* is a string indicating how the Ufunc was called, either
  4. ``"__call__"`` to indicate it was called directly, or one of its
  5. :ref:`methods<ufuncs.methods>`: ``"reduce"``, ``"accumulate"``,
  6. ``"reduceat"``, ``"outer"``, or ``"at"``.
  7. - *inputs* is a tuple of the input arguments to the ``ufunc``
  8. - *kwargs* contains any optional or keyword arguments passed to the
  9. function. This includes any ``out`` arguments, which are always
  10. contained in a tuple.

典型的实现将转换作为自己类实例的任何输入或输出,使用 将所有内容传递给超类 super(),最后在可能的反向转换后返回结果。举例来说,来自测试案例采取 test_ufunc_override_with_supercore/tests/test_umath.py,如下。

  1. import numpy as np
  2. class A(np.ndarray):
  3. def __array_ufunc__(self, ufunc, method, *inputs, out=None, **kwargs):
  4. args = []
  5. in_no = []
  6. for i, input_ in enumerate(inputs):
  7. if isinstance(input_, A):
  8. in_no.append(i)
  9. args.append(input_.view(np.ndarray))
  10. else:
  11. args.append(input_)
  12. outputs = out
  13. out_no = []
  14. if outputs:
  15. out_args = []
  16. for j, output in enumerate(outputs):
  17. if isinstance(output, A):
  18. out_no.append(j)
  19. out_args.append(output.view(np.ndarray))
  20. else:
  21. out_args.append(output)
  22. kwargs['out'] = tuple(out_args)
  23. else:
  24. outputs = (None,) * ufunc.nout
  25. info = {}
  26. if in_no:
  27. info['inputs'] = in_no
  28. if out_no:
  29. info['outputs'] = out_no
  30. results = super().__array_ufunc__(ufunc, method, *args, **kwargs)
  31. if results is NotImplemented:
  32. return NotImplemented
  33. if method == 'at':
  34. if isinstance(inputs[0], A):
  35. inputs[0].info = info
  36. return
  37. if ufunc.nout == 1:
  38. results = (results,)
  39. results = tuple((np.asarray(result).view(A)
  40. if output is None else output)
  41. for result, output in zip(results, outputs))
  42. if results and isinstance(results[0], A):
  43. results[0].info = info
  44. return results[0] if len(results) == 1 else results

所以,这个类实际上并没有做任何有趣的事情:它只是将它自己的任何实例转换为常规 ndarray(否则,我们会得到无限递归!),并添加一个info字典,告诉它转换了哪些输入和输出。因此,例如,

  1. >>> a = np.arange(5.).view(A)
  2. >>> b = np.sin(a)
  3. >>> b.info
  4. {'inputs': [0]}
  5. >>> b = np.sin(np.arange(5.), out=(a,))
  6. >>> b.info
  7. {'outputs': [0]}
  8. >>> a = np.arange(5.).view(A)
  9. >>> b = np.ones(1).view(A)
  10. >>> c = a + b
  11. >>> c.info
  12. {'inputs': [0, 1]}
  13. >>> a += b
  14. >>> a.info
  15. {'inputs': [0, 1], 'outputs': [0]}

请注意,另一种方法是使用而不是调用。对于此示例,结果将是相同的,但如果另一个操作数也定义了 ,则会有所不同。例如,让我们假设我们评估 ,其中是另一个具有覆盖的类的实例。如果您在示例中使用as , 会注意到它具有覆盖,这意味着它无法评估结果本身。因此,它将返回NotImplemented,我们的 class也将返回 。然后,控制将传递给,它要么知道如何处理我们并产生结果,要么不知道并返回NotImplemented,从而引发.getattr(ufunc, methods)(*inputs, **kwargs)``super``__array_ufunc__``np.add(a, b)``b``B``super``ndarray.__array_ufunc__``b``A``b``TypeError

相反,如果我们用 替换我们的super电话,我们就有效地做到了。同样, 将被调用,但现在它将 an视为另一个参数。很可能,它会知道如何处理这个问题,并将该类的一个新实例返回给我们。我们的示例类没有设置来处理这个问题,但如果,例如,要使用 重新实现,它可能是最好的方法。getattr(ufunc, method)``np.add(a.view(np.ndarray), b)``B.__array_ufunc__``ndarray``B``MaskedArray``__array_ufunc__

最后要注意的是:如果super路由适合给定的类,使用它的好处是它有助于构建类层次结构。例如,假设我们的另一个类在其 实现中B也使用了,并且我们创建了一个依赖于两者的类,即(为了简单起见,没有另一个 覆盖)。然后,实例上的任何 ufunc将传递给,调用将转到 ,调用将转到 ,从而允许并进行协作。super``__array_ufunc__``C``class C(A, B)``__array_ufunc__``C``A.__array_ufunc__``super``A``B.__array_ufunc__``super``B``ndarray.__array_ufunc__``A``B

__array_wrap__对于 ufuncs 和其他函数

在 numpy 1.13 之前,ufunc 的行为只能使用__array_wrap__和进行调整 __array_prepare__。这两个允许更改 ufunc 的输出类型,但与 相比 __array_ufunc__,不允许对输入进行任何更改。希望最终弃用这些,但__array_wrap__也被其他 numpy 函数和方法使用,例如squeeze,因此目前仍需要完整功能。

从概念上讲,__array_wrap__“包装动作”是指允许子类设置返回值的类型并更新属性和元数据。让我们用一个例子来展示它是如何工作的。首先我们回到更简单的示例子类,但使用不同的名称和一些打印语句:

  1. import numpy as np
  2. class MySubClass(np.ndarray):
  3. def __new__(cls, input_array, info=None):
  4. obj = np.asarray(input_array).view(cls)
  5. obj.info = info
  6. return obj
  7. def __array_finalize__(self, obj):
  8. print('In __array_finalize__:')
  9. print(' self is %s' % repr(self))
  10. print(' obj is %s' % repr(obj))
  11. if obj is None: return
  12. self.info = getattr(obj, 'info', None)
  13. def __array_wrap__(self, out_arr, context=None):
  14. print('In __array_wrap__:')
  15. print(' self is %s' % repr(self))
  16. print(' arr is %s' % repr(out_arr))
  17. # then just call the parent
  18. return super().__array_wrap__(self, out_arr, context)

我们在新数组的一个实例上运行一个 ufunc:

  1. >>> obj = MySubClass(np.arange(5), info='spam')
  2. In __array_finalize__:
  3. self is MySubClass([0, 1, 2, 3, 4])
  4. obj is array([0, 1, 2, 3, 4])
  5. >>> arr2 = np.arange(5)+1
  6. >>> ret = np.add(arr2, obj)
  7. In __array_wrap__:
  8. self is MySubClass([0, 1, 2, 3, 4])
  9. arr is array([1, 3, 5, 7, 9])
  10. In __array_finalize__:
  11. self is MySubClass([1, 3, 5, 7, 9])
  12. obj is MySubClass([0, 1, 2, 3, 4])
  13. >>> ret
  14. MySubClass([1, 3, 5, 7, 9])
  15. >>> ret.info
  16. 'spam'

请注意, ufunc ( np.add) 调用了__array_wrap__带有参数selfas的方法obj,以及out_arr作为加法的 (ndarray) 结果。反过来,默认__array_wrap__ ( ndarray.__array_wrap__) 已将结果强制转换为 class MySubClass,并调用__array_finalize__- 因此复制info 属性。这一切都发生在 C 级。

但是,我们可以做任何我们想做的事情:

  1. class SillySubClass(np.ndarray):
  2. def __array_wrap__(self, arr, context=None):
  3. return 'I lost your data'
  1. >>> arr1 = np.arange(5)
  2. >>> obj = arr1.view(SillySubClass)
  3. >>> arr2 = np.arange(5)
  4. >>> ret = np.multiply(obj, arr2)
  5. >>> ret
  6. 'I lost your data'

因此,通过__array_wrap__为我们的子类定义一个特定的方法,我们可以调整 ufuncs 的输出。该__array_wrap__方法需要self,然后是一个参数——它是 ufunc 的结果——和一个可选的参数上下文。此参数由 ufuncs 作为 3 元素元组返回:(ufunc 的名称,ufunc 的参数,ufunc 的域),但不由其他 numpy 函数设置。虽然,如上所见,有可能以其他方式执行,但__array_wrap__应返回其包含类的实例。有关实现,请参阅掩码数组子类。

除了__array_wrap__在退出 ufunc 的途中调用的 之外,还有一个__array_prepare__方法在进入 ufunc 的途中调用,在创建输出数组之后但在执行任何计算之前。默认实现除了传递数组之外什么都不做。__array_prepare__不应尝试访问数组数据或调整数组大小,它旨在设置输出数组类型、更新属性和元数据,以及在计算开始之前根据可能需要的输入执行任何检查。像__array_wrap__,__array_prepare__必须返回一个 ndarray 或其子类或引发错误。

额外的问题 - 自定义__del__方法和 ndarray.base 

ndarray 解决的问题之一是跟踪 ndarray 及其视图的内存所有权。考虑我们创建了一个 ndarrayarr并使用. 这两个对象正在查看相同的内存。NumPy 使用以下属性跟踪特定数组或视图的数据来自何处 :v = arr[1:]``base

  1. >>> # A normal ndarray, that owns its own data
  2. >>> arr = np.zeros((4,))
  3. >>> # In this case, base is None
  4. >>> arr.base is None
  5. True
  6. >>> # We take a view
  7. >>> v1 = arr[1:]
  8. >>> # base now points to the array that it derived from
  9. >>> v1.base is arr
  10. True
  11. >>> # Take a view of a view
  12. >>> v2 = v1[1:]
  13. >>> # base points to the original array that it was derived from
  14. >>> v2.base is arr
  15. True

一般来说,如果数组拥有自己的内存,就arr在这种情况下,那么arr.base将是 None - 有一些例外 - 有关更多详细信息,请参阅 numpy book。

base属性有助于判断我们是否拥有视图或原始数组。如果我们需要知道在删除子类数组时是否进行一些特定的清理,这反过来会很有用。例如,如果原始数组被删除,我们可能只想做清理,而不是视图。对于如何能工作的例子,看看在memmap上课 numpy.core

子类化和下游兼容性

当子类化ndarray或创建模仿ndarray 接口的鸭子类型时,您有责任决定您的 API 与 numpy 的 API 的对齐程度。为了方便起见,具有相应的许多numpy的功能 ndarray的方法(例如,summeantakereshape)通过检查第一个参数的函数具有相同的名称的方法工作。如果存在,则调用该方法,而不是将参数强制转换为 numpy 数组。

例如,如果您希望您的子类或鸭子类型与 numpy 的sum函数兼容,则该对象的sum方法的方法签名应如下所示:

  1. def sum(self, axis=None, dtype=None, out=None, keepdims=False):
  2. ...

这是完全相同的方法签名np.sum,所以现在如果用户调用 np.sum这个对象,numpy将调用对象自己的sum方法并在签名中传递上面枚举的这些参数,并且不会引发错误,因为签名完全兼容彼此。

但是,如果您决定偏离此签名并执行以下操作:

  1. def sum(self, axis=None, dtype=None):
  2. ...

此对象不再与 兼容,np.sum因为如果您调用np.sum,它将传入意外的参数outkeepdims,从而引发 TypeError 。

如果您希望保持与 numpy 及其后续版本(可能会添加新的关键字参数)的兼容性,但又不想显示 numpy 的所有参数,则您的函数签名应接受**kwargs. 例如:

  1. def sum(self, axis=None, dtype=None, **unused_kwargs):
  2. ...

这个对象现在np.sum再次兼容,因为任何无关的参数(即不是axis或 的关键字dtype)将隐藏在 **unused_kwargs参数中。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号