Spec-Zone.ru › NumPy 1.20

Подклассы 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(MySubClass, cls).__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(C, cls).__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(InfoArray, subtype).__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(A, self).__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(MySubClass, self).__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–2021 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.20/user/basics.subclassing.html

Spec-Zone.ru

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