using subclass attribute in the main class in python

Question:

I’m trying to set the self.role under credential class to use the self.role under AXL class. the idea is to have different classes based on the role the api needs to be. if axl class is only reading data then the role would be r.

PATH = 'home_drive_'
PLATFORM = 'Linux_'
ITEM = '_PC'

class Credential:

    def __init__(self, path, platform):
        self.role = 'rx'
        self.username_file = path + platform + ('The role should be the same as AXL role: ' + self.role)

class AXL(Credential):

    def __init__(self, path, platform, item):
        super().__init__(path, platform)
        self.role = 'r'
        self.item = item

    def final(self):
        return self.username_file + self.item

reg1 = AXL(PATH, PLATFORM, ITEM)

print('AXL role:', reg1.role)
print(reg1.username_file)
print(reg1.final())

the result would be

AXL role: r 
home_drive_Linux_The role should be the same as AXL role: rx 
home_drive_Linux_The role should be the same as AXL role: rx_PC

Instead of rx, I need to see r

Here is a link to the sandbox

Answers:

Answer by @chepner is definitely correct solution. I’ll leave this here as a homage to over complicating a simple problem.

You could make username_file a @property on Credential so it’s evaluated on access not on instance creation.

class Credential:
    def __init__(self, path, platform):
        self.role = 'rx'
        self.path = path
        self.platform = platform

    @property
    def username_file(self):
        return self.path + self.platform + self.role

If it’s an instance of AXL roll will be ‘r’ and ‘rx’ if instance of Credential.

You can also cache the result of the first property access if you want to streamline:

class Credential:
    def __init__(self, path, platform):
        self.role = 'rx'
        self.path = path
        self.platform = platform
        self._username_file = None

    @property
    def username_file(self):
        if not self._usernme_file:
            self._username_file = self.path + self.platform + self.role
        return self._username_file
Answered By: SuperShoot

Ok, the problem here is basically the data type of username_file; its a string created in the constructor and does not update when it’s components do. There are a few ways to go around this, using properties is a pretty good and clean way to go about it:

PATH = 'home_drive_'
PLATFORM = 'Linux_'
ITEM = '_PC'


class Credential:
    def __init__(self, path, platform):
        self.path = path
        self.platform = platform
        self.role = 'rx'
        self.username_file = self.path + self.platform + ('The role should be the same as AXL role: ' + self.role)

    @property
    def role(self):
        return self._role

    @role.setter
    def role(self, new_role):
        self._role = new_role
        self.username_file = self.path + self.platform + ('The role should be the same as AXL role: ' + self.role)


class AXL(Credential):
    def __init__(self, path, platform, item):
        super().__init__(path, platform)
        self.role = 'r'
        self.item = item

    def final(self):
        return self.username_file + self.item


reg1 = AXL(PATH, PLATFORM, ITEM)

print('AXL role:', reg1.role)
print(reg1.username_file)
print(reg1.final())

EDIT:

Just a short explanation, you can turn to a property pretty much any variable, the reason for selecting role is resource management.
Essentially, it takes fewer resources to update username_file in the setter every time role changes (once in the program) than add strings (slow operation) every time you call the getter. Of course, considering the size of the program resource management should not be a deal breaker, but mentioning to explain the logic of the answer.

Answered By: nickyfot

role should be an parameter of Credential.__init__, not hard-coded, although it could have a default value for the base class. Subclasses would pass the required role directly to super().__init__. (If there is something that must have a role of 'rx', that should be a subclass of Credential as well, not Credential itself.)

class Credential:

    def __init__(self, path, platform, role='rx'):
        self.role = role
        self.username_file = path + platform + ('The role should be the same as AXL role: ' + role)


# class SomethingNeedingRoleRX(Credential):
#      def __init__(self, path, platform):
#          super().__init__(path, platform, 'rx')


class AXL(Credential):

    def __init__(self, path, platform, item):
        super().__init__(path, platform, 'r')
        self.item = item

    def final(self):
        return self.username_file + self.item

To play nicer with super, you might consider using keyword-only arguments for __init__:

class Credential:
    def __init__(self, *, path, platform, role='rx', **kwargs):
        super().__init__(**kwargs)
        self.role = role
        self.username_file = path + platform + role


class AXL(Credential):
    def __init__(self, *, item, **kwargs):
        super().__init__(role='r', **kwargs)
        self.item = item

    def final(self):
        return self.username_file + self.item


reg1 = AXL(path=PATH, platform=PLATFORM, item=ITEM)
Answered By: chepner

Some good answers, but we’re missing something obvious: class attributes

class Credential:
    role = 'rx'

    def __init__ (...
    # (... rest of class definition)

class AXL(Credential):
    role = 'r'

    def __init__ (...
    # (... rest of class definition)

If you want the quick answer: just remove the self.role = ... lines from the __init__ functions, and instead have these role = ... lines in as class attributes.

If you want the detailed answer: keep reading 🙂

The problem

The main problem lies in the last two lines of the __init__ function for Credential. First, it sets self.role to 'rx', and then it uses self.role to compute a value of self.username_file. From the point of view of the AXL class, this sets the wrong value for both attributes.

AXL needs to fix the value of self.role after Credential sets it to 'rx', but before Credential uses it to set the value of username_file. But it can’t. Your problem is that these two things are happening right next to each other in Credential’s __init__ function. There’s no space in-between for AXL to intervene and change the result.

To fix the problem, one of these assignments needs to be moved out of Credential’s __init__ function. The previous answers all decided to move username_file. But in my humble opinion, it’s easier and better to move role. Instead of setting username_file after the init function, we’re going to set role before the init function.

Class attributes

When the interpreter encounters a line like self.role = 'rx', it sets an instance attribute. This means that each particular Credential object gets its own personal value for role. They’re all stored separately, even if they all happen to be copies of the same string.

In your case, it looks like you want every Credential object to have the exact same role as every other Credential object, and every AXL object to have the same role as every other AXL object. That’s a perfect use case for a class attribute.

Class attributes are attributes that you define on the class itself, instead of on the individual instances. Importantly for our purposes, you can set them just once, when the class is defined. You don’t have to set them again every time in the __init__ function.

I found a pretty good article on class attributes here.

The fixed Code

PATH = 'home_drive_'
PLATFORM = 'Linux_'
ITEM = '_PC'

class Credential:
    role = 'rx'

    def __init__(self, path, platform):    
        self.username_file = path + platform + ('The role should be the same as AXL role: ' + self.role)

class AXL(Credential):
    role = 'r'

    def __init__(self, path, platform, item):
        super().__init__(path, platform)
        self.item = item

    def final(self):
        return self.username_file + self.item

reg1 = AXL(PATH, PLATFORM, ITEM)

print('AXL role:', reg1.role)
print(reg1.username_file)
print(reg1.final())

sandbox

How it works

First, the interpreter executes the class definition for Credential, which creates the class and sets the class attribute Credential.role to 'rx'. An __init__ function is defined, but it isn’t run.

Then the interpreter executes the the class definition for AXL. As before it creates the class and sets the class attribute. This time the attribut is AXL.role, and it’s set to 'r'.

At last, an AXL object is created, called reg1. This is when AXL.__init__ is actualy run, and in turn it runs Credential.__init__.

Here’s where it gets fun. Credential’s __init__ function tries to access self.role. self is the reg1 object, so the interpreter tries to look up reg1.role. reg1 doesn’t have a role attribute itself, but it’s an instance of the AXL class, and the class does have a role attribute, so the interpreter substitutes the value of AXL.role for self.role, and gets a value of 'r'.

That last part is the interesting bit. Even though we’re inside Credential‘s __init__, the self object is an instance of AXL, so AXL.role is the value we want. And that’s the value we get.

Answered By: Douglas Winship
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.