Метод класса Python также метод экземпляра

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

например что-то типа


class MyClass(object):
    attribs = 1, 2, 3

    def myMethod(self, args):
        if isclass(self):
            "do class stuff"
        else:
            "do instance stuff"


MyClass.myMethod(2) #should now be called as a class method, e.g. I would normally do @classmethod

MyClass().myMethod(2) #should now be called as instance method

Конечно, я мог бы объявить его как статический метод и явно передать либо экземпляр, либо объект класса, но это кажется довольно непитоновским и недружественным для пользователя.


person Torilla    schedule 24.12.2019    source источник
comment
Хотя это можно сделать (создав дескриптор, похожий на classmethod), назначение методов класса и обычных методов обычно сильно различается. Тщательно подумайте, не перевешивает ли простота вызова одного унифицированного метода простоту понимания того, что делает каждый отдельный метод.   -  person MisterMiyagi    schedule 24.12.2019


Ответы (2)


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

class MyCrazyClass:
    @classmethod
    def magicmeth(cls):
        print("I'm a class")

    def _magicmeth(self):
        print("I'm an instance")

    def __init__(self):
        self.magicmeth = self._magicmeth
person Jonathon Reinhart    schedule 24.12.2019
comment
Хорошо, спасибо, это должно сработать. Это по-прежнему считается приемлемым стилем программирования или слишком сильно скрывает то, что происходит в фоновом режиме? - person Torilla; 24.12.2019
comment
Честно говоря, весь ваш запрос кажется довольно странным, и я не могу сказать, что когда-либо видел, чтобы этот шаблон использовался раньше. Это не плохо, просто странно. Возможно, если бы ваш вопрос был немного более явным в отношении того, что именно вы пытаетесь сделать, было бы легче сделать соответствующее предложение. - person Jonathon Reinhart; 24.12.2019
comment
Ну, дело в том, что по умолчанию я работаю только с объектами класса, а объекты экземпляра вступают в игру только в определенных особых случаях, когда требуется дополнительная информация об объекте класса. Рассмотрим этот пример: обычно физическая единица измерения представляет собой что-то вроде значения*коэффициента (1 метр), и вы можете преобразовать ее в другую единицу, просто сделав newval = oldval * oldfactor/newfactor. Но иногда вам, возможно, также понадобится мощность, например (1 Meter**2, которая в моем случае будет экземпляром класса Meter), но тогда для преобразования потребуется знать мощность экземпляра. - person Torilla; 24.12.2019
comment
@Torilla Обратите внимание, что квадратный метр не имеет тот же тип, что и метр. Meter*Meter такой же, как Meter, как и Meter*Second, а именно совсем нет. Если вы моделируете такие юниты, ваша система юнитов сломана. - person MisterMiyagi; 24.12.2019
comment
Я не совсем уверен, что вы имеете в виду. Я рассматриваю любую единицу (единицу СИ, если уж на то пошло) как отдельное измерение. Например. У меня есть четко определенный базовый класс для длины, времени, массы и т. д. Все это очень хорошо. Проблема возникает при определении единицы из нескольких основных единиц (например, Джоуль == кг м²с⁻²), когда можно оперировать с объектом класса, определенным его основными единицами, но также можно добавить мощность (например, Дж² == кг² м⁴с⁻⁴ = = (кг м²с⁻²)² и т. д.). - person Torilla; 24.12.2019
comment
Просто моделируйте композиты как композиты, даже если их компоненты одинаковы. Итак, m*m работает так же, как m*s. Затем вы также можете смоделировать J*J теми же средствами. - person MisterMiyagi; 24.12.2019
comment
На самом деле это именно то, что я делаю, у меня возникают проблемы, когда, например, я убеждаюсь, что 1Js == kg m²s == 1W, например. класс должен знать о своих потенциальных псевдонимах, которые были определены ранее, и он не должен зависеть от того, что пользователь использует в качестве базовых единиц, если тип создаваемой единицы одинаков. - person Torilla; 24.12.2019
comment
@Torilla Совершенно неясно, где здесь играет роль различие между типами (классом) и значениями (экземпляром). Базовая единица, такая как m, в основном такая же, как производная единица, такая как J, — вы можете свободно выбирать, какая из них является базовой, а какая — производной. Либо они все типы, либо все значения. - person MisterMiyagi; 24.12.2019

Вы можете определить декоратор, который работает как обычный метод при вызове экземпляра или метод класса при вызове класса. Для этого требуется дескриптор:

from functools import partial

class anymethod:
    """Transform a method into both a regular and class method"""
    def __init__(self, call):
        self.__wrapped__ = call

    def __get__(self, instance, owner):
        if instance is None:  # called on class
            return partial(self.__wrapped__, owner)
        else:                 # called on instance
            return partial(self.__wrapped__, instance)


class Foo:
    @anymethod
    def bar(first):
        print(first)

Foo.bar()    # <class '__main__.Foo'>
Foo().bar()  # <__main__.Foo object at 0x106f86610>

Обратите внимание, что такое поведение не будет очевидным для большинства программистов. Используйте его только в том случае, если он вам действительно нужен.

person MisterMiyagi    schedule 24.12.2019