rspec/rules/S3862/python/rule.adoc

103 lines
2.5 KiB
Plaintext
Raw Normal View History

== Why is this an issue?
For-in loops, https://docs.python.org/3/whatsnew/3.3.html#pep-380-syntax-for-delegating-to-a-subgenerator[``++yield from++``] and iterable unpacking only work with https://docs.python.org/3/glossary.html#term-iterable[iterable objects]. In order to be iterable, an object should have either an ``++__iter__++`` method or a ``++__getitem__++`` method implementing the https://docs.python.org/3/glossary.html#term-sequence[Sequence] semantic.
2020-06-30 12:48:39 +02:00
2021-02-02 15:02:10 +01:00
Note also that iterating over an https://docs.python.org/3/glossary.html#term-asynchronous-iterable[asynchronous iterable], i.e. an object having the ``++__aiter__++`` method, requires the use of https://docs.python.org/3/reference/compound_stmts.html#the-async-for-statement[``++async for ... in++``] instead of ``++for ... in++``.
2020-06-30 12:48:39 +02:00
2021-02-02 15:02:10 +01:00
2021-01-27 13:42:22 +01:00
This rule raises an issue when a non iterable object is used in a ``++for-in++`` loop, in a ``++yield from++`` or when it is unpacked.
2020-06-30 12:48:39 +02:00
=== Noncompliant code example
2020-06-30 12:48:39 +02:00
2022-02-04 17:28:24 +01:00
[source,python]
2020-06-30 12:48:39 +02:00
----
class Empty:
pass
empty = Empty()
for a in empty: # Noncompliant
print(a)
a, b, c = empty # Noncompliant
print(*empty) # Noncompliant
[1, 2, 3, *empty] # Noncompliant
# yield from
def generator():
yield from Empty() # Noncompliant
# async generators
async def async_generator():
yield 1
a, *rest = async_generator() # Noncompliant
for a in async_generator(): # Noncompliant; "async" is missing before "for"
print(a)
----
=== Compliant solution
2020-06-30 12:48:39 +02:00
2022-02-04 17:28:24 +01:00
[source,python]
2020-06-30 12:48:39 +02:00
----
class MyIterable:
def __init__(self, values):
self._values = values
def __iter__(self):
return iter(self._values)
my_iterable = MyIterable(range(10))
for a in my_iterable:
print(a)
a, b, *c = my_iterable
print(*my_iterable)
[1, 2, 3, *my_iterable]
# yield from
def generator():
yield from subgenerator()
def subgenerator():
yield 1
# async generators
async def async_generator():
yield 1
async for a in async_generator():
print(a)
----
== Resources
2020-06-30 12:48:39 +02:00
* https://www.python.org/dev/peps/pep-0234/#python-api-specification[PEP 234 - Iterators]
* https://docs.python.org/3/library/stdtypes.html#iterator-types[Python documentation - Iterator Types]
ifdef::env-github,rspecator-view[]
'''
== Implementation Specification
(visible only on this page)
=== Message
* Replace this expression with an iterable object.
* Add "async" before "for"; Expression is an async generator.
=== Highlighting
* Primary: the non-iterable expression
* Secondary: The class/function/... definition
message: 'Definition of "X".'
endif::env-github,rspecator-view[]