2016-04-26 8 views
1

Ich bin ein Website-Generator mit verschiedenen Klassen schreiben, die die Inhalte in den Web-Seiten darstellen wie Page, NewsPost, Tag, Category usw.Kontextuell Injektion statische Klasseneigenschaften in Python

Ich mag würde in der Lage sein konstruiere diese Objekte einfach, und damit habe ich kein Problem.

Allerdings möchte ich auch diese Objekte in einem bestimmten Kontext konstruieren - sagen wir der Kontext einer Website mit einer bestimmten Root-URL. Angenommen, ich lege diesen Kontext in eine Instanz einer Klasse ContentManager. Dies ist der Code, den ich hoffe, bis schließlich am Ende mit:

page = Page(title='Test Page', content='hello world!') 
assert page.cm == None 

cm = ContentManager(root_url='//localhost') 
page = cm.Page(title='Test Page', content='hello world!') 
assert page.cm == cm 

ich diese einfach verwalten kann, wenn page.cm ist eine pro-Instanz Eigenschaft in __init__ gesetzt, aber ich brauche Klassenmethoden auf cm.Page nennen, die den Zugriff auf die Notwendigkeit cm Objekt, also muss es eine statische Eigenschaft sein.

Wenn ich es nur als statische Eigenschaft auf der Klasse Page gesetzt, wäre es andereContentManager s Seiten und am Ende zu beeinflussen, was nicht erwünscht ist.

Wie würde ich das erreichen? Metaklassen? Oder eine Art Klassenfabrikfunktion?

+0

Sie machen es entweder zu einer Instanz oder zu einem Klassenattribut/-eigenschaft, es gibt keine anderen Möglichkeiten, selbst wenn Sie Metaklassenzauber einführen. Es scheint eher, dass Sie ein Instanzattribut benötigen und ein Teil Ihres Codes von Klassenmethoden in Instanzmethoden verschoben werden muss, um Zugriff auf diese Instanzvariablen zu haben. – pasztorpisti

+1

Variablen (nicht-const Daten) zu statisch/global/shared/was auch immer zu platzieren ist eine schlechte Übung im Allgemeinen (auch in anderen Programmiersprachen). Es weist normalerweise auf ungeeignetes Design hin, das neu überdacht werden muss. – pasztorpisti

+0

Ich denke, Sie haben Recht, aber angesichts der (mangelnden) Wichtigkeit und des kleinen Umfangs des Projekts, entschied ich, dass es sich lohnen würde, wenn es machbar wäre. – Andreas

Antwort

1

Eine Lösung könnte eine Unterklasse von Page für jede ContentManage Instanz erschaffen:

class Page: 
    cm = None 

    def __init__(self, title, content): 
     self.title = title 
     self.content = content 


class ContentManager: 
    def __init__(self, root_url): 
     class PerContentManagerPage(Page): 
      cm = self 

     self.Page = PerContentManagerPage 


page0 = Page(title='Test Page', content='hello world!') 

cm = ContentManager(root_url='//localhost') 
page = cm.Page(title='Test Page', content='hello world!') 

cm2 = ContentManager(root_url='//localhost') 
page2 = cm2.Page(title='Test Page 2', content='hello world!') 

assert page0.cm is None 
assert page.cm == cm 
assert page2.cm == cm2 

In Python eine Klasse ist auch ein Objekt (eine Instanz seiner Metaklasse). Diese Lösung erstellt eine neue Unterklasse von Page jedes Mal, wenn Sie ContentManager instanziieren. Dies bedeutet, dass die Klasse cm.Page nicht mit der Klasse cm2.Page identisch ist, aber beide sind die Unterklassen von Page. Aus diesem Grund ist es möglich, dass cm.Page.cm und cm2.Page.cm unterschiedliche Werte haben, da dies zwei separate Klassen (oder Klassenobjekte) sind.

Hinweis: Obwohl dies in Python durch dynamisches Erstellen von Unterklassenobjekten gelöst werden kann, haben Probleme normalerweise bessere Lösungen. Das dynamische Erstellen von Klassen/Unterklassen ist ein Warnzeichen (HACK).

Ich bin immer noch davon überzeugt, dass Sie keine Seitenunterklasse für jede Content-Manager-Instanz erstellen sollten. Stattdessen würde ich einfach Instanzen der globalen Klassen ContentManager und Page verwenden, indem ich sie in geeigneter Weise mit Referenzen zueinander verbinde und die Daten und den Code in Instanzattribute/Methoden setze.

1

Wenn Sie alles andere beiseite legen, müssen Sie nur dynamisch eine Klasse erstellen, die an jede Instanz von ContentManager gebunden wird. Wir können dies mit der eingebauten Funktion type tun, die entweder mit einem Argument den Typ eines Objekts angeben kann oder mit drei Argumenten (Klassenname, Basisklassen und Klassenwörterbuch) eine neue Klasse erstellen kann.

Hier ist ein Beispiel, wie das in Ihrer Situation aussehen könnte:

class Page(object): 
    # This is just a default value if we construct a Page 
    # outside the context of a ContentManager 
    cm = None 
    def __init__(self, *args, **kwargs): 
     self.args = args 
     self.kwargs = kwargs 

    @classmethod 
    def do_class_thing(cls): 
     return cls.cm 

class ContentManager(object): 

    def __init__(self, root_url): 
     self.url = root_url 

     """ 
     This is where the magic happens. We're telling type() to 
     construct a class, with the class name ContentManagerPage, 
     have it inherit from the above explicitly-declared Page 
     class, and then overriding its __dict__ such that the class 
     cm variable is set to be the ContentManager we're 
     constructing it from. 
     """ 

     self.Page = type(str('ContentManagerPage'), (Page,), {'cm': self}) 

Sobald Sie haben alles aufgebaut, dann ist es einfach genug, um genau das zu tun, was Sie versuchen, mit cm zu tun, wie eine Klassenvariable

+0

Gibt es praktische Unterschiede zwischen der Verwendung der 'type' -Funktion und der Definition der Klasse selbst innerhalb der' __init__'-Methode? – Andreas

+0

Nein! Ganz stilistisch. –