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

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

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

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

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

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

Вау! Понятно, что я до сих пор не привык к первоклассным функциям. Вот что я получил – благодаря Unknown для предоставления ключа!

 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__ , поскольку вы не можете переназначить этот метод.

Сомнительно полезные хакеры 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 

@Unknown Какая разница между вашим и этим:

 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 

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

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

 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 

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

Попытайтесь вспомнить уникальные имена для вашего метода classmethod и instance. т.е.

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

Вы можете переназначить свой метод идентификации в 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