Использование functools.partial для создания пользовательских фильтров для ошибки атрибута получения pdfquery

Фон

Я использую pdfquery для анализа нескольких файлов, таких как этот.

Проблема

Я пытаюсь написать обобщенную функцию фильтрации, построенную на основе пользовательских селекторов, упомянутых в pdfquery's docs, который может принимать в качестве аргумента определенный диапазон. Поскольку на this ссылаются, я подумал, что смогу обойти это, предоставив частичную функцию, используя functools.partial (как показано ниже)

Вход

import pdfquery
import functools

def load_file(PDF_FILE):
    pdf = pdfquery.PDFQuery(PDF_FILE)
    pdf.load()
    return pdf

file_with_table = 'Path to the file mentioned above'
pdf = load_file(file_with_table)


def elements_in_range(x1_range):
    return in_range(x1_range[0], x1_range[1], float(this.get('x1',0)))

x1_part = functools.partial(elements_in_range, (95,350))

pdf.pq('LTPage[page_index="0"] *').filter(x1_part)

Но когда я это делаю, я получаю следующую ошибку атрибута;

Выход

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
C:\Anaconda3\lib\site-packages\pyquery\pyquery.py in filter(self, selector)
    597                     if len(args) == 1:
--> 598                         func_globals(selector)['this'] = this
    599                     if callback(selector, i, this):

C:\Anaconda3\lib\site-packages\pyquery\pyquery.py in func_globals(f)
     28 def func_globals(f):
---> 29     return f.__globals__ if PY3k else f.func_globals
     30 

AttributeError: 'functools.partial' object has no attribute '__globals__'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
<ipython-input-74-d75c2c19f74b> in <module>()
     15 x1_part = functools.partial(elements_in_range, (95,350))
     16 
---> 17 pdf.pq('LTPage[page_index="0"] *').filter(x1_part)

C:\Anaconda3\lib\site-packages\pyquery\pyquery.py in filter(self, selector)
    600                         elements.append(this)
    601             finally:
--> 602                 f_globals = func_globals(selector)
    603                 if 'this' in f_globals:
    604                     del f_globals['this']

C:\Anaconda3\lib\site-packages\pyquery\pyquery.py in func_globals(f)
     27 
     28 def func_globals(f):
---> 29     return f.__globals__ if PY3k else f.func_globals
     30 
     31 

AttributeError: 'functools.partial' object has no attribute '__globals__'

Есть ли способ обойти это? Или, возможно, какой-то другой способ написать собственный селектор для pdfquery, который может принимать аргументы?


person James Draper    schedule 24.08.2017    source источник


Ответы (2)


Как насчет того, чтобы просто использовать функцию для возврата новой функции (в некотором роде похожую на functools.partial), но вместо этого использовать замыкание?

import pdfquery

def load_file(PDF_FILE):
    pdf = pdfquery.PDFQuery(PDF_FILE)
    pdf.load()
    return pdf

file_with_table = './RG234621_90110.pdf'
pdf = load_file(file_with_table)

def in_range(x1, x2, sample):
    return x1 <= sample <= x2

def in_x_range(bounds):
    def wrapped(*args, **kwargs):
        x = float(this.get('x1', 0))
        return in_range(bounds[0], bounds[1], x)
    return wrapped

def in_y_range(bounds):
    def wrapped(*args, **kwargs):
        y = float(this.get('y1', 0))
        return in_range(bounds[0], bounds[1], y)
    return wrapped


print(len(pdf.pq('LTPage[page_index="0"] *').filter(in_x_range((95, 350))).filter(in_y_range((60, 100)))))

# Or, perhaps easier to read

x_check = in_x_range((95, 350))
y_check = in_y_range((60, 100))

print(len(pdf.pq('LTPage[page_index="0"] *').filter(x_check).filter(y_check)))

ВЫВОД

1
16 # <-- bounds check is larger for y in this test

Вы можете параметризовать свойство, которое вы сравниваете

import pdfquery

def load_file(PDF_FILE):
    pdf = pdfquery.PDFQuery(PDF_FILE)
    pdf.load()
    return pdf

file_with_table = './RG234621_90110.pdf'
pdf = load_file(file_with_table)

def in_range(prop, bounds):
    def wrapped(*args, **kwargs):
        n = float(this.get(prop, 0))
        return bounds[0] <= n <= bounds[1]
    return wrapped


print(len(pdf.pq('LTPage[page_index="0"] *').filter(in_range('x1', (95, 350))).filter(in_range('y1', (60, 100)))))

x_check = in_range('x1', (95, 350))
y_check = in_range('y1', (40, 100))

print(len(pdf.pq('LTPage[page_index="0"] *').filter(x_check).filter(y_check)))

Я бы также предложил использовать аргумент parse_tree_cacher, так как это ускорило поиск подходящего решения (хотя вам может не потребоваться частая повторная обработка, как это делал я, когда выяснял это).

import pdfquery
from pdfquery.cache import FileCache

def load_file(PDF_FILE):
    pdf = pdfquery.PDFQuery(PDF_FILE, parse_tree_cacher=FileCache("/tmp/"))
    pdf.load()
    return pdf
person sberry    schedule 24.08.2017
comment
Благодарность! +1 к решению параметризованного свойства. Это сэкономит мне столько времени. - person James Draper; 24.08.2017

Хотя мне нравится подход с закрытием, я действительно должен упомянуть, что вы можете копировать атрибуты из вашей обернутой функции в свою оболочку.

from functools import update_wrapper

custom_filter = update_wrapper(
    partial(
        elements_in_range, (95, 20)
    ),
    wrapped=elements_in_range,
    assigned=('__globals__', '__code__')
)
person Oluwafemi Sule    schedule 24.08.2017