Не могу использовать partial как str [дубликат]


На этот вопрос уже есть ответ здесь:

Я столкнулся с этой проблемой, пытаясь написать красивую процедуру печати для программы, в которой я использую несколько именованных кортежей, содержащих пары с плавающей запятой.
from collections import namedtuple
Position = namedtuple('Position', 'x y')
Vector = namedtuple('Vector', 'x y')
Size = namedtuple('Size', 'width height')

Я хочу отформатировать числа с плавающей запятой при печати, потому что результат:

import math
print(Position(math.pi, math.pi), Vector(math.pi, math.pi), Size(math.pi, math.pi))

Слишком длинно:

Position(x=3.141592653589793, y=3.141592653589793) Vector(x=3.141592653589793, y=3.141592653589793) Size(width=3.141592653589793, height=3.141592653589793)

Поэтому я создал функцию для вывода именованных кортежей:

def pretty_float_pair(name, labels, obj):
    """
    If labels = ('a', 'b') and object = (1.2345, 1.2345) returns:
        'name(a=1.23, b=1.23)'
    """
    return '{}({}={:.2f}, {}={:.2f})'.format(name, labels[0], obj[0], labels[1], obj[1])

Имя и метки должны быть фиксированы для каждого типа, и только аргумент obj изменяется, поэтому я подумал, что могу использовать functools partial.

from functools import partial
Position.__str__ = partial(pretty_float_pair, 'Position', ('x', 'y'))
Vector.__str__ = partial(pretty_float_pair, 'Vector', ('x', 'y'))
Size.__str__ = partial(pretty_float_pair, 'Size', ('width', 'height'))
print(Position(math.pi, math.pi), Vector(math.pi, math.pi), Size(math.pi, math.pi))

Но это бросает TypeError: pretty_float_pair() missing 1 required positional argument: 'obj'.

Удивительно, если я использую лямбду для создания функций, она работает.

Position.__str__ = lambda x: pretty_float_pair('Position', ('x', 'y'), x)
Vector.__str__ = lambda x: pretty_float_pair('Vector', ('x', 'y'), x)
Size.__str__ = lambda x: pretty_float_pair('Size', ('width', 'height'), x)
print(Position(math.pi, math.pi), Vector(math.pi, math.pi), Size(math.pi, math.pi))

Печатает то, что я хотел:

Position(x=3.14, y=3.14) Vector(x=3.14, y=3.14) Size(width=3.14, height=3.14)

Я пытаюсь понять, почему частичная версия не работает. работа.

2 2

2 ответа:

functools.partial возвращает не вызываемый дескриптор, грубо эквивалентный несвязанному методу. Это означает, что ему не передается параметр self, который согласуется с ошибкой, которую вы видите.

Поскольку лямбда ведет себя точно так же, как регулярная функция, определенная с помощью def, она фактически является дескриптором. Метод __get__ лямбды возвращает связанную версию, которая передается в экземпляре как x.

Чтобы получить частичную функцию, которая ведет себя больше как метод, используйте functools.partialmethod вместо этого. Вам нужно будет переместить obj в начало списка аргументов, чтобы он мог получить self, когда метод привязан.

Вот ваш пример:

from functools import partialmethod

def pretty_float_pair(obj, name, labels):
    """
    If labels = ('a', 'b') and object = (1.2345, 1.2345), returns:
        name(a=1.23, b=1.23)
    """
    return '{}({}={:.2f}, {}={:.2f})'.format(name, labels[0], obj[0], labels[1], obj[1])

Position.__str__ = partialmethod(pretty_float_pair, 'Position', ('x', 'y'))
Vector.__str__ = partialmethod(pretty_float_pair, 'Vector', ('x', 'y'))
Size.__str__ = partialmethod(pretty_float_pair, 'Size', ('width', 'height'))

print(Position(math.pi, math.pi), Vector(math.pi, math.pi), Size(math.pi, math.pi))

Функции

Получают свой неявный аргумент self, будучи дескрипторами : lookup x.f конструирует и возвращает объект метода, который запоминает x, чтобы предоставить его f. functools.partial(...) не возвращает дескриптор, поэтому он не получает специального обращения. (На самом деле это класс, поэтому он "возвращает" экземпляр самого себя.)