Описание и то, что я пробовал:

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

Однако я не нашел обсуждения, связанного с привязкой метода к самому классу. Я могу думать об обходных путях, но мне любопытно, есть ли простой способ достигнуть следующего:

import types
def quacks(some_class):
    def quack(self, number_of_quacks):
       self.number_of_quacks = number_of_quacks
    setattr(some_class, "quack", types.MethodType(quack, some_class))
    return some_class

@quacks
class Duck:
   pass

Но выше не сработает

d1 = Duck()
d2 = Duck()
d1.quack(1)
d2.quack(2)
print(d2.number_of_quacks)
# 2
print(d1.number_of_quacks)
# 2

Потому что кряк фактически изменяет сам класс, а не экземпляр.

Есть два обходных пути, о которых я могу подумать. Либо что-то вроде ниже:

class Duck:
    def __init__(self):
        setattr(self, "quack", types.MethodType(quack, self))

Или что-то вроде

class Quacks:
    def quack(self, number_of_quacks):
        self.number_of_quacks = number_of_quacks

class Duck(Quacks):
    pass

< Сильный > Вопрос : Итак, мой вопрос: есть ли простой способ получить простой декоратор класса @quacks, который я описал выше?

Почему я спрашиваю: Я намерен создать набор функций для модульного добавления общих методов, которые я использую к классам. Если я не выйду из этого проекта, список, вероятно, со временем будет расти, и я бы предпочел, чтобы он выглядел хорошо при определении кода. И что касается вкуса, я думаю, что вариант 1 ниже выглядит лучше, чем вариант 2:

# option 1
@quacks
@walks
@has_wings
@is_white
@stuff
class Duck:
    pass

# option 2
class Duck(
  Quacks,
  Walks,
  HasWings,
  IsWhite,
  Stuff):
    pass
1
ozgeneral 5 Апр 2020 в 18:31

2 ответа

Лучший ответ

Нашел другой обходной путь, я думаю, что ниже сделаю это для меня.

def quacks(some_class):
    def quack(self, number_of_quacks):
        self.number_of_quacks = number_of_quacks

    old__init__ = some_class.__init__
    def new__init__(self, *args, **kwargs):
        setattr(self, "quack", types.MethodType(quack, self))
        old__init__(self, *args, **kwargs)
    setattr(some_class, "__init__", new__init__)
    return some_class

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

Редактировать : менее хакерский путь, вдохновленный ответом @ SethMMorton:


def quack(self, number_of_quacks):
    self.number_of_quacks = number_of_quacks

def add_mixin(some_class, some_fn):
    new_class = type(some_class.__name__, (some_class,), {
        some_fn.__name__: some_fn
    })
    return new_class

def quacks(some_class):
    return add_mixin(some_class, quack)

@quacks
class Duck:
    pass

d1 = Duck()
d2 = Duck()
d1.quack(1)
d2.quack(2)
print(d1.number_of_quacks)
print(d2.number_of_quacks)
0
ozgeneral 5 Апр 2020 в 17:17

Если вы не возражаете против изменения желаемого синтаксиса полностью , чтобы получить желаемую функциональность, вы можете динамически создавать классы с помощью type (см. вторую подпись).

Первый аргумент - это имя класса, второй - кортеж суперклассов, а третий - словарь добавляемых атрибутов.

Duck = type("Duck", (), {
    "quack", quack_function,
    "walk", walk_function,
    ...
})

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

1
SethMMorton 5 Апр 2020 в 16:34