26 Stimmen

Eine Klassenmethode, die sich anders verhält, wenn sie als Instanzmethode aufgerufen wird?

Ich frage mich, ob es möglich ist, eine Methode zu erstellen, die sich beim Aufruf als Klassenmethode anders verhält als beim Aufruf als Instanzmethode.

Als Projekt zur Verbesserung der Fähigkeiten schreibe ich zum Beispiel eine Matrix Klasse (ja, ich weiß, dass es bereits sehr gute Matrixklassen gibt). Ich habe dafür eine Klassenmethode namens identity die eine Identitätsmatrix einer bestimmten Größe zurückgibt.

Nun, wenn sie auf einer Instanz von Matrix erscheint es logisch, dass die Größe nicht angegeben werden muss; sie sollte eine Identitätsmatrix der gleichen Größe wie die Matrix es wird aufgerufen.

Mit anderen Worten, ich möchte eine Methode definieren, die feststellen kann, ob sie über eine Instanz aufgerufen wurde, und, falls ja, auf die Attribute dieser Instanz zugreifen kann. Leider habe ich auch nach Durchforsten der Dokumentation und einigen Google-Suchen nichts gefunden, was darauf hindeutet, dass dies möglich ist. Weiß jemand etwas anderes?

Editer :

Wahnsinn! Offensichtlich bin ich noch nicht ganz an erstklassige Funktionen gewöhnt. So sieht das Ergebnis aus - vielen Dank an Unknown für die Bereitstellung des Schlüssels!

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

Bearbeiten 2:

Nur ein kurzer Hinweis - diese Technik (und die meisten der unten vorgestellten) funktioniert nicht bei Klassen, die __slots__ da Sie die Methode nicht neu zuordnen können.

39voto

Unknown Punkte 44574

Fragwürdig nützliche Python-Hacks sind meine Stärke.

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

7voto

Benji York Punkte 1907

[Bearbeiten: Attribut verwenden, um eine direktere Antwort zu geben; siehe den hilfreichen Kommentar von John Fouhy]

Sie können einen Deskriptor verwenden, um das zu tun, was Sie wollen:

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)

Unter Verwendung der oben genannten:

>>> print Foo.bar(5)
6
>>> my_foo = Foo()
>>> print my_foo.bar()
20

7voto

Ale Punkte 1245

@Unknown Was ist der Unterschied zwischen deinem und diesem:

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

3voto

jsamsa Punkte 909

Ich denke, das größere Problem ist, dass Sie den Namen 'bar' auf die Klasse 'Foo' überladen, was Python nicht erlaubt. Die zweite Definition von "bar" macht die erste Definition von "bar" zunichte.

Versuchen Sie, sich eindeutige Namen für Ihre Klassen- und Instanzmethode auszudenken, z. B.

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

def rubber_stamp(self):
   ...

2voto

Greg Punkte 121

Sie können Ihre Identitätsmethode neu zuordnen in init mit kurzer Lambda-Funktion:

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)

Wenn Sie mit Lambda nicht vertraut sind, wird damit eine anonyme Funktion erstellt. Das ist selten notwendig, aber es kann Ihren Code prägnanter machen. Die obige lambda-Zeile könnte umgeschrieben werden:

    def identity(self):
        self.__class__.indentity(self)
    self.identity = identity

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X