4

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
5
  • Can you subclass the original meta class? Commented Dec 12, 2021 at 0:29
  • Also, would you mind showing us the definition of the meta class? Commented Dec 12, 2021 at 0:31
  • @DanielWalker , I added metaclass. Thanks! Yes. I can change metaclass. I am developing new form library for my projects. Commented Dec 12, 2021 at 1:21
  • 3
    You can't refer to a class that doesn't exist yet, but you could give it the class name and get the class from the name when it's first used? I.e. getattr(sys.modules[module_name], classname) if you had to. However, you'll still get nasty problems if that happens before the class is completed, which it might since you have no control over the Field constructor. What problem are you actually trying to solve? Commented Dec 12, 2021 at 1:35
  • @Grismar, I would like to create convenient code for custom forms library. That form can render model fields with recursion. And I want children models use the same form class to create children subforms. I will use form class attribute while instantiating. I think I could get real class by name from the module. I thought about it =) thanks. Commented Dec 12, 2021 at 1:49

1 Answer 1

2

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()
Sign up to request clarification or add additional context in comments.

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.