метод травления - PullRequest
       33

метод травления

3 голосов
/ 27 февраля 2012

У меня есть класс, экземпляры которого должны форматировать вывод в соответствии с инструкциями пользователя. Существует формат по умолчанию, который можно переопределить. Я реализовал это так:

class A:
  def __init__(self, params):
    # ...
    # by default printing all float values as percentages with 2 decimals
    self.format_functions = {float: lambda x : '{:.2%}'.format(x)}
  def __str__(self):
    # uses self.format_functions to format output
    # ...

a = A(params)
print(a) # uses default output formatting

# overriding default output formatting
# float printed as percentages 3 decimal digits; bool printed as Y / N
a.format_functions = {float : lambda x: '{:.3%}'.format(x),
                      bool : lambda x: 'Y' if x else 'N'}
print(a)

Это нормально? Дайте мне знать, если есть лучший способ разработать это.

К сожалению, мне нужно засечь экземпляры этого класса. Но можно выбирать только функции, определенные на верхнем уровне модуля; lambda функции не выбираются, поэтому мой атрибут экземпляра format_functions прерывает выборку.

Я попытался переписать это, чтобы использовать метод класса вместо лямбда-функций, но все же не повезло по той же причине:

class A:
  @classmethod
  def default_float_format(cls, x):
    return '{:.2%}'.format(x)
  def __init__(self, params):
    # ...
    # by default printing all float values as percentages with 2 decimals
    self.format_functions = {float: self.default_float_format}
  def __str__(self):
    # uses self.format_functions to format output
    # ...

a = A(params)
pickle.dump(a) # Can't pickle <class 'method'>: attribute lookup builtins.method failed

Обратите внимание, что травление здесь не работает, даже если я не переопределяю значения по умолчанию; просто тот факт, что я назначил self.format_functions = {float : self.default_float_format}, нарушает его.

Что делать? Я бы предпочел не загрязнять пространство имен и не нарушать инкапсуляцию, определяя default_float_format на уровне модуля.

Кстати, почему в мире pickle создает это ограничение? Это, конечно, ощущается как неоправданная и существенная боль для конечного пользователя.

Ответы [ 2 ]

5 голосов
/ 27 февраля 2012

Для выборки экземпляров или функций класса (и, следовательно, методов) выбор Python зависит от того, доступно ли их имя в виде глобальных переменных - ссылка на метод в словаре указывает на имя, которое недоступно в глобальном пространстве имен - что лучше сказать "пространство имен модуля" -

Вы можете обойти это, настроив выборку вашего класса, создав методы "__setstate__" и "__getstate__" - но я думаю, что вам будет лучше, поскольку функция форматирования не зависит от какой-либо информации об объекте или сам класс (и даже если какая-то функция форматирования делает это, вы можете передать это как параметры) и определить функцию вне области видимости класса.

Это работает (Python 3.2):

def default_float_format( x):
    return '{:.2%}'.format(x)

class A:

  def __init__(self, params):
    # ...
    # by default printing all float values as percentages with 2 decimals
    self.format_functions = {float: default_float_format}
  def __str__(self):
    # uses self.format_functions to format output
    pass

a = A(1)
pickle.dumps(a)
2 голосов
/ 20 февраля 2015

Если вы используете модуль dill, любой из ваших двух подходов будет просто "работать" как . dill может использовать lambda, а также экземпляры классов и методы классов.

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

dill - это, по сути, десять лет или около того, чтобы найти правильную функцию copy_reg, которая регистрирует, как сериализовать большинство объектов в стандартном Python. Ничего особенного или хитрого, просто нужно время. Так почему бы pickle не сделать это для нас? Почему pickle имеет такое ограничение?

Что ж, если вы посмотрите на документы pickle, ответ будет: https://docs.python.org/2/library/pickle.html#what-can-be-pickled-and-unpickled

В основном: функции и классы выбираются по ссылке.

Это означает, что pickle не работает на объектах, определенных в __main__, и также не работает на многих динамически измененных объектах. dill регистрирует __main__ как модуль, поэтому у него есть допустимое пространство имен. dill также дает вам возможность не выполнять выборку по ссылке, чтобы вы могли сериализовать динамически измененные объекты… и экземпляры классов, методы класса (связанные и несвязанные) и т. Д.

...