10

I have a Parent class and a inherited child class, I would like to know how to access the child class variable in my Parent class..

I tried this and it fails -

class Parent(object):
    def __init__(self):
        print x

class Child(Parent):
    x = 1;            

x = Child();

Error:-

NameError: global name 'x' is not defined

This question is in relation to Django forms where we inherit the form class and declare some class variables.

For example:-

My form looks like this

from django import forms

class EmployeeForm(forms.Form):
      fname = forms.CharField(max_length=100)
      lname = forms.CharField(max_length=100)

I believe the form fields are considered as class variable and somehow passed to the parent class..

user1050619
  • 19,822
  • 85
  • 237
  • 413
  • x is a Class variable, e.g. `print(Child.x)` and `print(self.x)` will print the same variable. Did you mean to make this a Class variable? – AChampion Apr 10 '15 at 15:23
  • But this doesn't have anything to do with child and parent classes. if you defined `x = 1` directly on Parent at the class level, that *still* wouldn't work. You seem to be misunderstanding Python classes completely. – Daniel Roseman Apr 10 '15 at 15:29
  • @DanielRoseman is right. Besides, if you are sure that `x` will always be implemented in inherited classes, why don't you define it in the parent class? – Selcuk Apr 10 '15 at 15:30
  • I can define it but Im trying to understand how Django FORMS child class works. – user1050619 Apr 10 '15 at 15:30
  • @DanielRoseman is his defense, Django makes use of pseudo-metaclasses to "collect" these attributes -- trying to learn python from Django is not the best approach. – jedwards Apr 10 '15 at 15:32
  • OK, so why don't you post your actual problem? – Daniel Roseman Apr 10 '15 at 15:32
  • @DanielRoseman:-- I dont have any issues and its for my educational purpose.I'm trying to understand how django forms class variable(fname & lname) is being accessed in its Parent class - Form. – user1050619 Apr 10 '15 at 15:35

5 Answers5

7

Django does this with metaclasses. (Relevant Django source)

Here's a distilled example of the relevant code:

class Field(object):
    def __init__(self, *args):
        self.args = args

    def __repr__(self): 
        return "Form(%s)" % (', '.join(map(repr, self.args)),)


class Meta(type):
    def __new__(mcs, name, bases, attrs):
        field_list = []
        for k,v in attrs.items():
            if isinstance(v, Field):
                field_list.append(v)

        cls = type.__new__(mcs, name, bases, attrs)

        cls.fields = field_list

        return cls


class Form(object):
    __metaclass__ = Meta


class MyForm(Form):
    fe1 = Field("Field1", "Vars1")
    fe2 = Field("Field2", "Vars2")
    x = "This won't appear"


form_fields = MyForm.fields
print(form_fields)

There are many questions on here about Python metaclasses (example), so I won't try to re-explain the concept.

In this case, when you create the class MyForm, each of the class attributes are checked for being instances of Field. If they are, they're added to a list (field_list).

The class is created, then an attribute .fields is added to the class, which is field_list, the list of Field elements.

You can then access the form fields through <FormSubclass>.fields or in the case of this example, MyForm.fields.


Edit:

It's worth noting that you can accomplish very similar functionality, without the metaclass syntactic sugar with something like:

class Field(object):
    def __init__(self, *args):
        self.args = args

    def __repr__(self): 
        return "Form(%s)" % (', '.join(map(repr, self.args)),)


class Form(object):
    def __init__(self):
        self._fields = None

    def fields(self):
        if self._fields is None:            
            field_list = []
            for k in dir(self):
                v = getattr(self, k)
                if isinstance(v, Field):
                    field_list.append(v)
            self._fields = field_list

        return self._fields


class MyForm(Form):
    def __init__(self):
        Form.__init__(self)
        self.fe1 = Field("Field1", "Vars1")
        self.fe2 = Field("Field2", "Vars2")
        self.x = "This won't appear"


form_fields = MyForm().fields()
print(form_fields)  # [Form('Field1', 'Vars1'), Form('Field2', 'Vars2')]
Community
  • 1
  • 1
jedwards
  • 29,432
  • 3
  • 65
  • 92
  • @user1050619 No problem, I added a bit of example-specific discussion at the bottom of the answer. I may update it a few more times in the next few minutes if I see places I could expand on. – jedwards Apr 10 '15 at 15:54
  • Shoo-wee! That link was a big read - definitely watched my own eyes glaze over in the computer screen. Honestly your first code snippet really helped me to understand what was happening! Thank you kind Samaritan! – Shmack Feb 19 '22 at 08:36
4

Short answer : you dont access subclasse's attributes from a parent class - because the parent class can not know what attributes a child class might have.

Long answer : ... unless the parent class defines a protocol allowing subclasses to let the parent class knows about at least part of it's own attributes.

Django's form framework (as well as django's orm FWIW) use such a protocol: the base Form class has a custom metaclass that collects the form.fields declared in a subclass - and do quite some black magic. FWIW, Django is oss so you could have answered the question yourself just reading the source code: https://github.com/django/django/blob/master/django/forms/forms.py

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
2

This might not help you in regards to Django Forms, but another alternative is to work with abstract classes. You would exchange attributes with methods/properties. It also prevents you from using the parent class by itself.

from abc import ABC, abstractmethod

class Parent(ABC):

    @property
    @abstractmethod
    def x(self):
        pass

    def __init__(self):
        print(self.x)

class Child(Parent):

    @property
    def x(self):
        return 1

if __name__ == '__main__':
    child_instance = Child()    # prints "1"
    parent_instance = Parent()  # fails
Beolap
  • 768
  • 9
  • 15
1

You need to refer to self.x to access Child class variables:

class Parent(object):
    def __init__(self):
        print(self.x)

class Child(Parent):
    x = 1

if __name__ == '__main__':
    child_instance = Child()
dgel
  • 16,352
  • 8
  • 58
  • 75
-1

Well, if I got you right... Maybe you're thinking of getting a field from the child class to work on the parent class. Well, that's polymorphism and it's done by overriding the parent class. Let's assume you have : A parent has x, now to increase x from the child and make it reflect in the parent, check the code below to get it.

class Parent:
    def __init__(self, x):
        self.x = x
    def Print(self):
        print(f"{self.x}")

class Child(Parent):
    def __init__(self, x):
        Parent.__init__(self, x)
        x += 1
        self.x = x

""""""

c1 = Child(2)
c1.Print()

#output: 3

""""""

c2 = Child(8)
c2.Print()

#output: 9
AlexK
  • 2,855
  • 9
  • 16
  • 27