Метод класса, который ведет себя по-разному, когда вызывается как метод экземпляра? - PullRequest
26 голосов
/ 14 мая 2009

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

Например, в качестве проекта по повышению квалификации я пишу класс Matrix (да, я знаю, что там уже есть очень хорошие матричные классы). Я создал для него метод класса с именем identity, который возвращает единичную матрицу указанного размера.

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

Другими словами, я хотел бы определить метод, который может определить, был ли он вызван через экземпляр и, если да, получить доступ к атрибутам этого экземпляра. К сожалению, даже после просмотра документации и нескольких поисков в Google я не нашел ничего, что бы предполагало, что это возможно. Кто-нибудь знает по-другому?

Edit:

Вау! Очевидно, я все еще не совсем привык к первоклассным функциям. Вот чем я закончил - спасибо Неизвестному за предоставленный ключ!

class Foo(object):
    def __init__(self, bar):
        self.baz = bar
        self.bar = MethodType(lambda self: self.__class__.bar(self.baz), self, self.__class__)

    @classmethod
    def bar(cls, baz):
        return 5 * baz

Foo.bar(3) # returns 15

foo = Foo(7)
foo.bar() # returns 35

Редактировать 2:

Просто небольшое замечание - этот метод (и большинство из представленных ниже) не будет работать с классами, которые определяют __slots__, так как вы не можете переназначить метод.

Ответы [ 5 ]

38 голосов
/ 14 мая 2009

Сомнительно полезные взломы Python - моя сильная сторона.

from types import *

class Foo(object):
    def __init__(self):
        self.bar = methodize(bar, self)
        self.baz = 999

    @classmethod
    def bar(cls, baz):
        return 2 * baz


def methodize(func, instance):
    return MethodType(func, instance, instance.__class__)

def bar(self):
    return 4*self.baz


>>> Foo.bar(5)
10
>>> a=Foo()
>>> a.bar()
3996
7 голосов
/ 14 мая 2009

@ Неизвестно В чем разница между вашим и этим:

class Foo(object):

    def _bar(self, baz):
        print "_bar, baz:", baz

    def __init__(self, bar):
        self.bar = self._bar
        self.baz = bar

    @classmethod
    def bar(cls, baz):
        print "bar, baz:", baz

In [1]: import foo

In [2]: f = foo.Foo(42)

In [3]: f.bar(1)
_bar, baz: 1

In [4]: foo.Foo.bar(1)
bar, baz: 1

In [5]: f.__class__.bar(1)
bar, baz: 1
7 голосов
/ 14 мая 2009

[отредактировано: используйте атрибут для более прямого ответа; см. полезный комментарий Джона Фухи]

Вы можете использовать дескриптор, чтобы делать то, что вы хотите:

class cls_or_inst_method(object):
    def __init__(self, class_method, instance_method):
        self.class_method = class_method
        self.instance_method = instance_method

    def __get__(self, obj, objtype):
        if obj is None:
            return self.class_method
        else:
            return lambda: self.instance_method(obj)

def my_class_method(baz):
    return baz + 1

def my_instance_method(self):
    return self.baz * 2

class Foo(object):
    baz = 10
    bar = cls_or_inst_method(my_class_method, my_instance_method)

Используя вышеупомянутое:

>>> print Foo.bar(5)
6
>>> my_foo = Foo()
>>> print my_foo.bar()
20
3 голосов
/ 14 мая 2009

Я думаю, что большая проблема в том, что вы перегружаете имя 'bar' в классе 'Foo', что Python не позволяет Второе определение «бара» перекрывает первое определение «бара».

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

@classmethod
def create(cls, baz):
   ...

def rubber_stamp(self):
   ...
2 голосов
/ 14 мая 2009

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

class Matrix(object):
    def __init__(self):
        self.identity = lambda s=self:s.__class__.identity(s)

        #...whatever initialization code you have...
        self.size = 10        

    @classmethod
    def identity(self, other):
        #...always do you matrix calculations on 'other', not 'self'...
        return other.size


m = Matrix()
print m.identity()
print Matrix.identity(m)

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

    def identity(self):
        self.__class__.indentity(self)
    self.identity = identity
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...