Функция вести себя по-разному в классе против экземпляра - PullRequest
0 голосов
/ 30 июня 2018

Я бы хотел, чтобы определенная функция вызывалась как метод класса и работала иначе, когда она вызывается в экземпляре.

Например, если у меня есть class Thing, я хочу, чтобы Thing.get_other_thing() работал, но также thing = Thing(); thing.get_other_thing() вел себя по-другому.

Я думаю, что перезапись get_other_thing метода при инициализации должна сработать (см. Ниже), но это выглядит немного странно. Есть ли лучший способ?

class Thing:

    def __init__(self):
        self.get_other_thing = self._get_other_thing_inst()

    @classmethod
    def get_other_thing(cls):
        # do something...

    def _get_other_thing_inst(self):
        # do something else

Ответы [ 2 ]

0 голосов
/ 30 июня 2018

Отличный вопрос! То, что вы ищете, может быть легко сделано с помощью дескрипторов .

Дескрипторы - это объекты Python, которые реализуют протокол дескриптора , обычно начинающийся с __get__().

Они существуют, в основном, для установки в качестве атрибута класса в разных классах. При обращении к ним вызывается их метод __get__() с передачей класса экземпляра и владельца.

class DifferentFunc:
    """Deploys a different function accroding to attribute access

    I am a descriptor.
    """

    def __init__(self, clsfunc, instfunc):
        # Set our functions
        self.clsfunc = clsfunc
        self.instfunc = instfunc

    def __get__(self, inst, owner):
        # Accessed from class
        if inst is None:
            return self.clsfunc.__get__(None, owner)

        # Accessed from instance
        return self.instfunc.__get__(inst, owner)


class Test:
    @classmethod
    def _get_other_thing(cls):
        print("Accessed through class")

    def _get_other_thing_inst(inst):
        print("Accessed through instance")

    get_other_thing = DifferentFunc(_get_other_thing,
                                    _get_other_thing_inst)

А теперь результат:

>>> Test.get_other_thing()
Accessed through class
>>> Test().get_other_thing()
Accessed through instance

Это было легко!

Кстати, вы заметили, что я использовал __get__ в функции класса и экземпляра? Угадай, что? Функции также являются дескрипторами, и вот как они работают!

>>> def func(self):
...   pass
...
>>> func.__get__(object(), object)
<bound method func of <object object at 0x000000000046E100>>

После доступа к атрибуту функции вызывается его __get__, и таким образом вы получаете привязку функции.

Для получения дополнительной информации я настоятельно рекомендую прочитать руководство по Python и "How-To" , ссылки на которые приведены выше. Дескрипторы являются одной из самых мощных функций Python и едва известны.


Почему бы не установить функцию при создании экземпляра?

или Почему бы не установить self.func = self._func внутри __init__?

Установка функции при создании экземпляра сопряжена с рядом проблем:

  1. self.func = self._func вызывает циклическую ссылку. Экземпляр хранится внутри объекта функции, возвращаемого self._func. Это, с другой стороны, сохраняется в экземпляре во время назначения. Конечный результат заключается в том, что экземпляр ссылается на себя и очищается гораздо медленнее и тяжелее.
  2. Другой код, взаимодействующий с вашим классом, может попытаться вывести функцию прямо из класса и использовать __get__(), который является обычным ожидаемым методом, чтобы связать ее. Они получат неправильную функцию.
  3. Не будет работать с __slots__.
  4. Хотя с дескрипторами необходимо понимать механизм, его установка на __init__ не так чиста и требует установки нескольких функций на __init__.
  5. Занимает больше памяти. Вместо того, чтобы хранить одну функцию, вы сохраняете связанную функцию для каждого экземпляра.
  6. Не будет работать с свойствами .

Есть еще много чего, что я не добавил по мере того, как список можно продолжать и продолжать.

0 голосов
/ 30 июня 2018

Вот немного хакерское решение:

class Thing(object):
    @staticmethod
    def get_other_thing():
        return 1

    def __getattribute__(self, name):
        if name == 'get_other_thing':
            return lambda: 2
        return super(Thing, self).__getattribute__(name)

print Thing.get_other_thing()  # 1
print Thing().get_other_thing()  # 2

Если мы находимся в классе, выполняется статический метод. Если мы находимся в экземпляре, сначала выполняется __getattribute__, поэтому мы можем вернуть не Thing.get_other_thing, а какую-то другую функцию (lambda в моем случае)

...