// Copyright (c) 2013 Jython Developers
package org.python.core;
import java.nio.ByteBuffer;
import org.python.core.buffer.BaseBuffer;
import org.python.core.util.StringUtil;
import org.python.expose.ExposedGet;
import org.python.expose.ExposedMethod;
import org.python.expose.ExposedNew;
import org.python.expose.ExposedType;
import org.python.expose.MethodType;
/**
* Class implementing the Python memoryview type. It provides a wrapper around the
* Jython buffer API.
*/
@ExposedType(name = "memoryview", doc = BuiltinDocs.memoryview_doc, base = PyObject.class,
isBaseType = false)
public class PyMemoryView extends PySequence implements BufferProtocol, Traverseproc {
public static final PyType TYPE = PyType.fromClass(PyMemoryView.class);
/** The buffer exported by the object of which this is a view. */
private PyBuffer backing;
/**
* A memoryview in the released state forbids most Python API actions. If the underlying
* PyBuffer is shared, the memoryview may be released while the underlying PyBuffer is not
* "finally" released.
*/
private boolean released;
/** Cache the result of getting shape here. */
private PyObject shape;
/** Cache the result of getting strides here. */
private PyObject strides;
/** Cache the result of getting suboffsets here. */
private PyObject suboffsets;
/** Hash value cached (so we still know it after {@link #release()} is called. */
private int hashCache;
private boolean hashCacheValid = false;
/**
* Construct a PyMemoryView from an object bearing the {@link BufferProtocol}
* interface. If this object is already an exported buffer, the memoryview takes a
* new lease on it. The buffer so obtained will be writable if the underlying object permits it.
*
* @param pybuf buffer exported by some underlying object
* @throws ClassCastException in cases where {@code pybuf.getBuffer} does so.
*/
public PyMemoryView(BufferProtocol pybuf) throws ClassCastException {
super(TYPE);
/*
* Ask for the full set of facilities (strides, indirect, etc.) from the object in case they
* are necessary for navigation, but only ask for read access. If the object is writable,
* the PyBuffer will be writable.
*/
backing = pybuf.getBuffer(PyBUF.FULL_RO);
}
@ExposedNew
static PyObject memoryview_new(PyNewWrapper new_, boolean init, PyType subtype,
PyObject[] args, String[] keywords) {
// One 'object' argument required
if (args.length != 1) {
throw Py.TypeError("memoryview() takes exactly one argument");
}
// Use the ArgParser to access it
ArgParser ap = new ArgParser("memoryview", args, keywords, "object");
PyObject obj = ap.getPyObject(0);
if (obj instanceof BufferProtocol) {
// Certain types that implement BufferProtocol do not implement the buffer protocol
try {
return new PyMemoryView((BufferProtocol) obj);
} catch (ClassCastException e) { /* fall through to message */ }
}
throw Py.TypeError(
"cannot make memory view because object does not have the buffer interface");
}
@Override
public Object __tojava__(Class> c) {
if (c == ByteBuffer.class) {
// Present the memoryview as a Java NIO buffer
checkNotReleased();
return backing.getNIOByteBuffer();
}
return super.__tojava__(c);
}
// @ExposedGet(doc = obj_doc) // Not exposed in Python 2.7
public PyObject obj() {
checkNotReleased();
BufferProtocol obj = backing.getObj();
return (obj instanceof PyObject) ? (PyObject)obj : Py.None;
}
@ExposedGet(doc = format_doc)
public String format() {
checkNotReleased();
return backing.getFormat();
}
@ExposedGet(doc = itemsize_doc)
public int itemsize() {
checkNotReleased();
return backing.getItemsize();
}
@ExposedGet(doc = shape_doc)
public PyObject shape() {
checkNotReleased();
if (shape == null) {
shape = tupleOf(backing.getShape());
}
return shape;
}
@ExposedGet(doc = ndim_doc)
public int ndim() {
checkNotReleased();
return backing.getNdim();
}
@ExposedGet(doc = strides_doc)
public PyObject strides() {
checkNotReleased();
if (strides == null) {
strides = tupleOf(backing.getStrides());
}
return strides;
}
@ExposedGet(doc = suboffsets_doc)
public PyObject suboffsets() {
checkNotReleased();
if (suboffsets == null) {
suboffsets = tupleOf(backing.getSuboffsets());
}
return suboffsets;
}
@ExposedGet(doc = readonly_doc)
public boolean readonly() {
checkNotReleased();
return backing.isReadonly();
}
/**
* Implementation of Python tobytes(). Return the data in the buffer as a byte
* string (an object of class str).
*
* @return byte string of buffer contents.
*/
/*
* From Python 3, this is equivalent to calling the bytes constructor on the
* memoryview.
*/
public PyString tobytes() {
return memoryview_tobytes();
}
@ExposedMethod(doc = tobytes_doc)
final PyString memoryview_tobytes() {
checkNotReleased();
if (backing instanceof BaseBuffer) {
// In practice, it always is
return new PyString(backing.toString());
} else {
// But just in case ...
String s = StringUtil.fromBytes(backing);
return new PyString(s);
}
}
/**
* Implementation of Python tolist(). Return the data in the buffer as a
* list where the elements are an appropriate type (int in the case of
* a byte-oriented buffer, which is the only case presently supported).
*
* @return a list of buffer contents.
*/
public PyList tolist() {
return memoryview_tolist();
}
@ExposedMethod(doc = tolist_doc)
final PyList memoryview_tolist() {
checkNotReleased();
int n = backing.getLen();
PyList list = new PyList();
for (int i = 0; i < n; i++) {
list.add(new PyInteger(backing.intAt(i)));
}
return list;
}
/**
* Make an integer array into a PyTuple of PyLong values or None if the argument is null.
*
* @param x the array (or null)
* @return the PyTuple (or Py.None)
*/
private PyObject tupleOf(int[] x) {
if (x != null) {
PyLong[] pyx = new PyLong[x.length];
for (int k = 0; k < x.length; k++) {
pyx[k] = new PyLong(x[k]);
}
return new PyTuple(pyx, false);
} else {
return Py.None;
}
}
@Override
public int __len__() {
checkNotReleased();
return backing.getLen();
}
@Override
public int hashCode() {
return memoryview___hash__();
}
@ExposedMethod
final int memoryview___hash__() {
if (!hashCacheValid) {
// We'll have to calculate it: only possible if not released
checkNotReleased();
// And if not mutable
if (backing.isReadonly()) {
hashCache = backing.toString().hashCode();
hashCacheValid = true;
} else {
throw Py.ValueError("cannot hash writable memoryview object");
}
}
return hashCache;
}
/*
* ============================================================================================
* Python API comparison operations
* ============================================================================================
*/
@Override
public PyObject __eq__(PyObject other) {
return memoryview___eq__(other);
}
@Override
public PyObject __ne__(PyObject other) {
return memoryview___ne__(other);
}
@Override
public PyObject __lt__(PyObject other) {
return memoryview___lt__(other);
}
@Override
public PyObject __le__(PyObject other) {
return memoryview___le__(other);
}
@Override
public PyObject __ge__(PyObject other) {
return memoryview___ge__(other);
}
@Override
public PyObject __gt__(PyObject other) {
return memoryview___gt__(other);
}
/**
* Comparison function between two buffers of bytes, returning 1, 0 or -1 as a>b, a==b, or
* a<b respectively. The comparison is by value, using Python unsigned byte conventions,
* left-to-right (low to high index). Zero bytes are significant, even at the end of the array:
* [65,66,67]<"ABC\u0000", for example and [] is less than every
* non-empty b, while []=="".
*
* @param a left-hand wrapped array in the comparison
* @param b right-hand wrapped object in the comparison
* @return 1, 0 or -1 as a>b, a==b, or a<b respectively
*/
private static int compare(PyBuffer a, PyBuffer b) {
// Compare elements one by one in these ranges:
int ap = 0;
int aEnd = ap + a.getLen();
int bp = 0;
int bEnd = b.getLen();
while (ap < aEnd) {
if (bp >= bEnd) {
// a is longer than b
return 1;
} else {
// Compare the corresponding bytes
int aVal = a.intAt(ap++);
int bVal = b.intAt(bp++);
int diff = aVal - bVal;
if (diff != 0) {
return (diff < 0) ? -1 : 1;
}
}
}
// All the bytes matched and we reached the end of a
if (bp < bEnd) {
// But we didn't reach the end of b
return -1;
} else {
// And the end of b at the same time, so they're equal
return 0;
}
}
/**
* Comparison function between this memoryview and any other object. The inequality comparison
* operators are based on this.
*
* In Python 2.7, memoryview objects are ordered by their equivalent byte sequence
* values, and there is no concept of a released memoryview. In Python 3,
* memoryview objects are not ordered but may be tested for equality: a
* memoryview is always equal to itself, and distinct memoryview
* objects are equal if they are not released, and view equal bytes. This method supports the
* Python 2.7 model, and should probably not survive into Jython 3.
*
* @param b
* @return 1, 0 or -1 as this>b, this==b, or this<b respectively, or -2 if the comparison is
* not implemented
*/
private int memoryview_cmp(PyObject b) {
// Check the memeryview is still alive: works here for all the inequalities
checkNotReleased();
// Try to get a byte-oriented view
PyBuffer bv = BaseBytes.getView(b);
if (bv == null) {
// Signifies a type mis-match. See PyObject._cmp_unsafe() and related code.
return -2;
} else {
try {
if (bv == backing) {
// Same buffer: quick result
return 0;
} else {
// Actually compare the contents
return compare(backing, bv);
}
} finally {
// Must always let go of the buffer
bv.release();
}
}
}
/**
* Fail-fast comparison function between byte array types and any other object, for when the
* test is only for equality. The inequality comparison operators __eq__ and
* __ne__ are based on this.
*
* In Python 2.7, memoryview objects are ordered by their equivalent byte sequence
* values, and there is no concept of a released memoryview. In Python 3,
* memoryview objects are not ordered but may be tested for equality: a
* memoryview is always equal to itself, and distinct memoryview
* objects are equal if they are not released, and view equal bytes. This method supports a
* compromise between of the two and should be rationalised in Jython 3.
*
* @param b
* @return 0 if this==b, or +1 or -1 if this!=b, or -2 if the comparison is not implemented
*/
private int memoryview_cmpeq(PyObject b) {
if (this == b) {
// Same object: quick success (even if released)
return 0;
} else if (released) {
// Released memoryview is not equal to anything (but not an error to have asked)
return -1;
} else if ((b instanceof PyMemoryView) && ((PyMemoryView)b).released) {
// Released memoryview is not equal to anything (but not an error to have asked)
return 1;
} else {
// Try to get a byte-oriented view
PyBuffer bv = BaseBytes.getView(b);
if (bv == null) {
// Signifies a type mis-match. See PyObject._cmp_unsafe() and related code.
return -2;
} else {
try {
if (bv == backing) {
// Same buffer: quick result
return 0;
} else if (bv.getLen() != backing.getLen()) {
// Different size: can't be equal, and we don't care which is bigger
return 1;
} else {
// Actually compare the contents
return compare(backing, bv);
}
} finally {
// Must always let go of the buffer
bv.release();
}
}
}
}
/**
* Implementation of __eq__ (equality) operator. Comparison with an invalid type returns null.
*
* @param other Python object to compare with
* @return Python boolean result or null if not implemented for the other type.
*/
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.memoryview___eq___doc)
final PyObject memoryview___eq__(PyObject other) {
int cmp = memoryview_cmpeq(other);
if (cmp == 0) {
return Py.True;
} else if (cmp > -2) {
return Py.False;
} else {
return null;
}
}
/**
* Implementation of __ne__ (not equals) operator. Comparison with an invalid type returns null.
*
* @param other Python object to compare with
* @return Python boolean result or null if not implemented for the other type.
*/
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.memoryview___ne___doc)
final PyObject memoryview___ne__(PyObject other) {
int cmp = memoryview_cmpeq(other);
if (cmp == 0) {
return Py.False;
} else if (cmp > -2) {
return Py.True;
} else {
return null;
}
}
/**
* Implementation of __lt__ (less than) operator. Comparison with an invalid type returns null.
*
* @param other Python object to compare with
* @return Python boolean result or null if not implemented for the other type.
*/
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.memoryview___lt___doc)
final PyObject memoryview___lt__(PyObject other) {
int cmp = memoryview_cmp(other);
if (cmp >= 0) {
return Py.False;
} else if (cmp > -2) {
return Py.True;
} else {
return null;
}
}
/**
* Implementation of __le__ (less than or equal to) operator. Comparison with an invalid type
* returns null.
*
* @param other Python object to compare with
* @return Python boolean result or null if not implemented for the other type.
*/
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.memoryview___le___doc)
final PyObject memoryview___le__(PyObject other) {
int cmp = memoryview_cmp(other);
if (cmp > 0) {
return Py.False;
} else if (cmp > -2) {
return Py.True;
} else {
return null;
}
}
/**
* Implementation of __ge__ (greater than or equal to) operator. Comparison with an invalid type
* returns null.
*
* @param other Python object to compare with
* @return Python boolean result or null if not implemented for the other type.
*/
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.memoryview___ge___doc)
final PyObject memoryview___ge__(PyObject other) {
int cmp = memoryview_cmp(other);
if (cmp >= 0) {
return Py.True;
} else if (cmp > -2) {
return Py.False;
} else {
return null;
}
}
/**
* Implementation of __gt__ (greater than) operator. Comparison with an invalid type returns
* null.
*
* @param other Python object to compare with
* @return Python boolean result or null if not implemented for the other type.
*/
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.memoryview___gt___doc)
final PyObject memoryview___gt__(PyObject other) {
int cmp = memoryview_cmp(other);
if (cmp > 0) {
return Py.True;
} else if (cmp > -2) {
return Py.False;
} else {
return null;
}
}
/**
* Called at the start of a context-managed suite (supporting the with clause).
*
* @return this object
*/
public PyObject __enter__() {
return memoryview___enter__();
}
@ExposedMethod(names = "__enter__")
final PyObject memoryview___enter__() {
checkNotReleased();
return this;
}
/**
* Called at the end of a context-managed suite (supporting the with clause), and
* will release the memoryview.
*
* @return false
*/
public boolean __exit__(PyObject type, PyObject value, PyObject traceback) {
return memoryview___exit__(type, value, traceback);
}
@ExposedMethod
final boolean memoryview___exit__(PyObject type, PyObject value, PyObject traceback) {
memoryview_release();
return false;
}
/*
* These strings are adapted from the patch in CPython issue 15855 and the on-line documentation
* most attributes do not come with any docstrings in CPython 2.7, so the make_pydocs trick
* won't work. This is a complete set, although not all are needed in Python 2.7.
*/
private final static String cast_doc = "M.cast(format[, shape]) -> memoryview\n\n"
+ "Cast a memoryview to a new format or shape.";
private final static String release_doc = "M.release() -> None\n\n"
+ "Release the underlying buffer exposed by the memoryview object.";
private final static String tobytes_doc = "M.tobytes() -> bytes\n\n"
+ "Return the data in the buffer as a bytestring (an object of class str).";
private final static String tolist_doc = "M.tolist() -> list\n\n"
+ "Return the data in the buffer as a list of elements.";
private final static String c_contiguous_doc = "c_contiguous\n"
+ "A bool indicating whether the memory is C contiguous.";
private final static String contiguous_doc = "contiguous\n"
+ "A bool indicating whether the memory is contiguous.";
private final static String f_contiguous_doc = "c_contiguous\n"
+ "A bool indicating whether the memory is Fortran contiguous.";
private final static String format_doc = "format\n"
+ "A string containing the format (in struct module style)\n"
+ " for each element in the view.";
private final static String itemsize_doc = "itemsize\n"
+ "The size in bytes of each element of the memoryview.";
private final static String nbytes_doc = "nbytes\n"
+ "The amount of space in bytes that the array would use in\n"
+ "a contiguous representation.";
private final static String ndim_doc = "ndim\n"
+ "An integer indicating how many dimensions of a multi-dimensional\n"
+ "array the memory represents.";
private final static String obj_doc = "obj\n" + "The underlying object of the memoryview.";
private final static String readonly_doc = "readonly\n"
+ "A bool indicating whether the memory is read only.";
private final static String shape_doc = "shape\n"
+ "A tuple of ndim integers giving the shape of the memory\n"
+ "as an N-dimensional array.";
private final static String strides_doc = "strides\n"
+ "A tuple of ndim integers giving the size in bytes to access\n"
+ "each element for each dimension of the array.";
private final static String suboffsets_doc = "suboffsets\n"
+ "A tuple of ndim integers used internally for PIL-style arrays\n" + "or None.";
/*
* ============================================================================================
* Support for the Buffer API
* ============================================================================================
*
* The buffer API allows other classes to access the storage directly.
*/
/**
* {@inheritDoc}
*
* The {@link PyBuffer} returned from this method is just the one on which the
* memoryview was first constructed. The Jython buffer API is such that sharing
* directly is safe (as long as the get-release discipline is observed).
*/
@Override
public synchronized PyBuffer getBuffer(int flags) {
checkNotReleased(); // Only for compatibility with CPython
/*
* The PyBuffer itself does all the export counting, and since the behaviour of memoryview
* need not change, it really is a simple as:
*/
return backing.getBuffer(flags);
}
/**
* Request a release of the underlying buffer exposed by the memoryview object.
* Many objects take special actions when a view is held on them (for example, a
* bytearray would temporarily forbid resizing); therefore, calling
* release() is handy to remove these restrictions (and free any dangling
* resources) as soon as possible.
*
* After this method has been called, any further operation on the view raises a
* ValueError (except release() itself which can be called multiple
* times with the same effect as just one call).
*
* This becomes an exposed method in CPython from 3.2. The Jython implementation of
* memoryview follows the Python 3.3 design internally and therefore safely
* anticipates Python 3 in exposing memoryview.release along with the related
* context-management behaviour.
*/
public synchronized void release() {
memoryview_release();
}
@ExposedMethod(doc = release_doc)
public synchronized final void memoryview_release() {
/*
* It is not an error to call this release method while this memoryview has
* buffer exports (e.g. another memoryview was created on it), but it will not
* release the underlying object until the last consumer releases the buffer.
*/
if (!released) {
// Release the buffer (which is not necessarily final)
backing.release();
// Remember we've been released
released = true;
}
}
/**
* Check that the memoryview is not released and raise a ValueError if it is. Almost every
* operation must call this before it starts work.
*/
protected void checkNotReleased() {
if (released) {
throw Py.ValueError("operation forbidden on released memoryview object");
}
// The backing should not have been released if the memoryview has not been.
assert (!backing.isReleased());
}
/*
* ============================================================================================
* API for org.python.core.PySequence
* ============================================================================================
*/
/**
* Gets the indexed element of the memoryview as a one byte string. This is an extension point
* called by PySequence in its implementation of {@link #__getitem__}. It is guaranteed by
* PySequence that the index is within the bounds of the memoryview.
*
* @param index index of the element to get.
* @return one-character string formed from the byte at the index
*/
@Override
protected PyString pyget(int index) {
// Our chance to check the memoryview is still alive
checkNotReleased();
// Treat the byte at the index as a character code
return new PyString(String.valueOf((char)backing.intAt(index)));
// Originally implemented Python 3 semantics, returning a PyInteger (for byte-oriented) !
// return new PyInteger(backing.intAt(index));
}
/**
* Returns a slice of elements from this sequence as a PyMemoryView.
*
* @param start the position of the first element.
* @param stop one more than the position of the last element.
* @param step the step size.
* @return a PyMemoryView corresponding the the given range of elements.
*/
@Override
protected synchronized PyMemoryView getslice(int start, int stop, int step) {
// Our chance to check the memoryview is still alive
checkNotReleased();
int n = sliceLength(start, stop, step);
PyBuffer view = backing.getBufferSlice(PyBUF.FULL_RO, start, n, step);
PyMemoryView ret = new PyMemoryView(view);
view.release(); // We've finished (new PyMemoryView holds a lease)
return ret;
}
/**
* memoryview*int is not implemented in Python, so this should never be called. We still have to
* override it to satisfy PySequence.
*
* @param count the number of times to repeat this.
* @return never
* @throws PyException {@code NotImplemented} always
*/
@Override
protected synchronized PyMemoryView repeat(int count) throws PyException {
throw Py.NotImplementedError("memoryview.repeat()");
}
/**
* Sets the indexed element of the memoryview to the given value, treating the operation as
* assignment to a slice of length one. This is different from the same operation on a byte
* array, where the assigned value must be an int: here it must have the buffer API and length
* one. This is an extension point called by PySequence in its implementation of
* {@link #__setitem__} It is guaranteed by PySequence that the index is within the bounds of
* the memoryview. Any other clients calling pyset(int, PyObject) must make the same
* guarantee.
*
* @param index index of the element to set.
* @param value to set this element to, regarded as a buffer of length one unit.
* @throws PyException {@code AttributeError} if value cannot be converted to an integer
* @throws PyException {@code ValueError} if value<0 or value>255
*/
@Override
public synchronized void pyset(int index, PyObject value) throws PyException {
// Our chance to check the memoryview is still alive
checkNotReleased();
// Get a buffer API on the value being assigned
PyBuffer valueBuf = BaseBytes.getViewOrError(value);
try {
if (valueBuf.getLen() != 1) {
// CPython 2.7 message
throw Py.ValueError("cannot modify size of memoryview object");
}
backing.storeAt(valueBuf.byteAt(0), index);
} finally {
valueBuf.release();
}
}
/**
* Sets the given range of elements according to Python slice assignment semantics. If the step
* size is one, it is a simple slice and the operation is equivalent to replacing that slice,
* with the value, accessing the value via the buffer protocol.
*
*
* a = bytearray(b'abcdefghijklmnopqrst')
* m = memoryview(a)
* m[2:7] = "ABCDE"
*
*
* Results in a=bytearray(b'abABCDEhijklmnopqrst').
*
* If the step size is one, but stop-start does not match the length of the right-hand-side a
* ValueError is thrown.
*
* If the step size is not one, and start!=stop, the slice defines a certain number of elements
* to be replaced. This function is not available in Python 2.7 (but it is in Python 3.3).
*
*
*
* a = bytearray(b'abcdefghijklmnopqrst')
* a[2:12:2] = iter( [65, 66, 67, long(68), "E"] )
*
*
* Results in a=bytearray(b'abAdBfChDjElmnopqrst') in Python 3.3.
*
* @param start the position of the first element.
* @param stop one more than the position of the last element.
* @param step the step size.
* @param value an object consistent with the slice assignment
*/
@Override
protected synchronized void setslice(int start, int stop, int step, PyObject value) {
// Our chance to check the memoryview is still alive
checkNotReleased();
if (step == 1 && stop < start) {
// Because "b[5:2] = v" means insert v just before 5 not 2.
// ... although "b[5:2:-1] = v means b[5]=v[0], b[4]=v[1], b[3]=v[2]
stop = start;
}
// Get a buffer API on the value being assigned
PyBuffer valueBuf = BaseBytes.getViewOrError(value);
// We'll also regard the assigned slice as a buffer.
PyBuffer backingSlice = null;
try {
// How many destination items? Has to match size of value.
int n = sliceLength(start, stop, step);
if (n != valueBuf.getLen()) {
// CPython 2.7 message
throw Py.ValueError("cannot modify size of memoryview object");
}
/*
* In the next section, we get a sliced view of the backing and write the value to it.
* The approach to errors is unusual for compatibility with CPython. We pretend we will
* not need a WRITABLE buffer in order to avoid throwing a BufferError. This does not
* stop the returned object being writable, simply avoids the check. If in fact it is
* read-only, then trying to write raises TypeError.
*/
backingSlice = backing.getBufferSlice(PyBUF.FULL_RO, start, n, step);
backingSlice.copyFrom(valueBuf);
} finally {
// Release the buffers we obtained (if we did)
if (backingSlice != null) {
backingSlice.release();
}
valueBuf.release();
}
}
/* Traverseproc implementation */
@Override
public int traverse(Visitproc visit, Object arg) {
int retVal;
if (backing != null) {
if (backing instanceof PyObject) {
retVal = visit.visit((PyObject)backing, arg);
if (retVal != 0) {
return retVal;
}
} else if (backing instanceof Traverseproc) {
retVal = ((Traverseproc)backing).traverse(visit, arg);
if (retVal != 0) {
return retVal;
}
}
}
if (shape != null) {
retVal = visit.visit(shape, arg);
if (retVal != 0) {
return retVal;
}
}
if (strides != null) {
retVal = visit.visit(strides, arg);
if (retVal != 0) {
return retVal;
}
}
return suboffsets == null ? 0 : visit.visit(suboffsets, arg);
}
@Override
public boolean refersDirectlyTo(PyObject ob) {
if (ob != null && (ob == backing || ob == shape || ob == strides || ob == suboffsets)) {
return true;
} else if (suboffsets instanceof Traverseproc) {
return ((Traverseproc)suboffsets).refersDirectlyTo(ob);
} else {
return false;
}
}
}
Ads help maintain this website.