如何解决“迭代器应该返回字符串,而不是字节”

Posted

技术标签:

【中文标题】如何解决“迭代器应该返回字符串,而不是字节”【英文标题】:How to resolve "iterator should return strings, not bytes" 【发布时间】:2013-04-21 00:23:45 【问题描述】:

我正在尝试导入 CSV 文件,使用表单从客户端系统上传文件。获得文件后,我将提取其中的一部分并在我的应用程序中填充模型。但是,当我遍历上传文件中的行时,出现“迭代器应该返回字符串,而不是字节”错误。我花了几个小时尝试不同的东西并阅读我能找到的所有内容,但似乎无法解决它(注意,我对 Django 相对较新 - 运行 1.5 - 和 python - 运行 3.3)。我删除了一些东西以解决错误并像这样运行它以确保它仍然存在。在tools_clubs_import()中执行“for clubs in club_list”行时出现错误:

根据下面标记的答案,以下是正确的 views.py:

import csv
from io import TextIOWrapper
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from rank.forms import ClubImportForm

def tools_clubs_import(request):
    if request.method == 'POST':
        form = ClubImportForm(request.POST, request.FILES)
        if form.is_valid():
            # the following 4 lines dumps request.META to a local file
            # I saw a lot of questions about this so thought I'd post it too
            log = open("/home/joel/meta.txt", "w")
            for k, v in request.META.items():
                print ("%s: %s\n" % (k, request.META[k]), file=log)
            log.close()
            # I found I didn't need errors='replace', your mileage may vary
            f = TextIOWrapper(request.FILES['filename'].file,
                    encoding='ASCII')
            club_list = csv.DictReader(f)
            for club in club_list:
                # do something with each club dictionary entry
                pass
            return HttpResponseRedirect(reverse('rank.views.tools_clubs_import_show'))
    else:
        form = ClubImportForm()

    context = 'form': form, 'active_menu_item': 4,
    return render(request, 'rank/tools_clubs_import.html', context)

def tools_clubs_import_show(request):
    return render(request, 'rank/tools_clubs_import_show.html')

以下是我提交的原始版本(生成表单的html包含在此代码列表的底部:

views.py
--------
import csv
from django.shortcuts import render
from django.http import HttpResponseRedirect
from rank.forms import ClubImportForm

def tools(request):
    context = 'active_menu_item': 4,
    return render(request, 'rank/tools.html', context)

def tools_clubs(request):
    context = 'active_menu_item': 4,
    return render(request, 'rank/tools_clubs.html', context)

def tools_clubs_import(request):
    if request.method == 'POST':
        form = ClubImportForm(request.POST, request.FILES)
        if form.is_valid():
            f = request.FILES['filename']
            club_list = csv.DictReader(f)
            for club in club_list:
                # error occurs before anything here is executed
                # process here... not included for brevity
            return HttpResponseRedirect(reverse('rank.views.tools_clubs_import_show'))
    else:
        form = ClubImportForm()

    context = 'form': form, 'active_menu_item': 4,
    return render(request, 'rank/tools_clubs_import.html', context)

def tools_clubs_import_show(request):
    return render(request, 'rank/tools_clubs_import_show.html')

forms.py
--------
from django import forms


class ClubImportForm(forms.Form):
    filename = forms.FileField(label='Select a CSV to import:',)


urls.py
-------
from django.conf.urls import patterns, url
from rank import views

urlpatterns = patterns('',
    url(r'^tools/$', views.tools, name='rank-tools'),
    url(r'^tools/clubs/$', views.tools_clubs, name='rank-tools_clubs'),
    url(r'^tools/clubs/import$',
        views.tools_clubs_import,
        name='rank-tools_clubs_import'),
    url(r'^tools/clubs/import/show$',
        views.tools_clubs_import_show,
        name='rank-tools_clubs_import_show'),
)


tools_clubs_import.html
-----------------------
% extends "rank/base.html" %
% block title %Tools/Club/Import% endblock %
% block center_col %

    <form enctype="multipart/form-data" method="post" action="% url 'rank-tools_clubs_import' %">% csrf_token %
         form.as_p 
        <input type="submit" value="Submit" />
    </form>

% endblock %

异常值:

迭代器应该返回字符串,而不是字节(您是否以文本模式打开文件?)

异常位置:/usr/lib/python3.3/csv.py 在字段名中,第 96 行

【问题讨论】:

【参考方案1】:

request.FILES 为您提供 二进制 文件,但 csv 模块希望使用文本模式文件。

您需要将文件包装在io.TextIOWrapper() instance 中,并且您需要弄清楚编码:

from io import TextIOWrapper

f = TextIOWrapper(request.FILES['filename'].file, encoding=request.encoding)

如果提供了Content-Type 标头中的charset 参数可能会更好;这就是客户告诉你的字符集。

您无法解决需要知道文件数据的正确编码的问题;您可以强制解释为 ASCII,例如,通过提供 errors 关键字(将其设置为“替换”或“忽略”),但这确实会导致数据丢失:

f = TextIOWrapper(request.FILES['filename'].file, encoding='ascii', errors='replace')

使用 TextIOWrapper 仅在使用 Django 1.11 及更高版本时有效(如this changeset added the required support)。在早期版本中,您可以在事后对支持进行猴子补丁:

from django.core.files.utils import FileProxyMixin

if not hasattr(FileProxyMixin, 'readable'):
    # Pre-Django 1.11, add io.IOBase support, see
    # https://github.com/django/django/commit/4f474607de9b470f977a734bdd47590ab202e778        
    def readable(self):
        if self.closed:
            return False
        if hasattr(self.file, 'readable'):
            return self.file.readable()
        return True

    def writable(self):
        if self.closed:
            return False
        if hasattr(self.file, 'writable'):
            return self.file.writable()
        return 'w' in getattr(self.file, 'mode', '')

    def seekable(self):
        if self.closed:
            return False
        if hasattr(self.file, 'seekable'):
            return self.file.seekable()
        return True

    FileProxyMixin.closed = property(
        lambda self: not self.file or self.file.closed)
    FileProxyMixin.readable = readable
    FileProxyMixin.writable = writable
    FileProxyMixin.seekable = seekable

【讨论】:

生成“'InMemoryUploadedFile'对象没有属性'可读'”。 @MeaOrdo:已更新;抱歉,我没有运行 atm 的 Django-on-3.x,所以它只在这里读取源代码和推断。 :-) @MeaOrdo:迭代开始读取;你向csv 询问一行,然后模块向文件询问数据。 InMemoryUploadedFile 对象充当实际文件对象的代理,io.BytesIO 对象。您确实需要使用 TextIOWrapper 包装该对象,但您 必须 找出正确的编码。如果一切都失败了,请使用TextIOWrapper(request.FILES['filename'].file, encoding='ASCII', errors='replace')。你有没有看过request.META. CONTENT_TYPE,看看是否指定了charset参数? 您正在以正确的方式进行操作,但您不能忽略编码。如果您的客户上传国际数据,您需要能够将其读取为 Unicode。 @Jonathan:感谢您的编辑;不过,单挑也可以。我添加了一个解决方法。【参考方案2】:

在 python 3 中,我使用了:

import csv
from io import StringIO
csvf = StringIO(xls_file.read().decode())
reader = csv.reader(csvf, delimiter=',')

xls_file 是从 POST 表单获取的文件。希望对你有帮助。

【讨论】:

【参考方案3】:

融合你的两种方法,这在 Python 3.5.2 和 Django 1.9 中永远不会失败

delimitador = list_delimitadores[int(request.POST['delimitador'])][1]
try:
    text = TextIOWrapper(request.FILES['csv_x'].file, encoding='utf-8 ', errors='replace')
    reader = csv.reader(text, delimiter=delimitador)
except:
    text = StringIO(request.FILES['csv_x'].file.read().decode())
    reader = csv.reader(text, delimiter=delimitador)

【讨论】:

根据PEP 8,请不要使用裸except 子句——您具体期望什么例外?

以上是关于如何解决“迭代器应该返回字符串,而不是字节”的主要内容,如果未能解决你的问题,请参考以下文章

如何解决rar文件解压缩失败

如何在 C# 中解压缩多层 zip 文件

深度学习可解释性问题如何解决?图灵奖得主Bengio有一个解

问题的分类以及计算机如何解决这些问题

ORACLE报错 ORA-28000: 帐户已被锁定 解决方法,如何解锁?

程序员如何解决问题呢?