Найти - Пользователи
Полная версия: Проблема с Pillow
Начало » Python для экспертов » Проблема с Pillow
1 2
paradox81ru
Привет всем.
Я делаю свою графическую капчу, и для ее реализации требуется пакет для работы с растровой графикой. Самый популярный пакет, это Pillow, и с помощью него я собственно капчу и сделал. И на моем компе в тестовом режиме эта капча вполне себе нормально работала. Но потом пришло время проверить свою капчу на боевом сервере… И вот тут то и возникла проблема, капча не проявилась, а при запросе этой капчи возвращается ошибка 500. Я стал изучать логи вэбсервера, и нашел вот такую интересную ошибку:
 Exception ignored in: <object repr() failed>
Traceback (most recent call last):
  File "D:\pyprojects\aaa-portal\pythonProject\venv\Lib/site-packages\PIL\Image.py", line 587, in __del__
NameError: name 'hasattr' is not defined

Как видно, ошибка весьма нетипичная, так как ссылается на отсутствие встроенной функции ‘hasattr’. Далее я нашел эту же ошибку и на своем тестовом сервере, которая, вправду, в отличии от боевого сервера, все таки не мешает отобразить капчу. Но на боевом сервере картинка не отображается. Попытки найти информацию по данной проблеме на просторах интернета ни к чему не привели, надо сказать что подобный вопрос я вообще нашел всего в двух местах, причем один из них без ответа, а второй ответ не помог. Мои личные поиски решения проблемы так же ни к чему не привели, и в какую сторону копать я уже больше и не знаю.
В связи с вышесказанным, я не очень то уже надеюсь, что кто-то подскажет как решить данную проблему, но мог бы тогда кто-нибудь подсказать какую-нибудь реальную альтернативу пакету Pillow? Только что-бы она работала под питоном 3.6.
FishHook
paradox81ru
Питоний вебсервер под виндой - это странное, очень странное решение, объяснений которому крайне трудно придумать.
Можеть быть вам не искать замену Pillow, а поискать замену Windows?
paradox81ru
Это тестовый сервер на Windows, а рабочий на Ubuntu 14.04, еще умные мысли есть?
FishHook
paradox81ru
а рабочий на Ubuntu 14.04
Ну и? Есть на нем ошибка? Если нет, то проблему считаю решенной.
paradox81ru
FishHook
Ну и? Есть на нем ошибка? Если нет, то проблему считаю решенной.
Я же написал, что ошибка эта есть на обоих серверах, только под виндами капча все таки отображается, а на рабочем возвращает ошибку 500.
Вот лог с убунты, чтобы винды не смущали:
 Exception ignored in: <object repr() failed>
Traceback (most recent call last):
  File "/var/pyprojects/aaa-portal/pythonProject/venv3.6/lib/python3.6/site-packages/PIL/Image.py", line 587, in __del__
NameError: name 'hasattr' is not defined
Rodegast
> “/var/pyprojects/aaa-portal/pythonProject/venv3.6/lib/python3.6/site-packages/PIL/Image.py”, line 587

Ты смотрел что там написано? Какая версия PIL? Где исходник? virtual event использовать не нужно.
paradox81ru
Rodegast
Ты смотрел что там написано? Какая версия PIL? Где исходник? virtual event использовать не нужно.
Да смотрел, проверяется некий атрибут ‘fp’ в своем классе Image, и вроде как не находит, и создает его со значением None. Смотрел это в дебагере в PyCharm.
 class Image(object):
   ...
    if sys.version_info >= (3, 4, 0):
        def __del__(self):
            if (hasattr(self, 'fp') and hasattr(self, '_exclusive_fp')
               and self.fp and self._exclusive_fp):
                self.fp.close()
            self.fp = None
Версия Pillow 5, до этого была 4.x какая-то там, было то же самое, почему и решил попробовать обновить ее до 5 версии, но проблему не решило. А что, виртуальное окружение действительно проблемы может создавать?
https://github.com/python-pillow/Pillow/blob/master/src/PIL/Image.py
Да, так, на всякий случай, Pillow ставил через pip, а не c гитхаба, чтобы чего не подумали.
Rodegast
> https://github.com/python-pillow/Pillow/blob/master/src/PIL/Image.py

Вообще-то я имел в виду исходник твоей капчи.
paradox81ru
Rodegast
Вообще-то я имел в виду исходник твоей капчи.

 import os
import random
from PIL import Image, ImageDraw, ImageFont, ImageChops, ImageColor, ImageFilter
from django.conf import settings
class Captcha:
    def __init__(self, destination,
                 trash_range=15,
                 code_range=5,
                 offset=50,
                 font_size=40,
                 base_size=(250, 50)):
        self._destination = destination
        self._trash_range = trash_range
        self._code_range = code_range
        self._offset = offset
        self._font_size = font_size
        self._base_size = base_size
        self._letters = self._letters()
        self._font_path = self._font_path()
    @classmethod
    def _random_fill(cls, red=255, green=255, blue=255):
        """ Случайный цвет RGB """
        fill = (random.randrange(red),
                random.randrange(green),
                random.randrange(blue))
        return fill
    @classmethod
    def _random_font(cls, path='fonts', s1=12, s2=35, main=None):
        """ Случайный шрифт """
        if main is None:
            size = random.randrange(s1, s2)
        else:
            size = main
        font_files = os.listdir(path)
        r_font = os.path.join(path, random.choice(font_files))
        font = ImageFont.truetype(r_font, size)
        return font
    @classmethod
    def _random_coords(cls, coords=(250, 50)):
        """ Случайные координаты """
        coords = (random.randrange(0, coords[0]),
                  random.randrange(0, coords[1]))
        return coords
    @classmethod
    def _letters(cls):
        """ Создает и возвращает кортеж символов """
        letters = (
            'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'j', 'k', 'm', 'n',
            'p', 'q', 'r', 's', 't', 'u',
            'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9'
        )
        # или, лучше воспользуемся готовыми наборами:
        # letters = tuple(string.ascii_lowercase + string.digits)
        return letters
    @classmethod
    def _font_path(cls):
        """ Возвращает папку со шрифтами"""
        return os.path.join(settings.BASE_DIR, r'common\helpers\fonts')
    def _background_img(self):
        """ Создает и возвращает фон с мусором """
        img = Image.new("RGBA", self._base_size, 'white')
        img_drw = ImageDraw.Draw(img)
#        self._draw_trash_letters(img_drw)
        self._draw_trash_lines(img_drw)
        del img_drw
        return img
    # Не используется
    def _draw_trash_letters(self, img_drw):
        """ Рисует мусор из светных символов """
        for i in range(self._trash_range):
            img_drw.font = self._random_font(self._font_path)
            img_drw.text(self._random_coords(self._base_size), random.choice(self._letters), self._random_fill())
    def _draw_trash_lines(self, img_drw):
        """
        Рисуте мусор из прямых линий
        :type img_drw: ImageDraw.Draw
        :return:
        """
        img_drw.line(self._rand_line('h'), ImageColor.getrgb("Black"), 2)
        img_drw.line(self._rand_line('h'), ImageColor.getrgb("Black"), 2)
        img_drw.line(self._rand_line('v'), ImageColor.getrgb("Black"), 2)
        img_drw.line(self._rand_line('v'), ImageColor.getrgb("Black"), 2)
    def _rand_line(self, direction):
        """
        Возвращает случайные линии горизонтальные или вертикальные
        :param direction: направление линии 'h' или 'v'
        :return:
        """
        direction = direction.lower()
        if direction not in ('h', 'v'):
            raise ValueError("Координаты могут быть только 'h' или 'v'")
        if direction == 'h':
            max_size = self._base_size[1]
            cord1 = random.randint(1, max_size-2)
            cord2 = random.randint(1, max_size-2)
            line_coord = [0, cord1, 250, cord2]
        else:
            max_size = self._base_size[0]
            cord1 = random.randint(1, max_size-2)
            cord2 = random.randint(1, max_size-2)
            line_coord = [cord1, 0, cord2, 50]
        return line_coord
    def _code_img(self, code):
        """ Создает и возвращает изображение с кодом капчи"""
        img = Image.new("RGBA", self._base_size, 'white')
        d2 = ImageDraw.Draw(img)
        x = 0
        offset = random.randrange(30, self._offset)
        for c in code:
            d2.text((x, 0), c, 'black', font=self._random_font(self._font_path, main=self._font_size))
            x += offset
        # img = im2.filter(ImageFilter.BLUR)
        del d2
        return img
    def captcha(self):
        # Если установлен режим отладки и отключена капча,
        if settings.DEBUG and settings.NO_CAPTCHA:
            # то сделаем капчу равной "12345".
            code = list('12345')
        else:
            # Иначе генерируем случайный набор символов из списка символов в пределах.
            code = [random.choice(self._letters) for i in range(self._code_range)]
        # Создаем первое изображение с мусором — случайными символами
        im1 = self._background_img()
        # Создаем вторую часть для отображения кода
        im2 = self._code_img(code)
        # Накладываем второе изображение на первое.
        img = ImageChops.multiply(im1, im2)
        # Сохраняем изображение
        img.save(self._destination, 'PNG')
        # И возвращаем строку
        return ''.join(code)
    def __call__(self, *args, **kwargs):
        return self.captcha()
Rodegast
1) Не надо пытаться что-то удалить через del. Мусорщик удалит сам то что нужно.
2) Не надо создавать несколько изображений. Т.е. должно быть что-то вроде этого:
 class Captcha:
   .....
    def _draw_trash_lines(self, img_drw):
        """
        Рисуте мусор из прямых линий
        :type img_drw: ImageDraw.Draw
        :return:
        """
        img_drw.line(self._rand_line('h'), ImageColor.getrgb("Black"), 2)
        img_drw.line(self._rand_line('h'), ImageColor.getrgb("Black"), 2)
        img_drw.line(self._rand_line('v'), ImageColor.getrgb("Black"), 2)
        img_drw.line(self._rand_line('v'), ImageColor.getrgb("Black"), 2)
    
    def _code_img(self, code, img_drw):
        """ Создает и возвращает изображение с кодом капчи"""
        x = 0
        offset = random.randrange(30, self._offset)
        for c in code:
            img_drw.text((x, 0), c, 'black', font=self._random_font(self._font_path, main=self._font_size))
            x += offset
        
    def __call__(self):
        code = "".join( random.choice(self._letters) for i in range(self._code_range) )
        # Создаем первое изображение с мусором — случайными символами
        img = Image.new("RGBA", self._base_size, 'white')
        img_drw = ImageDraw.Draw(img)
        self._draw_trash_lines(img_drw)
        # Создаем вторую часть для отображения кода
        self._code_img(code, img_drw)
        img.save(self._destination, 'PNG')
        # И возвращаем строку
        return code
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB