[PYTHON] The story of a Django model field disappearing from a class

Overview

――I looked up what my colleague had announced and looked it up for myself and made a note of it. ――Why does a field that is a class variable disappear from a class in Django's model? There was a story ――Leave a brief (miscellaneous) story about why this happens.

Class variables

--First, let's talk about class variables --Class variables are shared by all instances.

class Person:
    name = 'takanory'
    
Person.name # => takanory

p1 = Person()
p1.name # => takanory

assert Person.name is p1.name  # same object

Fields disappear from Django model

--Next, look at Django's model definition -Although it inherits ** models.Model **, it is a normal Python class definition. --But the class variable of the field does not exist

class Person(models.Model):
    name = models.CharField(max_length=255, default='takanory')
    age = 37

#Ordinary class variables exist
Person.age # => 37
    
#Field object's class variable "name" is missing
Person.name 
# => AttributeError: type object 'Person' has no attribute 'name'

#Actually, "name" is "_meta.Exists in "fields"
Person._meta.fields
# => (<django.db.models.fields.AutoField: id>, <django.db.models.fields.CharField: name>)

—— No wonder for those who normally use Django -** But if you think about it, isn't this strange behavior of Python? Question **

Classes and metaclasses

――To answer this question, we need to talk about metaclasses. -You can create a class object with ** type ** --Class definition is synonymous with creating a class object with ** type ** --The class is an instance of ** type **

#Ordinary class definition
class Person:
    name = 'takanory'

#Create the same class object "Person" as the above class definition
Person = type('Person', tuple(), {'name': 'takanory'})

#The class object is an instance of type
assert isinstance(Person, type)

--The original class for creating a class is called ** metaclass ** --By default, ** type ** is set as ** metaclass ** to generate a class. -You can use another ** metaclass ** by specifying ** metaclass **. --In other words, you can customize the class definition method itself.

class Taka22(type):
    """ 
name is'takanory'Only in the case of
    nickname = 'taka22'A metaclass that adds a class variable called
    """
    def __new__(cls, name, bases, attrs):
        if attrs.get('name') == 'takanory':
            attrs['nickname'] = 'taka22'
        return super().__new__(cls, name, bases, attrs)  

class Person(metaclass=Taka22):
    name = 'takanory'
 
Person.nickname # => 'taka22'

Follow the Django source

――At this point, you can imagine that the metaclass is doing the "field disappears" from the class. --Actually check the relevant part.

Check the metaclass of the Model class

-** Model ** class uses a metaclass called ** ModelBase **

class Model(six.with_metaclass(ModelBase)):
    _deferred = False

    def __init__(self, *args, **kwargs):

# refs https://github.com/django/django/blob/master/django/db/models/base.py#L355

--six is a library for making Python2 and Python3 compatible. --There is a difference in how to specify ** metaclass ** between Python2 and Python3, so it will be filled in.

# python3
class Hoge(metaclass=NewMeta):

# python2
class Hoge(object):
    __metaclass__ = NewMeta

# python3 and python2
class Hoge(six.with_metaclass(NewMeta):

Where the field is erased

--In the metaclass ** ModelBase , you haven't passed attrs (which contains class variables) to the new (super_new) of the parent class ( type **). -Only ** __module __ ** is passed as attrs. At this point, all class variables such as field objects have disappeared from the class definition.

class ModelBase(type):
    """
    Metaclass for all models.
    """
    def __new__(cls, name, bases, attrs):
        #Parent class(type)of__new__Method
        super_new = super(ModelBase, cls).__new__

        # ~abridgement~

        # Create the class.
        module = attrs.pop('__module__')
        #pass only module as attr and new_Generating class
        #At this point the class variables are gone
        new_class = super_new(cls, name, bases, {'__module__': module})

# refs https://github.com/django/django/blob/master/django/db/models/base.py#L67             

Move fields to ** _meta.fields **

--After this, the fields are set to ** _meta.fields ** in the model with their own ** contribute_to_class ** --Expect all field objects to have ** contribute_to_class ** --In other words, whether or not to set it in ** _meta.fields ** is transferred to ** Field ** instead of ** Model **. -Class variables that do not have ** contribute_to_class ** will be set as is

def add_to_class(cls, name, value):
    # We should call the contribute_to_class method only if it's bound
    if not inspect.isclass(value) and hasattr(value, 'contribute_to_class'):
        value.contribute_to_class(cls, name) #value is a field object
    else:
        # contribute_to_I don't have a class
        #attr is set as it is in the class definition(Be returned)
        setattr(cls, name, value)

# refs https://github.com/django/django/blob/c339a5a6f72690cd90d5a653dc108fbb60274a20/django/db/models/base.py#L303

-Set to ** Model._meta.fields ** in ** contribute_to_class ** of ** Field **

def contribute_to_class(self, cls, name, private_only=False, virtual_only=NOT_PROVIDED):
    # ~abridgement

    #About this area, I received cls(=model)of_I'm adding myself as fields to meta.
    self.model = cls
    if private_only:
        cls._meta.add_field(self, private=True)
    else:
        cls._meta.add_field(self)
    if self.choices:
        setattr(cls, 'get_%s_display' % self.name,
                    curry(cls._get_FIELD_display, field=self))

# refs https://github.com/django/django/blob/master/django/db/models/fields/__init__.py#L678

Summary

--How do class variables such as fields disappear from Django's model? -** That's because Django's Model deliberately removes it from class variables in its own metaclass (ModelBase) ** ――It's a complete guess from here, but I wondered if the intention of this implementation was to protect the field object when instantiating the model.

class Person(models.Model):
    name = models.CharField(max_length=255, default='takanory')
    
p1 = Person()
p1.name # => 'takanory'

#The instance can directly refer to the value string "takanory"
#When the Person model instantiates, self.The "value" of the field is assigned to name
#At the same time, the class variable "Person"."name" is "self".Cannot be referenced by "name"
#Therefore, "Person._meta.I wonder if they are evacuating to "fields"?

reference

--About Python metaclasses -Understanding Python metaprogramming (metaclass, metaclass) -Django model and field class variables

Recommended Posts

The story of a Django model field disappearing from a class
The story of launching a Minecraft server from Discord
The story of writing a program
DJango Memo: From the beginning (model settings)
The story of blackjack A processing (python)
DJango Memo: From the beginning (creating a view)
The story of making a lie news generator
The story of viewing media files in Django
A story of creating 16 * 16 dots from a Digimon photo
The story of making a mel icon generator
The story of moving from Pipenv to Poetry
Python scikit-learn A collection of predictive model tips often used in the field
The story of making a web application that records extensive reading with Django
The story of Django creating a library that might be a little more useful
Python scikit-learn A collection of predictive model tips often used in the field
The story of sys.path.append ()
A story that reduces the effort of operation / maintenance
The wall of changing the Django service from Python 2.7 to Python 3
Calculate volume from the two-dimensional structure of a compound
A python implementation of the Bayesian linear regression class
Python points from the perspective of a C programmer
The story of making a music generation neural network
DJango Note: From the beginning (using a generic view)
DJango Note: From the beginning (creating a view from a template)
Make the model a string on a Django HTML template
A story about changing the master name of BlueZ
Zip 4 Gbyte problem is a story of the past
A story that analyzed the delivery of Nico Nama.
[Django] Give Form a dynamic initial value from Model
The story of switching from WoSign to Let's Encrypt for a free SSL certificate
The story of creating a VIP channel for in-house chatwork
DJango Note: From the beginning (simplification and splitting of URLConf)
The story of building Zabbix 4.4
[Apache] The story of prefork
Django Model Field Customization Various
Different from the import type of python. from A import B meaning
I made a function to check the model of DCGAN
A story that makes it easier to see Model debugging in the Django + SQLAlchemy environment
The story of copying data from S3 to Google's TeamDrive
Use django model from interpreter
Create a correlation diagram from the conversation history of twitter
After all, the story of returning from Linux to Windows
Analyze the topic model of becoming a novelist with GensimPy3
The story of creating a database using the Google Analytics API
The story of making a question box bot with discord.py
A story about predicting prefectures from the names of cities, wards, towns and villages with Jubatus
[Django] What to do if the model you want to create has a large number of fields
Create an instance of a predefined class from a string in Python
A story stuck with the installation of the machine learning library JAX
A formula that simply calculates the age from the date of birth
A story that struggled to handle the Python package of PocketSphinx
The story of making a standard driver for db with python.
[Python] Get the update date of a news article from HTML
[Django] Correct the plural form of the model name on the management screen
The story of creating a site that lists the release dates of books
Evaluate the performance of a simple regression model using LeaveOneOut cross-validation
Evaluate the accuracy of the learning model by cross-validation from scikit learn
The story of making a module that skips mail with python
Best 3 from the impressions of reading a new shell programming textbook
The story of failing to update "calendar.day_abbr" on the admin screen of django
The story of Python and the story of NaN