Python 3.6 - “字符串%元组”做啥?

Posted

技术标签:

【中文标题】Python 3.6 - “字符串%元组”做啥?【英文标题】:Python 3.6 - What does "string % tuple" do?Python 3.6 - “字符串%元组”做什么? 【发布时间】:2019-03-27 05:06:16 【问题描述】:

我正在为基于 Django 的应用程序编写一个 python 模块,该应用程序通过 cx_Oracle 访问 Oracle 数据库。 “似乎”django 代码有一个错误,它破坏了 cx_Oracle“executemany”方法的使用。如果我使用 cx_Oracle 并严格通过 cx_Oracle 打开连接,则逻辑工作正常。通过django使用连接,失败。

由于 django 是一项要求,我正在寻找一种解决方法,并且需要了解它失败的语句(如下)试图做什么。我知道“%”既可用作模运算符,也可用于字符串格式,因为在这种情况下显然是这样。但是,尽管进行了很多搜索,但这似乎不符合我能找到的任何使用“%”的字符串格式化语法。有人可以解释这是要做什么吗?

query = query % tuple(args)

TypeError: not all arguments converted during string formatting

地点:

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']

如果你在 REPL 中输入这些值和上面的语句,你会得到同样的错误。

我知道我应该提交一份 django 错误报告。稍后会弄清楚。现在我希望我能以某种方式更改查询字符串中的 Oracle 绑定变量位置符号以满足上述语句。同样,查询字符串直接使用 cx_Oracle 没有问题。

详情:

Python 3.6.5 :: Anaconda, Inc.

cx-Oracle 7.0.0

Django 2.0.7

cx_Oracle 查询格式: https://www.oracle.com/technetwork/articles/dsl/prez-python-queries-101587.html (见“一次很多”)

我的 cx_Oracle 代码:

cursor = conn.cursor()
cursor.prepare(query)
cursor.executemany(query, list_of_tuples_of_values)
rows_affected = cursor.rowcount
conn.commit()

失败的代码在 django 模块 base.py 中,第 494 行: (C:\python\Anaconda2\envs\py36\lib\site-packages\django\db\backends\oracle\base.py)

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) > 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)             <==============
    return query, self._format_params(params)

params = (datetime.datetime(2018, 10, 12, 0, 0), '123456', 10, 10, 8)

【问题讨论】:

永远不要在 SQL 查询中使用字符串格式(f-strings、str.format()%)。这样你就会得到 SQL 注入漏洞。 【参考方案1】:

为了实现跨数据库兼容性,Django 在所有数据库后端使用统一的占位符。您粘贴的代码将 %s 占位符转换为特定于 Oracle 的占位符,但它失败了,因为您的查询已经在使用特定于 Oracle 的占位符。

%s 替换占位符,它应该可以工作:

 query = 'INSERT INTO DATABASE.TABLE\n        (DATE, ID, COL_A, COL_B, COL_C)\n    VALUES (%s, %s, %s, %s, %s)\n'

【讨论】:

【参考方案2】:

% 是string formatting operator,它将后面的元组中的项目值应用到它前面的格式字符串中的占位符(以% 开头)。

由于您的query 字符串实际上不包含任何以% 开头的格式化占位符,因此% 运算符会失败,因为当没有任何占位符时,它无法将元组中的值映射到占位符.出于您的目的,您应该将 args 作为参数传递给数据库游标的 execute 方法,以便可以将参数值应用于绑定变量(在query 字符串)以安全的方式。

【讨论】:

以上是关于Python 3.6 - “字符串%元组”做啥?的主要内容,如果未能解决你的问题,请参考以下文章

SQL语句里面的"Cast"是做啥的?是不是是保留字?

python 列表,字典,元组,字符串,常用函数

python基础--字符串元组

python字符串/元组/列表/字典互转

python - 字符串,元组,列表,字典

Python列表元组字典和字符串的常用函数