2016-03-23 5 views
3

Sagen, ich habe diese Klasse:Anfügen an eine Mitgliederliste, wenn in Python Subklassen

class FooClass(object): 
    foos = ["foo", "bar"] 

    def do_foos(self): 
     for foo in self.foos: 
      print("I have a " + foo) 

    # ... 

Ich möchte ein SpecialisedFooClass für diese Klasse zu schaffen, die FooClass erstreckt, das Hinzufügen eines Elements "spec"-foos (dh so dass foos enthält ["foo", "bar", "spec"]).

SpecialisedFooClass.foos sollte auf FooClass.foos abhängig: Wenn ich FooClass 'Definition ändern, so dass foos enthält ["foo", "bam", "bat"], SpecialisedFooClass.foos sollte dann ["foo", "bam", "bat", "spec"] enthalten.


Dies ist die beste Art, wie ich mit so weit habe kommen:

class SpecialisedFooClass(FooClass): 
    foos = FooClass.foos + ["spec"] 

Aber ich finde die ausdrückliche Bezugnahme auf FooClass über. Wenn ich beschließe, eine Zwischenunterklasse hinzuzufügen (d. H. Wenn sich die Oberklasse SpecialisedFooClass ändert), werde ich unweigerlich vergessen, diese Referenz zu aktualisieren. Ich habe tatsächlich diesen Fehler IRL mit der Codebasis gemacht, an der ich arbeite (die nicht tatsächlich mit foos, bams und Fledermäusen beschäftigt ...).


Es gibt eigentlich keine spezielle Anforderung in meinem Fall, dass foos ein Klassenmitglied ist eher als eine Instanz Mitglied, so würde dies auch funktionieren, aber ich finde es hässlich. Auch der super Aufruf hat immer noch eine explizite Klassenreferenz - weniger beunruhigend hier, weil es zu der Klasse in der es erscheint.

class FooClass(object): 
    def __init__(self, *args, **kwargs): 
     self.foos = ["foo", "bar"] 

    def do_foos(self): 
     for foo in self.foos: 
      print("I have a " + foo) 

class SpecialisedFooClass(FooClass): 
    def __init__(self, *args, **kwargs): 
     super(SpecialisedFooClass, self).__init__(*args, **kwargs) 
     self.foos.append("spec") 

Welche anderen Möglichkeiten gibt es, und gibt es eine "Pythonic" Weg, dies zu tun?

Antwort

2

Es gibt ein paar Möglichkeiten, wie Sie damit umgehen können. Eine Option, wenn Attribute ein gewisses Maß an Berechnung erfordern, ist es konvertieren zu einem property

class FooClass(object): 
    foos = ["foo", "bar"] 


class SpecializedFooClass(FooClass) 

    @property 
    def foos(self): 
     return super(SpecializedFooClass, self).foos + ['spec'] 

Sie können einige Optimierungen tun, so dass es nur einmal berechnet ist, oder so, dass sie zur Laufzeit geändert werden kann, statt die Rückkehr immer dasselbe.

class SpecializedFooClass(FooClass) 

    def __init__(self): 
     super(SpecializedFoo, self).__init__() 
     self._foos = None 

    @property 
    def foos(self): 
     if self._foos is None: 
      self._foos = super(SpecializedFooClass, self).foos + ['spec'] 
     return self._foos 

Der größte Nachteil einer Eigenschaft verwendet, ist dieser Zusammenhang ist, dass es nicht wirklich wie ein Klassenattribut verhalten mehr, da Sie eine Klasse zu instanziiert haben werden, den Wert zu erhalten.

Sie können auch metaclasses (great answer on metaclasses) verwenden.In einigen Fällen kann dies Ihre Codebasis drastisch reduzieren, aber es kann auch verwirrend sein, wenn Sie eine sehr tiefe Vererbungskette haben und es nicht klar ist, dass eine Metaklasse verwendet wird. Aber auf der positiven Seite wird es genau wie ein Klassenattribut funktionieren, weil es tatsächlich ein Klassenattribut ist.

class FooMeta(type): 

    def __new__(cls, name, bases, attrs): 
     foos = [] 
     for base in bases: 
      if hasattr(base, 'foos'): 
       foos.extend(base.foos) 
     attrs['foos'] = foos + attrs.get('foos', []) 
     return type.__new__(cls, name, bases, attrs) 


class Foo(object): 
    __metaclass__ = FooMeta 
    foos = ['one', 'two'] 


class SpecialFoo(Foo): 
    foos = ['three'] 

print Foo.foos 
# ['one', 'two'] 

print SpecialFoo.foos 
# ['one', 'two', 'three'] 

Eine dritte Option ist die Verwendung von Klasse Decorators. Es ist ein bisschen weniger Magie als Metaklassen, aber es bedeutet auch, dass Sie daran denken müssen, jede Unterklasse zu dekorieren. Es funktioniert genau wie ein Klassenattribut, weil es tatsächlich eins ist.

def set_foos(cls): 
    foos = [] 
    for base in cls.__bases__: 
     if hasattr(base, 'foos'): 
      foos.extend(base.foos) 
    cls.foos = foos + getattr(cls, 'foos', []) 
    return cls 


class FooClass(object): 
    foos = ['foo', 'bar'] 


@set_foo 
class SpecialFoo(FooClass): 
    foos = ['spec'] 
+1

Die Metaklasse Ansatz ist cool, hätte nicht gedacht! – Bahrom

+1

In diesem kleinen Beispiel ist es wahrscheinlich nicht gerechtfertigt, aber wenn dies nur die Spitze des Eisbergs ist, dann könnte es Sinn machen. –

+0

Eine weitere Option hinzugefügt - Klasse Decorators. –

2

Das könnte Sie nahe kommen:

class A(object): 
    foo_ = ['a'] 

    @classmethod 
    def foo(cls): 
     return sum((getattr(c, 'foo_', []) for c in cls.__mro__[::-1]), []) 

class B(A): 
    foo_ = ['b'] 

class C(B): 
    foo_ = ['c','d','e'] 

class D(A): 
    foo_ = ['f', 'g', 'h'] 

class E(B,D): 
    foo_ = ['i', 'j', 'k'] 


for cls in (A,B,C,D,E): 
    print cls.__name__, cls.foo() 

druckt

A ['a'] 
B ['a', 'b'] 
C ['a', 'b', 'c', 'd', 'e'] 
D ['a', 'f', 'g', 'h'] 
E ['a', 'f', 'g', 'h', 'b', 'i', 'j', 'k'] 

EDIT

auf einen Class umgewandelt, so ist es nicht notwendig zu instanziiert ist die foo Liste zu bekommen. Es wurde auch eine Diamantenvererbung hinzugefügt, um zu zeigen, dass dies aussehen würde.

Verwandte Themen