Spec-Zone.ru › NumPy 1.21

Подклассы ndarray

Введение

Наследование от ndarray относительно просто, но имеет некоторые сложности по сравнению с другими объектами Python. На этой странице мы объясним механизм, позволяющий создавать подклассы ndarray, и последствия для реализации подкласса.

ndarray и создание объектов

Наследование от ndarray усложняется тем, что новые экземпляры классов ndarray могут появляться тремя различными способами:

  1. Явное вызов конструктора — как в MySubClass(params). Это обычный способ создания экземпляров в Python.
  2. Преобразование представления — приведение существующего ndarray к заданному подклассу.
  3. Создание из шаблона — создание нового экземпляра на основе шаблона. Примеры включают возвращение срезов из подклассированного массива, создание типов возвращаемых значений от ufunc и копирование массивов. Более подробная информация в разделе Создание из шаблона.

Два последних способа являются специфичными для ndarray — для поддержки таких операций, как срезы массивов. Сложности при наследовании от ndarray связаны с механизмами NumPy для поддержки этих последних двух способов создания экземпляров.

Преобразование представления

Преобразование представления — это стандартный механизм ndarray, позволяющий взять ndarray любого подкласса и вернуть представление массива как другой (указанный) подкласс:

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

Создание из шаблона

Новые экземпляры подкласса ndarray также могут создаваться с помощью механизма, очень похожего на преобразование представления, когда NumPy необходимо создать новый экземпляр из существующего шаблона. Наиболее очевидным случаем является получение срезов из подклассированного массива. Например:

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

Срез является представлением исходных c_arr данных. Таким образом, при получении представления от ndarray мы возвращаем новый ndarray того же класса, который указывает на данные в оригинале.

Существуют и другие случаи использования ndarray, где нужны такие представления, такие как копирование массивов (c_arr.copy()), создание массивов вывода ufunc (см. также __array_wrap__ для ufunc и других функций) и методы сокращения (например, c_arr.mean()).

Взаимосвязь преобразования представления и создания из шаблона

Эти пути используют одинаковый механизм. Мы различаем их здесь, потому что они приводят к разным входным данным для ваших методов. В частности, преобразование представления означает, что вы создали новый экземпляр вашего типа массива из любого потенциального подкласса ndarray. Создание из шаблона означает, что вы создали новый экземпляр своего класса из существующего экземпляра, что позволяет, например, скопировать атрибуты, специфичные для вашего подкласса.

Последствия для наследования

Если мы наследуем от ndarray, нам нужно обрабатывать не только явное создание нашего типа массива, но и преобразование представления или создание из шаблона. NumPy имеет механизмы для этого, и именно этот механизм делает наследование немного нестандартным.

В механизме ndarray, поддерживающем представления и создание из шаблона для подклассов, есть два аспекта.

Первый — использование метода ndarray.__new__ для основной работы с инициализацией объекта, а не более распространённого метода __init__. Второй — использование метода __array_finalize__, позволяющего подклассам выполнять очистку после создания представлений и новых экземпляров из шаблонов.

Краткий справочник по Python для __new__ и __init__

__new__ — это стандартный метод Python, и, если он присутствует, вызывается перед __init__ при создании экземпляра класса. Более подробную информацию см. в документации Python по __new__.

Например, рассмотрим следующий фрагмент кода Python:

class C:
    def __new__(cls, *args):
        print('Cls in __new__:', cls)
        print('Args in __new__:', args)
        # The `object` type __new__ method takes a single argument.
        return object.__new__(cls)

    def __init__(self, *args):
        print('type(self) in __init__:', type(self))
        print('Args in __init__:', args)

что означает:

>>> c = C('hello')
Cls in __new__: <class 'C'>
Args in __new__: ('hello',)
type(self) in __init__: <class 'C'>
Args in __init__: ('hello',)

При вызове C('hello'), метод __new__ получает в качестве первого аргумента свой класс и переданный аргумент, который является строкой 'hello'. После того, как Python вызовет __new__, он обычно (см. ниже) вызовет наш метод __init__, с результатом работы __new__ в качестве первого аргумента (теперь это экземпляр класса), и передаваемые аргументы следуют за ним.

Как вы видите, объект может быть инициализирован в методе __new__ или методе __init__, или в обоих, и на самом деле ndarray не имеет метода __init__, потому что вся инициализация выполняется в методе __new__.

Почему используется __new__ вместо обычного __init__? Потому что в некоторых случаях, как в случае с ndarray, мы хотим иметь возможность возвращать объект другого класса. Рассмотрим следующий пример:

class D(C):
    def __new__(cls, *args):
        print('D cls is:', cls)
        print('D args in __new__:', args)
        return C.__new__(C, *args)

    def __init__(self, *args):
        # we never get here
        print('In D __init__')

что означает:

>>> obj = D('hello')
D cls is: <class 'D'>
D args in __new__: ('hello',)
Cls in __new__: <class 'C'>
Args in __new__: ('hello',)
>>> type(obj)
<class 'C'>

Определение C такое же, как и прежде, но для D, метод __new__ возвращает экземпляр класса C вместо D. Обратите внимание, что метод __init__ класса D не вызывается. В общем случае, когда метод __new__ возвращает объект класса, отличного от класса, в котором он определён, метод __init__ этого класса не вызывается.

Вот как подклассы класса ndarray могут возвращать представления, сохраняющие тип класса. При получении представления стандартный механизм ndarray создаёт новый объект ndarray с чем-то вроде:

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

где subdtype — это подкласс. Таким образом, возвращаемое представление имеет тот же класс, что и подкласс, а не класс ndarray.

Это решает проблему возвращения представлений того же типа, но теперь у нас есть новая проблема. Механизм ndarray может установить класс таким образом в своих стандартных методах получения представлений, но метод ndarray __new__ ничего не знает о том, что мы сделали в нашем собственном методе __new__ для установки атрибутов и так далее. (Отступление — почему не вызвать obj = subdtype.__new__(...? Потому что у нас может не быть метода __new__ с такой же сигнатурой вызова).

Роль __array_finalize__

__array_finalize__ — это механизм, предоставляемый NumPy, позволяющий подклассам обрабатывать различные способы создания новых экземпляров.

Помните, что экземпляры подклассов могут быть созданы тремя способами:

  1. Явный вызов конструктора (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__, различаются для трёх способов создания экземпляров, описанных выше.

Следующий код позволяет взглянуть на последовательности вызовов и аргументы:

import numpy as np

class C(np.ndarray):
    def __new__(cls, *args, **kwargs):
        print('In __new__ with class %s' % cls)
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        # in practice you probably will not need or want an __init__
        # method for your subclass
        print('In __init__ with class %s' % self.__class__)

    def __array_finalize__(self, obj):
        print('In array_finalize:')
        print('   self type is %s' % type(self))
        print('   obj type is %s' % type(obj))

Теперь:

>>> # Explicit constructor
>>> c = C((10,))
In __new__ with class <class 'C'>
In array_finalize:
   self type is <class 'C'>
   obj type is <type 'NoneType'>
In __init__ with class <class 'C'>
>>> # View casting
>>> a = np.arange(10)
>>> cast_a = a.view(C)
In array_finalize:
   self type is <class 'C'>
   obj type is <type 'numpy.ndarray'>
>>> # Slicing (example of new-from-template)
>>> cv = c[:1]
In array_finalize:
   self type is <class 'C'>
   obj type is <class 'C'>

Подпись __array_finalize__:

def __array_finalize__(self, obj):

Видно, что вызов super, который переходит к ndarray.__new__, передаёт __array_finalize__ новый объект нашего собственного класса (self), а также объект, из которого было взято представление (obj). Как видно из вывода выше, self всегда является вновь созданным экземпляром нашего подкласса, а тип obj отличается для трёх способов создания экземпляров:

  • При вызове из явного конструктора, obj является None
  • При вызове из преобразования представления, obj может быть экземпляром любого подкласса ndarray, включая наш собственный.
  • При вызове при создании из шаблона, obj является другим экземпляром нашего собственного подкласса, который мы можем использовать для обновления нового экземпляра self.

Поскольку __array_finalize__ — единственный метод, который всегда видит создание новых экземпляров, он является разумным местом для заполнения значений по умолчанию атрибутов нового объекта, среди прочих задач.

Это может быть более понятно на примере.

Простой пример — добавление дополнительного атрибута к ndarray

import numpy as np

class InfoArray(np.ndarray):

    def __new__(subtype, shape, dtype=float, buffer=None, offset=0,
                strides=None, order=None, info=None):
        # Create the ndarray instance of our type, given the usual
        # ndarray input arguments.  This will call the standard
        # ndarray constructor, but return an object of our type.
        # It also triggers a call to InfoArray.__array_finalize__
        obj = super().__new__(subtype, shape, dtype,
                              buffer, offset, strides, order)
        # set the new 'info' attribute to the value passed
        obj.info = info
        # Finally, we must return the newly created object:
        return obj

    def __array_finalize__(self, obj):
        # ``self`` is a new object resulting from
        # ndarray.__new__(InfoArray, ...), therefore it only has
        # attributes that the ndarray.__new__ constructor gave it -
        # i.e. those of a standard ndarray.
        #
        # We could have got to the ndarray.__new__ call in 3 ways:
        # From an explicit constructor - e.g. InfoArray():
        #    obj is None
        #    (we're in the middle of the InfoArray.__new__
        #    constructor, and self.info will be set when we return to
        #    InfoArray.__new__)
        if obj is None: return
        # From view casting - e.g arr.view(InfoArray):
        #    obj is arr
        #    (type(obj) can be InfoArray)
        # From new-from-template - e.g infoarr[:3]
        #    type(obj) is InfoArray
        #
        # Note that it is here, rather than in the __new__ method,
        # that we set the default value for 'info', because this
        # method sees all creation of default objects - with the
        # InfoArray.__new__ constructor, but also with
        # arr.view(InfoArray).
        self.info = getattr(obj, 'info', None)
        # We do not need to return anything

Использование объекта выглядит следующим образом:

>>> obj = InfoArray(shape=(3,)) # explicit constructor
>>> type(obj)
<class 'InfoArray'>
>>> obj.info is None
True
>>> obj = InfoArray(shape=(3,), info='information')
>>> obj.info
'information'
>>> v = obj[1:] # new-from-template - here - slicing
>>> type(v)
<class 'InfoArray'>
>>> v.info
'information'
>>> arr = np.arange(10)
>>> cast_arr = arr.view(InfoArray) # view casting
>>> type(cast_arr)
<class 'InfoArray'>
>>> cast_arr.info is None
True

Этот класс не очень полезен, потому что у него такой же конструктор, как у простого объекта ndarray, включая передачу буферов, форм и так далее. Мы, вероятно, хотели бы, чтобы конструктор мог принимать уже сформированный ndarray из обычных вызовов NumPy к np.array и возвращать объект.

Немного более реалистичный пример — добавление атрибута к существующему массиву

Вот класс, который принимает стандартный существующий ndarray, преобразует его в наш тип и добавляет дополнительный атрибут.

import numpy as np

class RealisticInfoArray(np.ndarray):

    def __new__(cls, input_array, info=None):
        # Input array is an already formed ndarray instance
        # We first cast to be our class type
        obj = np.asarray(input_array).view(cls)
        # add the new attribute to the created instance
        obj.info = info
        # Finally, we must return the newly created object:
        return obj

    def __array_finalize__(self, obj):
        # see InfoArray.__array_finalize__ for comments
        if obj is None: return
        self.info = getattr(obj, 'info', None)

Таким образом:

>>> arr = np.arange(5)
>>> obj = RealisticInfoArray(arr, info='information')
>>> type(obj)
<class 'RealisticInfoArray'>
>>> obj.info
'information'
>>> v = obj[1:]
>>> type(v)
<class 'RealisticInfoArray'>
>>> v.info
'information'

__array_ufunc__ для ufunc

Введено в версии 1.13.

Подкласс может переопределить поведение при выполнении numpy ufunc, переопределив метод по умолчанию ndarray.__array_ufunc__. Этот метод выполняется вместо ufunc и должен возвращать результат операции или NotImplemented, если запрошенная операция не реализована.

Подпись метода __array_ufunc__:

def __array_ufunc__(ufunc, method, *inputs, **kwargs):

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

Типичная реализация будет преобразовывать любые входные или выходные данные, являющиеся экземплярами собственного класса, передавать все в суперкласс с помощью super() и, наконец, возвращать результаты после возможного преобразования обратно. Пример, взятый из тестового случая test_ufunc_override_with_super в core/tests/test_umath.py, выглядит следующим образом.

input numpy as np

class A(np.ndarray):
    def __array_ufunc__(self, ufunc, method, *inputs, out=None, **kwargs):
        args = []
        in_no = []
        for i, input_ in enumerate(inputs):
            if isinstance(input_, A):
                in_no.append(i)
                args.append(input_.view(np.ndarray))
            else:
                args.append(input_)

        outputs = out
        out_no = []
        if outputs:
            out_args = []
            for j, output in enumerate(outputs):
                if isinstance(output, A):
                    out_no.append(j)
                    out_args.append(output.view(np.ndarray))
                else:
                    out_args.append(output)
            kwargs['out'] = tuple(out_args)
        else:
            outputs = (None,) * ufunc.nout

        info = {}
        if in_no:
            info['inputs'] = in_no
        if out_no:
            info['outputs'] = out_no

        results = super().__array_ufunc__(ufunc, method, *args, **kwargs)
        if results is NotImplemented:
            return NotImplemented

        if method == 'at':
            if isinstance(inputs[0], A):
                inputs[0].info = info
            return

        if ufunc.nout == 1:
            results = (results,)

        results = tuple((np.asarray(result).view(A)
                         if output is None else output)
                        for result, output in zip(results, outputs))
        if results and isinstance(results[0], A):
            results[0].info = info

        return results[0] if len(results) == 1 else results

Таким образом, этот класс фактически ничего интересного не делает: он просто преобразует любые экземпляры своего класса в обычные ndarray (иначе получим бесконечную рекурсию!) и добавляет словарь info, который указывает, какие входные и выходные данные были преобразованы. Следовательно, например,

>>> a = np.arange(5.).view(A)
>>> b = np.sin(a)
>>> b.info
{'inputs': [0]}
>>> b = np.sin(np.arange(5.), out=(a,))
>>> b.info
{'outputs': [0]}
>>> a = np.arange(5.).view(A)
>>> b = np.ones(1).view(A)
>>> c = a + b
>>> c.info
{'inputs': [0, 1]}
>>> a += b
>>> a.info
{'inputs': [0, 1], 'outputs': [0]}

Обратите внимание, что другой подход заключался бы в использовании getattr(ufunc, methods)(*inputs, **kwargs) вместо вызова super. В этом примере результат был бы идентичным, но есть разница, если другой операнд также определяет __array_ufunc__. Например, предположим, что мы вычисляем np.add(a, b), где b — экземпляр другого класса B, у которого есть переопределение. Если вы используете super, как в примере, ndarray.__array_ufunc__ заметит, что b имеет переопределение, что означает, что он не может вычислить результат сам. Таким образом, он вернёт NotImplemented, и наш класс A также. Затем управление передаётся b, который либо знает, как с этим справиться и производит результат, либо нет и возвращает NotImplemented, вызвав исключение TypeError.

Если вместо этого мы заменим наш вызов super на getattr(ufunc, method), мы фактически сделаем np.add(a.view(np.ndarray), b). Опять же, B.__array_ufunc__ будет вызван, но теперь он увидит ndarray в качестве другого аргумента. Вероятно, он будет знать, как с этим справиться, и вернёт нам новый экземпляр класса B. Наш пример класса не настроен для работы с этим, но это может быть лучший подход, если, например, нужно было бы повторно реализовать MaskedArray с помощью __array_ufunc__.

Наконец, если способ super подходит для данного класса, преимущество его использования заключается в построении иерархии классов. Например, предположим, что наш другой класс B также использовал super в своей реализации __array_ufunc__, и мы создали класс C, который зависел от обоих, т. е., class C(A, B) (при этом, для простоты, нет другого переопределения __array_ufunc__). Тогда любой ufunc над экземпляром C передастся в A.__array_ufunc__, вызов super в A пойдёт в B.__array_ufunc__, а вызов super в B пойдёт в ndarray.__array_ufunc__, что позволит A и B сотрудничать.

__array_wrap__ для ufunc и других функций

До numpy 1.13 поведение ufunc можно было настроить только с помощью __array_wrap__ и __array_prepare__. Эти два параметра позволяли изменить тип результата ufunc, но, в отличие от __array_ufunc__, не позволяли вносить изменения в входные данные. Будет надежда, что эти параметры со временем устареют, но __array_wrap__ также используется другими функциями и методами numpy, такими как squeeze, поэтому на данный момент он всё ещё необходим для полной функциональности.

По сути, __array_wrap__ «оборачивает действие», позволяя подклассу установить тип возвращаемого значения и обновлять атрибуты и метаданные. Покажем, как это работает на примере. Сначала вернёмся к более простому подклассу, но с другим именем и некоторыми операциями вывода:

import numpy as np

class MySubClass(np.ndarray):

    def __new__(cls, input_array, info=None):
        obj = np.asarray(input_array).view(cls)
        obj.info = info
        return obj

    def __array_finalize__(self, obj):
        print('In __array_finalize__:')
        print('   self is %s' % repr(self))
        print('   obj is %s' % repr(obj))
        if obj is None: return
        self.info = getattr(obj, 'info', None)

    def __array_wrap__(self, out_arr, context=None):
        print('In __array_wrap__:')
        print('   self is %s' % repr(self))
        print('   arr is %s' % repr(out_arr))
        # then just call the parent
        return super().__array_wrap__(self, out_arr, context)

Выполним ufunc на экземпляре нашего нового массива:

>>> obj = MySubClass(np.arange(5), info='spam')
In __array_finalize__:
   self is MySubClass([0, 1, 2, 3, 4])
   obj is array([0, 1, 2, 3, 4])
>>> arr2 = np.arange(5)+1
>>> ret = np.add(arr2, obj)
In __array_wrap__:
   self is MySubClass([0, 1, 2, 3, 4])
   arr is array([1, 3, 5, 7, 9])
In __array_finalize__:
   self is MySubClass([1, 3, 5, 7, 9])
   obj is MySubClass([0, 1, 2, 3, 4])
>>> ret
MySubClass([1, 3, 5, 7, 9])
>>> ret.info
'spam'

Обратите внимание, что ufunc (np.add) вызвал метод __array_wrap__ с аргументами self как obj и out_arr как (ndarray) результатом сложения. В свою очередь, метод по умолчанию __array_wrap__ (ndarray.__array_wrap__) преобразовал результат в класс MySubClass и вызвал __array_finalize__ — отсюда копирование атрибута info. Всё это произошло на уровне C.

Но мы могли бы сделать всё, что захотим:

class SillySubClass(np.ndarray):

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

Таким образом, определив специфический метод __array_wrap__ для нашего подкласса, мы можем настроить выходные данные от ufunc. Метод __array_wrap__ требует self, затем аргумент — результат ufunc — и необязательный параметр context. Этот параметр возвращается ufunc как кортеж из 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 и их представлений. Рассмотрим случай, когда мы создали ndarray arr и взяли срез с помощью v = arr[1:]. Два объекта смотрят на одну и ту же память. NumPy отслеживает, откуда пришли данные для конкретного массива или представления, с помощью атрибута base:

>>> # A normal ndarray, that owns its own data
>>> arr = np.zeros((4,))
>>> # In this case, base is None
>>> arr.base is None
True
>>> # We take a view
>>> v1 = arr[1:]
>>> # base now points to the array that it derived from
>>> v1.base is arr
True
>>> # Take a view of a view
>>> v2 = v1[1:]
>>> # base points to the original array that it was derived from
>>> v2.base is arr
True

В общем случае, если массив владеет своей памятью, как в случае с arr, то arr.base будет None — есть некоторые исключения, см. книгу по numpy для более подробной информации.

Атрибут base полезен для определения, имеем ли мы представление или исходный массив. Это, в свою очередь, может быть полезно, если нам нужно знать, следует ли выполнять определённую очистку при удалении подклассированного массива. Например, мы можем очищать только тогда, когда удаляется исходный массив, а не представления. Чтобы увидеть, как это работает, взгляните на класс memmap в numpy.core.

Наследование и совместимость с последующими версиями

При наследовании от ndarray или создании псевдотипов, имитирующих интерфейс ndarray, вам необходимо решить, насколько сопоставимы ваши API будут с API numpy. Для удобства многие функции numpy, имеющие соответствующий метод ndarray (например, sum, mean, take, reshape) работают, проверяя, есть ли у первого аргумента функции метод с таким же именем. Если он существует, метод вызывается вместо приведения аргументов к массиву numpy.

Например, если вы хотите, чтобы ваш подкласс или псевдотип были совместимы с функцией numpy sum, сигнатура метода sum этого объекта должна быть следующей:

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

Это точно такая же сигнатура метода, что и для np.sum, поэтому теперь, если пользователь вызовет np.sum на этом объекте, numpy вызовет собственный метод sum объекта и передаст перечисленные выше аргументы в сигнатуре, и ошибки не возникнет, потому что сигнатуры полностью совместимы.

Однако, если вы решите отклониться от этой сигнатуры и сделать что-то вроде этого:

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

Этот объект больше не совместим с np.sum, так как если вы вызовете np.sum, он передаст неожиданные аргументы out и keepdims, что приведёт к ошибке TypeError.

Если вы хотите сохранить совместимость с numpy и его последующими версиями (которые могут добавить новые ключевые аргументы), но не хотите отображать все аргументы numpy, сигнатура вашей функции должна принимать **kwargs. Например:

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

Этот объект снова совместим с np.sum, потому что любые дополнительные аргументы (то есть ключевые слова, которые не являются axis или dtype) будут спрятаны в параметре **unused_kwargs.

© 2005–2022 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.21/user/basics.subclassing.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API