2

I need to define a class variable, named "class". I want to do this directly in class namespace, not in a class method. Obviously, I cannot directly say:

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

So, I want to do something like:

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

Where "self" should be replaced with a reference to the class. So, how do I refer to a class from class namespace?


NOTE: This question might seem contrived, but it stems from a practical goal.

In fact, MyClass is a Django REST Framework serializer and I need a "class" field to be defined on it, because this REST endpoint has to follow a certain protocol.

There's a metaclass defined for Serializers, which calls __new__() upon class creation and that __new__() aggregates all the fields, defined on class and populates a registry of fields with them. So, I have to define my variable class before the class is created. Also see: Django REST Framework: how to make verbose name of field differ from its field_name?

5
  • 1
    You cannot do it, technically that class does not exist yet (and you cannot grab reference to something that does not exist yet). Commented Jul 12, 2016 at 9:50
  • 1
    "I need to define a class variable, named "class"" – "Need" is a strong word for something that you shouldn't be doing because you're using a reserved keyword... Commented Jul 12, 2016 at 9:58
  • @deceze I'd totally agree with you otherwise, but unfortunately, MyClass is a DRF serializer, representing a REST endpoint. I implement a certain protocol that required a specific field, named class on that REST endpoint and DRF doesn't allow for field name to differ from its representation in JSON. So I do need to define it. This is definitely a better way than messing up with to_internal_value()/to_representation() methods. Commented Jul 12, 2016 at 10:01
  • Overriding __new__ or using some alternative convention or alternative way to declare that attribute is impossible...? Some APIs will have explicit workarounds for this kind of case, like using class_ instead and recognising that as a special case. Commented Jul 12, 2016 at 10:02
  • @deceze I considered other solutions, but compared to them this one is relatively innocent. Other solutions are much more likely to shoot you in the back. Commented Jul 12, 2016 at 10:04

5 Answers 5

1

You could do:

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

But since class is a reserved keyword, then you have to access the variable using getattr and setattr, so that class remains a string.

>>> m = MyClass()
>>> getattr(m, 'class')
3
Sign up to request clarification or add additional context in comments.

Comments

1

You can create your class from type and add the attribute class to the class dictionary:

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

You can't directly access the name class with a dot reference, you'll need to use getattr:

>>> MyClass.class
  File "<stdin>", line 1
    MyClass.class
                ^
SyntaxError: invalid syntax

FWIW, you can define the class methods like you would do conventionally and then bind them to the class later on.

Caveat: While this works, I wouldn't use this hack myself as the keyword class is too much of a keyword to tamper with.

3 Comments

Wow, that's a great solution, I haven't thought about it. Hackish, but great.
Serializers use a custom metaclass, which will prevent you from creating the class with type, which is not a subclass of the custom Serializer metaclass.
@BrendanAbel In that case you would create the class using the appropriate metaclass instead of type. The interface is basically the same ☺
1

You don't need to name the attribute class, which can lead to all kinds of problems. You can name the attribute class_, but still have it pull from a source attribute named class and render out to JSON as class.

You can do this by overriding the metaclass for Serializers. Here is an example of a serializers.py file (the models and classes are largely pulled straight from the tutorial).

The main magic is this section of the 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))

This takes any field you define in the serializer that ends in an underscore (ie. field_) and removes the underscore from the name when it binds the Fields and sets the _declared_fields attribute on the serializer.

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

Here is the models.py file for reference (django doesn't allow field names to end in an underscore)

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')

This is how it looks from the 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'}

5 Comments

Brendan, if I name it class_, it will be represented in JSON as class_, which isn't ok, because I have to comply with a protocol. So, I'll have to override to_instance_value()/to_representation(), which is error prone, cause I've already overridden them with another piece of code - I use Django REST Framework Mongoengine
@Bob Ah, yes, you're right. That does seem to be true in the default implementation. See the updated answer for a way to allow it.
Brendan, thanks a lot for so much effort. I understood the approach, you suggest, and might accept it. At the same time, one of the reasons, why I'm afraid of excessive tampering with DRF internals is the fact that I use django-rest-framework-mongoengine and it monkey-patches initial_data and validated_data in order to handle dynamic fields. So, I need to be very careful with my changes to create()/update() now, so that I don't get blown up by this mine.
Brendan, I believe, your solution is great for my DRF question and I accepted it there, while the best solution for this question (as pure python) is probably the one, suggested by Moses. Thanks!
@Bob Yeah, realistically, this should be a change that is pushed upstream to django-rest-framework, every other ORM supports this type of feature. A better implementation would be to update the Field class to support accepting a field_name parameter, which the metaclass would use instead of the attribute name.
0

You cannot it while creating class - technically that object does not exist yet.

You could consider:

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

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

But MyClass.__dict__ is not a dict, but a dictproxy, and 'dictproxy' object does not support item assignment, so there would be TypeError raised.

Comments

0

Use '''setattr''' to set a class attribute immediately after you finish the class definition. Outside the definition, of course. Pass '''MyClass''' for parameter, and it will create an attribute of your class.

Dict of members should not be used, especially for modifying an object. In fact, it is rarely needed. Most of things (though not all) people usually intend it to do are better done by '''setattr''' and '''getattr'''.

Finally, as one of those who answered noticed, you do not really need a field named '''class''', but that's another story, different from your original question.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.