Q : I can't get away with the TypeError: function object is not iterable
For the sake of the TypeError
:
TypeError
exceptions is being thrown right due to wrong syntax ( ill-formated call to a joblib.Parallel( delayed( ... ) ... )
mis-obeying the documented calling syntax-constructor.
Example 1: a correct call:
This call follows the documented syntax-specification down to the last dot:
>>> from joblib import Parallel, delayed
>>> parallel = Parallel( n_jobs = -1 )
>>> import numpy as np
>>> parallel( delayed( np.sqrt ) ( i**2 ) for i in range( 10 ) )
# ^ ^^^^^^^ ^^^^ ^^^^ |||
# | ||||||| |||| |||| vvv
#JOBS(-1):-+ ||||||| |||| |||| |||
#DELAYED:-----+++++++ |||| |||| |||
#FUN( par ):--------------++++ |||| |||
# ||| |||| |||
# +++-FUN(signature-"decl.")---++++ |||
# ^^^ |||
# ||| |||
# +++-<<<-<iterator>-<<<-<<<-<<<-<<<--+++
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
and the result generated confirms that the call was fully compliant and interprete-able.
Example 2: a wrong call:
>>> from joblib import Parallel, delayed
>>> parallel = Parallel( n_jobs = -1 )
>>> import numpy as np
>>> parallel( delayed( np.sqrt( 10 ) ) ) #### THIS SLOC IS KNOWINGLY WRONG
# ^ ^^^^^^^ ^^^^(????) ???? ??? ####
# | ||||||| |||| |||| vvv ####
#JOBS(-1):-+ ||||||| |||| |||| ||| ####
#DELAYED:-----+++++++ |||| |||| ||| #### DELAYED( <float64> )
#FUN( par ):--------------++++ |||| ||| #### GOT NO CALLABLE FUN( par )
# ||| |||| ||| #### BUT A NUMBER
# +++-FUN(signature-"decl.")------++++ ||| #### FUN( signature )
# ^^^ ||| #### NOT PRESENT
# ||| ||| #### AND FEEDER
# +++-<<<-<iterator>-<<<-<<<-<<<-<<<-<<<-+++ #### <ITERATOR> MISSING
# ####
Traceback (most recent call last): #### FOR DETAILS, READ THE O/P
File "<stdin>", line 1, in <module> #### AND EXPLANATION BELOW
File ".../lib/python3.5/site-packages/joblib/parallel.py", line 947, in __call__
iterator = iter(iterable)
TypeError: 'function' object is not iterable
and the result confirms, that the O/P has used a syntax, that is incompatible with the documented joblib.Parallel( delayed(...) ... )
Q.E.D.
REMEDY :
Follow the joblib.Parallel( delayed( ... ) ... )
documented syntax:
#entroids, _ = parallel( delayed( kmeans(weights1d, cluster)))
# ^^^^^^(..................)
# ||||||(..................)
#THIS-IS-NOT-A-CALLABLE-BUT-VALUE-++++++(..................)
#
centroids, _ = parallel( delayed( kmeans ) ( weights1d, cluster ) for ... )
# ^^^^^^ ^^^^^^^^^^^^^^^^^^ |||||||
# |||||| |||||||||||||||||| vvvvvvv
# CALLABLE FUN()------------------++++++ |||||||||||||||||| |||||||
# FUN( <signature> )----------------++++++++++++++++++ |||||||
# ^^^^^^^^^^^ |||||||
# ||||||||||| |||||||
# +++++++++++------------<<<--feeding-<iterator>----+++++++
The best first step :
is to re-read the documented details of how the joblib.Parallel
was designed and what are the modes-of-use, so as to become better acquainted with the tool:
joblib.Parallel( n_jobs = None, # how many jobs will get instantiated
backend = None, # a method, how these will get instantiated
verbose = 0,
timeout = None,
pre_dispatch = '2 * n_jobs',
batch_size = 'auto',
temp_folder = None,
max_nbytes = '1M',
mmap_mode = 'r',
prefer = None, # None | { ‘processes’, ‘threads’ }
require = None # None | ‘sharedmem’ ~CONSTRAINTS backend
)
Next, one may master some trivial example ( and experiment and extend it towards one's intended use-case ):
Parallel( n_jobs = 2 ) ( delayed( sqrt ) ( i ** 2 ) for i in range( 10 ) )
# ^ ^^^^^^^ ^^^^ ^^^^^^ |||
# | ||||||| |||| |||||| vvv
#JOBS:-----+ ||||||| |||| |||||| |||
#DELAYED:-----------------+++++++ |||| |||||| |||
#FUN( par ):-----------------------++++ |||||| |||
# ||| |||||| |||
# +++--FUN(-signature-"declaration"-)---++++++ |||
# ^^^ |||
# ||| |||
# +++-<<<-<iterator>-<<<-<<<-<<<-<<<-<<<-<<<-<<<-+++
Parallel( n_jobs = -1 ) (
delayed( myTupleConsumingFUN ) ( # aFun( aTuple = ( a, b, c, d ) )
aTupleOfParametersGeneratingFUN( i ) )
for i in range( 10 )
)
NEXT: try to understand the costs and limits of using n_jobs
instantiation(s)
The default backend of joblib
will run each function call in isolated Python processes, therefore they cannot mutate a common Python object defined in the main program.
However if the parallel function really needs to rely on the shared memory semantics of threads, it should be made explicit with require='sharedmem'
Keep in mind that relying a on the shared-memory semantics is probably suboptimal from a performance point of view as concurrent access to a shared Python object will suffer from lock contention.
Using the threads-based backend permits "sharing", yet it implicates an immense cost of doing that - threads re-introduce the GIL-stepping which will re-[SERIAL]
-ise the flow of code-execution back into a one-after-another-after-another in a GIL-lock-stepping fashion. For computing-intensive processing that yields worse performance, than the original pure-[SERIAL]
code ( while this mode can help for latency-masking use-cases, where waiting for network-responses may allow threads to release GIL-lock and let other threads to go ahead and continue the work )
There are steps, one may implement so as to make separate process-based computing being able to communicate such need, yet, that comes at some add-on costs.
Computing intensive problems have to balance the needs for ultimate performance ( using more cores ) yet having in mind to have just an isolated (split) work-unit and minimum add-on costs for parameter-transfers and results-returns, all of which may easily cost more, than a wrong-designed intent to harness the joblib.Parallel
available forms of just-[CONCURRENT]
process-scheduling.
For more details on joblib.Parallel
For more details on add-on costs and atomicity-of-work implications on parallel-speedup