Метод класса, который ведет себя по-разному при вызове метода экземпляра?
Мне интересно, можно ли сделать метод, который ведет себя по-разному, когда вызывается как метод класса, чем при вызове как метод экземпляра.
Например, в качестве проекта улучшения навыков я пишу класс Matrix
(да, я знаю, что есть уже неплохие классы матриц уже там). Я создал метод класса для него под названием identity
, который возвращает идентификационную матрицу указанного размера.
Теперь, когда вызывается экземпляр Matrix
, кажется логичным, что размер не нужно указывать; он должен вернуть идентификационную матрицу того же размера, что и вызываемый Matrix
.
Другими словами, я хотел бы определить метод, который может определить, был ли он вызван через экземпляр и, если это так, получить доступ к этим атрибутам экземпляра. К сожалению, даже после проработки документации и нескольких поисковых запросов Google я не нашел ничего, что предполагает, что это возможно. Кто-нибудь знает по-другому?
Edit:
Ничего себе! Понятно, что я до сих пор не привык к первоклассным функциям. Вот что я получил - благодаря 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__
, поскольку вы не можете переназначить этот метод.
Ответы
Ответ 1
Наверно, полезные хаки 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
Ответ 2
[отредактирован: использовать атрибут для более прямого ответа; см. полезный комментарий Джона Фухи]
Вы можете использовать дескриптор, чтобы делать то, что хотите:
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
@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
Ответ 4
Я думаю, что большая проблема заключается в том, что вы перегружаете имя "bar" в классе "Foo", что-то вроде python не позволяет. Второе определение "bar" сжимает первое определение "bar" .
Попробуйте вспомнить уникальные имена для метода classmethod и instance. то есть.
@classmethod
def create(cls, baz):
...
def rubber_stamp(self):
...
Ответ 5
Вы можете переназначить свой метод идентификации в 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