Skip to content

Conversation

@sendilkumarn
Copy link

This adds iterator support for iterating through the Python Object.

This replaces Mutable collection with the IteratorProtocol and Sequence.

Resolves TF-337.

P.S. This is my first PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to delete this? I think your change should be purely additive.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, without removing this change It is falling back to the same iteration. I might be doing something wrong here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In think you would need to implement:
public func makeIterator() -> PythonObject { return Python.iter(self) }
as part of a conformance to Sequence?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding Sequence conformance works. Thanks 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is looking simpler:
guard let result: OwnedPyObjectPointer = PyIter_Next(obj.borrowedPyObject) else {
try! throwPythonErrorIfPresent()
return nil
}
return PythonObject(owning: result)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly what I had initially, but I refrained from the straight C-API calls.

@sendilkumarn sendilkumarn changed the title [WIP] [iterator] Use iter and next to loop through Python Object (TF-337) [iterator] Use iter and next to loop through Python Object (TF-337) Mar 13, 2019
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean these up because they aren't used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the prevailing syntax is "ClassName : Protocol". Please update to that here and for MutableCollection.

@pschuh
Copy link
Contributor

pschuh commented Mar 13, 2019

Looking quite close!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this over 80 columns? If so, can you please make it fit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our local convention is to avoid ‘self.’ unless it’s required.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid explicit type annotation when it can be inferred.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a space after each comma.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend against the technique of making sequences their own iterators too. It's usually a better choice to declare a nested Iterator type, ensuring that the sequence is unambiguously multi-pass with a fresh iterator from the start on each call to makeIterator().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I get this part correctly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could make a nested Iterator struct which would wrap a PythonObject and just expose next(). This way, it is more type-safe if someone tries to iterate a python sequence themselves.

Copy link
Contributor

@rxwei rxwei Mar 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can define a ‘struct Iterator : IteratorProtocol {}’ within a PythonObject extension.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the PR. Is this internal struct okay to be public here?

@sendilkumarn sendilkumarn force-pushed the py-iter branch 2 times, most recently from a94bd94 to 7785ea5 Compare March 14, 2019 15:38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property should be private or fileprivate because it is only used within this extension.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename this to something with less abbreviation. How about pythonIterator?

Suggested change
let pyObjIterator: PythonObject
let pythonIterator: PythonObject

@sendilkumarn
Copy link
Author

Thanks @rxwei 👍 for the comments

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be simplified to use ‘Python.next()’ directly. Have you tried that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s of course some trade off between efficiency and convenience. I think consistency is key here—we should either use pure C APIs for ‘makeIterator’ and ‘next’, or pure Python APIs.

Copy link
Author

@sendilkumarn sendilkumarn Mar 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, that was my first commit. I think it is better to stick with one [edited] APIs, rather than mix and match. #23268 (comment)

Edit: The problem with Python API is that Python.next and iter will return PythonObject rather than PythonObject? This complicates things, because the error is thrown when the method is called dynamically. I will try to use C-Apis for both

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with Python API is that Python.next and iter will return PythonObject rather than PythonObject? This complicates things, because the error is thrown when the method is called dynamically.

This is okay--you can check whether the python object is equal to Python.None. However, I like using C APIs for both since that's strictly more efficient :)

Copy link
Contributor

@rxwei rxwei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our local convention is to omit self. unless it's required.

Suggested change
let result = PyObject_GetIter(self.borrowedPyObject)
let result = PyObject_GetIter(borrowedPyObject)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could cause a segfault. PythonObject.init(consuming:) already takes ownership of a PyObject pointer and calls Py_DecRef in its deinit. Remove this defer block and then you are good.

Also, there's no need to add empty lines in this compact function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If PyObject_GetIter returns nil (aka, a NULL pointer), this would be a precondition failure and a Python TypeError must have been raised, according to PyObject_GetIter's documentation). Instead of force-unwrapping result, we should use a guard statement and throw the Python exception.

guard let result = PyObject_GetIter(borrowedPyObject) else {
  try! throwPythonErrorIfPresent()
  // Unreachable. A Python `TypeError` must have been thrown.
  preconditionFailure()
}
return Iterator(pythonIterator: PythonObject(consuming: result))

@sendilkumarn
Copy link
Author

Thanks for the awesome help ( I learnt a lot) 👍

try! throwPythonErrorIfPresent()
return nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last comment: Could you please remove this empty line?

@rxwei
Copy link
Contributor

rxwei commented Mar 18, 2019

Thanks a lot, @sendilkumarn!

@rxwei
Copy link
Contributor

rxwei commented Mar 18, 2019

@swift-ci please test tensorflow

2 similar comments
@rxwei
Copy link
Contributor

rxwei commented Mar 18, 2019

@swift-ci please test tensorflow

@rxwei
Copy link
Contributor

rxwei commented Mar 18, 2019

@swift-ci please test tensorflow

@rxwei rxwei merged commit f612e90 into swiftlang:tensorflow Mar 18, 2019
@sendilkumarn sendilkumarn deleted the py-iter branch March 18, 2019 15:58
rxwei pushed a commit to rxwei/swift that referenced this pull request May 11, 2019
…wiftlang#23268)

This adds iterator support for iterating through a `PythonObject`. Makes `PythonObject` conform to `Sequence`. 

Resolves [TF-337](https://bugs.swift.org/browse/TF-337).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants