title: Python 3.6 - What does "string % tuple" do?
body: <p>I am writing a python module for an Django-based application that accesses an Oracle database via cx_Oracle.  It "appears" that the django code has a bug that breaks the use of the cx_Oracle "executemany" method.  If I use cx_Oracle with a connection opened strictly via cx_Oracle, the logic works fine.  Use a connection via django, it fails.</p>
  
  <p>As django is a requirement, I am looking for a work-around and need to understand what the statement (below) where it fails is trying to do. I understand that "%" is used both as a modulo operator and for string formatting as it apparently is in this case.  But despite much searching, this doesn't seem to conform to any string formatting syntax using "%" that I can find.  Can someone explain what this is trying to do?</p>
  
  <pre><code>query = query % tuple(args)
  
  TypeError: not all arguments converted during string formatting
  </code></pre>
  
  <p>Where:</p>
  
  <pre><code>query = 'INSERT INTO DATABASE.TABLE\n        (DATE, ID, COL_A, COL_B, COL_C)\n    VALUES (:1, :2, :3, :4, :5)\n'
  
  args = [':arg0', ':arg1', ':arg2', ':arg3', ':arg4']
  </code></pre>
  
  <p>If you type these values and the above statement into the REPL, you will get this same error.</p>
  
  <p>I know I should submit a django bug report.  Will figure that out later.  For now I'm hoping I can somehow change the Oracle bind-variable positional notation in the query string to satisfy the above statement.  Again, the query string has no problem working directly with cx_Oracle.</p>
  
  <p>Details:</p>
  
  <p>Python 3.6.5 :: Anaconda, Inc.</p>
  
  <p>cx-Oracle 7.0.0</p>
  
  <p>Django 2.0.7</p>
  
  <p>cx_Oracle query format:
  <a href="https://www.oracle.com/technetwork/articles/dsl/prez-python-queries-101587.html" rel="nofollow noreferrer">https://www.oracle.com/technetwork/articles/dsl/prez-python-queries-101587.html</a>
  (See "Many at Once")</p>
  
  <p>My cx_Oracle code:</p>
  
  <pre><code>cursor = conn.cursor()
  cursor.prepare(query)
  cursor.executemany(query, list_of_tuples_of_values)
  rows_affected = cursor.rowcount
  conn.commit()
  </code></pre>
  
  <p>The code that's failing is in django module base.py, line 494:
  (C:\python\Anaconda2\envs\py36\lib\site-packages\django\db\backends\oracle\base.py)</p>
  
  <pre><code>def _fix_for_params(self, query, params, unify_by_values=False):
      # cx_Oracle wants no trailing ';' for SQL statements.  For PL/SQL, it
      # it does want a trailing ';' but not a trailing '/'.  However, these
      # characters must be included in the original query in case the query
      # is being passed to SQL*Plus.
      if query.endswith(';') or query.endswith('/'):
          query = query[:-1]
      if params is None:
          params = []
      elif hasattr(params, 'keys'):
          # Handle params as dict
          args = {k: ":%s" % k for k in params}
          query = query % args
      elif unify_by_values and len(params) &gt; 0:
          # Handle params as a dict with unified query parameters by their
          # values. It can be used only in single query execute() because
          # executemany() shares the formatted query with each of the params
          # list. e.g. for input params = [0.75, 2, 0.75, 'sth', 0.75]
          # params_dict = {0.75: ':arg0', 2: ':arg1', 'sth': ':arg2'}
          # args = [':arg0', ':arg1', ':arg0', ':arg2', ':arg0']
          # params = {':arg0': 0.75, ':arg1': 2, ':arg2': 'sth'}
          params_dict = {param: ':arg%d' % i for i, param in enumerate(set(params))}
          args = [params_dict[param] for param in params]
          params = {value: key for key, value in params_dict.items()}
          query = query % tuple(args)
      else:
          # Handle params as sequence
          args = [(':arg%d' % i) for i in range(len(params))]
          query = query % tuple(args)             &lt;==============
      return query, self._format_params(params)
  
  params = (datetime.datetime(2018, 10, 12, 0, 0), '123456', 10, 10, 8)
  </code></pre>