简体   繁体   中英

Using the same class in the class attribute definition

How can I make something like this?

class Form(object, metaclass=...):
    pass


class Field(object):
    def __init__(self, nested_form_class=None):
        self.nested_form_class = nested_form_class


class TaskForm(Form):
    children = Field(nested_form_class=TaskForm)

Renders:

NameError: name 'TaskForm' is not defined

The error occurs on the line defining the children attribute.

I use class attributes in the __new__ function of the metaclass and I can not move it to __init__ function of current class.

My metaclass (Like in django framework):

class FormMeta(type):
    """
    Meta class for extract fields from model
    """

    def __new__(mcs, name, bases, attrs):
        found_fields = dict()

        for k, v in attrs.items():
            if isinstance(v, Field):
                found_fields[k] = v

        attrs['found_fields'] = found_fields

        new_class = super().__new__(mcs, name, bases, attrs)

        parent_fields = {}

        for base in reversed(new_class.__mro__):
            # Collect fields from base class.
            if hasattr(base, 'found_fields'):
                parent_fields.update(base.found_fields)

            # Disable reordered fields.
            for attr, value in base.__dict__.items():
                if value is None and attr in parent_fields:
                    parent_fields.pop(attr)

        new_class.base_fields = parent_fields
        new_class.found_fields = parent_fields

        return new_class

Inside a class statement body, the class that is being declared not yet exists - and therefore it can't be referenced like in your example. Django and the original use of type annotations in Python PEP 484 work around this by allowing you to put the class name as a string instead - but this does not help when you want a plain reference to the class.

What there is in Python is the "descriptor protocol" - sone few methods that if implemented in classes of things that are intended to be used as fields - just like in your case, are called automatically by Python. __get__ , __set__ and __delete__ methods are called on attribute access, and __set_name__ is called when the class is created, and passed the owner class. You can rely on that for your Field class. No changes are needed for your metaclass. (just keep in mind that __set_name__ is called at the end of execution of type.__new__ , before a metaclass __init__ runs.):

class Form(object, metaclass=...):
    pass


class Field(object):
    def __init__(self, nested_form_class=None):
        self.nested_form_class = nested_form_class

    def __set_name__(self, owner, name):
         if self.nested_form_class is None:
            self.nested_form_class = owner
         self.name = name


class TaskForm(Form):
    children = Field()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM