2016-07-12 8 views
2

Ich muss eine Klassenvariable namens "Klasse" definieren. Ich möchte dies direkt im Klassennamespace tun, nicht in einer Klassenmethode. Offensichtlich kann ich nicht direkt sagen:python: wie auf die Klasse .__ dict__ von der Klassenvariablen zuzugreifen?

class MyClass(object): 
    a = 1 
    b = 2 
    class = 3 

Also, ich möchte wie etwas tun:

class MyClass(object): 
    a = 1 
    b = 2 
    self.__dict__["class"] = 3 

Wo „self“ sollte mit einem Verweis auf die Klasse ersetzt werden. Also, wie referenziere ich eine Klasse aus dem Klassennamespace?


HINWEIS: Diese Frage mag erfunden erscheinen, aber sie ergibt sich aus einem praktischen Ziel.

In der Tat ist MyClass ein Django REST Framework Serializer und ich brauche ein "Klasse" -Feld, um darauf definiert zu werden, da dieser REST-Endpunkt einem bestimmten Protokoll folgen muss.

Es gibt eine Metaklasse für Serializer definiert, die __new__() bei der Klassenerstellung aufruft und __new__() aggregiert alle Felder für die Klasse definiert und füllt eine Registrierung von Feldern mit ihnen. Also muss ich meine Variable class definieren, bevor die Klasse erstellt wird. Siehe auch: Django REST Framework: how to make verbose name of field differ from its field_name?

+1

Sie können es nicht tun, technisch gibt es diese Klasse noch nicht (und Sie können nicht Bezug nehmen auf etwas, das noch nicht existiert). –

+1

* "Ich muss eine Klassenvariable namens" class "definieren" * - "Need" ist ein starkes Wort für etwas, das Sie nicht tun sollten, weil Sie ein reserviertes Schlüsselwort verwenden ... – deceze

+0

@deceze I ' Ich stimme Ihnen ansonsten völlig zu, aber 'MyClass' ist leider ein DRF-Serializer, der einen REST-Endpunkt darstellt. Ich implementiere ein bestimmtes Protokoll, das ein bestimmtes Feld mit dem Namen 'class' auf diesem REST-Endpunkt benötigt, und DRF erlaubt es nicht, dass sich der Feldname von seiner Darstellung in JSON unterscheidet. Also muss ich es definieren. Dies ist definitiv ein besserer Weg, als mit 'to_intern_value()/to_representation()' Methoden zu verfahren. –

Antwort

1

Sie könnten tun:

class MyClass(object): 
    a = 1 
    b = 2 
    vars()['class'] = 3 

Aber da class ist ein reserviertes Schlüsselwort, dann müssen Sie die Variable Zugriff mit getattr und setattr, so dass class ein String bleibt.

>>> m = MyClass() 
>>> getattr(m, 'class') 
3 
0

Sie können es nicht während der Erstellung der Klasse - technisch ist dieses Objekt noch nicht vorhanden.

Sie könnten betrachten:

class MyClass(object): 
    a = 1 
    b = 2 

# class is already created 
MyClass.__dict__["class"] = 3 

Aber MyClass.__dict__ kein dict ist, sondern ein dictproxy und 'dictproxy' object does not support item assignment, so gäbe es TypeError angehoben.

0

Verwenden Sie '' 'setattr' '', um ein Klassenattribut direkt nach Abschluss der Klassendefinition festzulegen. Außerhalb der Definition natürlich. Übergeben Sie MyClass für Parameter, und es wird ein Attribut Ihrer Klasse erstellen.

Dict von Mitgliedern sollte nicht verwendet werden, insbesondere zum Ändern eines Objekts. In der Tat wird es selten benötigt. Die meisten Dinge (obwohl nicht alle) Leute, die es normalerweise tun, werden besser von Setattr und getattr gemacht.

Schließlich, als einer von denen, die geantwortet hat bemerkt, brauchen Sie nicht wirklich ein Feld namens '' 'Klasse' '', aber das ist eine andere Geschichte, anders als Ihre ursprüngliche Frage.

1

Sie können Ihre Klasse von type und fügen Sie das Attribut class zur Klasse Wörterbuch erstellen:

>>> MyClass = type('MyClass',(), {'class': 3, 'a':1, 'b':2}) 
>>> getattr(MyClass, 'class') 
3 

Sie können nicht direkt Zugriff auf den Namen class mit einem Punkt Referenz, müssen Sie getattr verwenden:

FWIW, Sie können die Klassenmethoden definieren, wie Sie es konventionell tun würden, und diese später an die Klasse binden.

Caveat: Während dies funktioniert, würde ich das selbst nicht verwenden hacken als das Schlüsselwort class ist zu viel von einem Schlüsselwort zu manipulieren.

+1

Wow, das ist eine großartige Lösung, ich habe nicht darüber nachgedacht. Hackish, aber großartig. –

+0

Serializer verwenden eine benutzerdefinierte Metaklasse, die verhindert, dass Sie die Klasse mit "type" erstellen, die keine Unterklasse der benutzerdefinierten Serializer-Metaklasse ist. –

+0

@ BrendanAbel In diesem Fall würden Sie die Klasse mit der entsprechenden Metaklasse anstelle von "type" erstellen. Die Schnittstelle ist im Grunde die gleiche ☺ –

1

Sie müssen das Attribut class nicht benennen, was zu allen Arten von Problemen führen kann. Sie können das Attribut class_ benennen, es jedoch weiterhin von einem Quellattribut namens class abrufen und als class in JSON rendern.

Sie können dies tun, indem Sie die Metaklasse für Serializer überschreiben. Hier ist ein Beispiel für eine serializers.py Datei (die Modelle und Klassen werden weitgehend direkt aus der tutorial gezogen).

Die Haupt Magie ist dieser Abschnitt der metaclass

# Remap fields (to use class instead of class_) 
fields_ = [] 
for name, field in fields: 
    if name.endswith('_'): 
     name = name.rstrip('_') 
    fields_.append((name, field)) 

Dieses beliebiges Feld Sie in dem Serializer definieren nimmt, die in einem Unterstrich endet (dh. field_) und entfernt den Unterstrich vom Namen, wenn es das bindet Fields und setzt das Attribut _declared_fields auf dem Serializer. Hier

from collections import OrderedDict 

from rest_framework import serializers 
from rest_framework.fields import Field 
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES 

class MyMeta(serializers.SerializerMetaclass): 

    @classmethod 
    def _get_declared_fields(cls, bases, attrs): 
     fields = [(field_name, attrs.pop(field_name)) 
        for field_name, obj in list(attrs.items()) 
        if isinstance(obj, Field)] 
     fields.sort(key=lambda x: x[1]._creation_counter) 

     # If this class is subclassing another Serializer, add that Serializer's 
     # fields. Note that we loop over the bases in *reverse*. This is necessary 
     # in order to maintain the correct order of fields. 
     for base in reversed(bases): 
      if hasattr(base, '_declared_fields'): 
       fields = list(base._declared_fields.items()) + fields 

     # Remap fields (to use class instead of class_) 
     fields_ = [] 
     for name, field in fields: 
      if name.endswith('_'): 
       name = name.rstrip('_') 
      fields_.append((name, field)) 

     return OrderedDict(fields_) 


class SnippetSerializer(serializers.Serializer): 

    __metaclass__ = MyMeta 

    pk = serializers.IntegerField(read_only=True) 
    title = serializers.CharField(required=False, allow_blank=True, max_length=100) 
    class_ = serializers.CharField(source='klass', label='class', default='blah') 

    def create(self, validated_data): 
     """ 
     Create and return a new `Snippet` instance, given the validated data. 
     """ 
     return Snippet.objects.create(**validated_data) 

    def update(self, instance, validated_data): 
     """ 
     Update and return an existing `Snippet` instance, given the validated data. 
     """ 
     instance.title = validated_data.get('title', instance.title) 
     instance.class_ = validated_data.get('class', instance.class_) 
     instance.save() 
     return instance 

ist die models.py Datei als Referenz (django erlaubt keine Feldnamen in einem Unterstrich zu enden)

from django.db import models 

class Snippet(models.Model): 
    title = models.CharField(max_length=100, blank=True, default='') 
    klass = models.CharField(max_length=100, default='yo') 

Dies ist, wie es aussieht von der django Shell

$ python manage.py shell 

>>> from snippets.models import Snippet 
>>> from snippets.serializers import SnippetSerializer 
>>> from rest_framework.renderers import JSONRenderer 
>>> from rest_framework.parsers import JSONParser 
>>> snippet = Snippet(title='test') 
>>> snippet.save() 
>>> serializer = SnippetSerializer(snippet) 
>>> serializer.data 
{'title': u'test', 'pk': 6, 'class': u'yo'} 
+0

Brendan, wenn ich es 'class_' nenne, wird es in JSON als' class_' dargestellt, was nicht ok ist, weil ich ein Protokoll einhalten muss. Also muss ich 'to_instance_value()/to_representation()' überschreiben, was fehleranfällig ist, weil ich sie bereits mit einem anderen Stück Code überschrieben habe - ich benutze Django REST Framework Mongoengine –

+0

@Bob Ah, ja, du 'hast recht. Dies scheint in der Standardimplementierung der Fall zu sein. Eine aktualisierte Möglichkeit finden Sie in der aktualisierten Antwort. –

+0

Brendan, vielen Dank für die Mühe. Ich habe den Ansatz verstanden, schlage ich vor und könnte ihn akzeptieren. Zur gleichen Zeit, einer der Gründe, warum ich Angst vor übermäßigen Manipulationen mit DRF-Interna habe, ist die Tatsache, dass ich 'django-rest-framework-mongoengine' und die Affen-Patches' initial_data' und 'valided_data' in der Reihenfolge verwende mit dynamischen Feldern umgehen. Ich muss also sehr vorsichtig mit meinen Änderungen an 'create()/update()' sein, damit ich nicht von dieser Mine in die Luft gejagt werde. –