如何解决“迭代器应该返回字符串而不是字节” [英] How to resolve "iterator should return strings, not bytes"

查看:327
本文介绍了如何解决“迭代器应该返回字符串而不是字节”的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试导入CSV文件,使用表单从客户端系统上传文件。在我收到文件后,我将收到部分文件,并在我的应用程序中填写一个模型。但是,当我去上传文件中的行时,我得到一个迭代器应该返回字符串而不是字节错误。我花了几个小时尝试不同的东西,并阅读我可以找到的一切,但似乎无法解决它(注意,我相对较新的Django-运行1.5-和python - 运行3.3)。我删除了一些事情,以得到错误,并运行它,以确保它仍然在那里。在tools_clubs_import()中执行club_list中的俱乐部行时会显示错误:

I am trying to import a CSV file, using a form to upload the file from the client system. After I have the file, I'll take parts of it and populate a model in my app. However, I'm getting an "iterator should return strings, not bytes" error when I go to iterate over the lines in the uploaded file. I've spent hours trying different things and reading everything I could find on this but can't seem resolve it (note, I'm relatively new to Django- running 1.5- and python - running 3.3). I stripped out things to get to just the error and ran it like this to make sure it is still there. The error is displayed when executing the line "for clubs in club_list" in tools_clubs_import():

以下是更正的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包含在内)在这个代码的底部lis t:

The following is the original version of what I submitted (the html that generates the form is included at the bottom of this code list:

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 %}

异常值:

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

iterator should return strings, not bytes (did you open the file in text mode?)

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

Exception Location: /usr/lib/python3.3/csv.py in fieldnames, line 96

推荐答案

request.FILES 给你二进制文件,但 csv 模块想改为使用文本模式文件。

request.FILES gives you binary files, but the csv module wants to have text-mode files instead.

您需要将文件包装在 io.TextIOWrapper()实例,您需要找出编码:

You need to wrap the file in a io.TextIOWrapper() instance, and you need to figure out the encoding:

from io import TextIOWrapper

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

如果您从中获取 charset 参数可能会更好内容类型标题(如果提供)这就是客户端告诉你字符集是什么。

It'd probably be better if you took the charset parameter from the Content-Type header if provided; that is what the client tells you the character set is.

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

You cannot work around needing to know the correct encoding for the file data; you can force interpretation as ASCII, for example, by providing a errors keyword as well (setting it to 'replace' or 'ignore'), but that does lead to data loss:

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

使用TextIOWrapper只能在使用Django 1.11和更高版本(如这个变更集添加了所需的支持)。在早期版本中,您可以在以下事实之后修改支持:

Using TextIOWrapper will only work when using Django 1.11 and later (as this changeset added the required support). In earlier versions, you can monkey-patch the support in after the fact:

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

这篇关于如何解决“迭代器应该返回字符串而不是字节”的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆