Search code examples
pythonoopsubclassingdefault-constructor

How to make sure a subclass has the same initialiser as the baseclass?


Basically, I am trying to define a subclass of string which conforms with the RFC UUID scheme. An object of this class can only exist if it conforms to the 8-4-4-4-12 hex string, else does not. So I need to check it during initialisation, but not sure how to account for all possible initialisation arguments of str.

This is how my code looks like:

import uuid

class UniversalID(str):
    def __init__(self, val:str):
        super().__init__()
        try:
            uuid.UUID(hex=val)
            self=val
        except ValueError as inv_str:
            logging.error(msg=f'Invalid id queried {val}')
            raise inv_str

sample= uuid.uuid1().hex
isinstance(UniversalID(sample), str) # Shows true, as expected

But not sure if this is the right approach, as there may be other arguments of str initialiser that I am not taking care of.

The question can be generalised to, if I want to modify the __init__ method with some validation check in the subclass, do I need to have total access to the initialiser in the base class, to make sure it accepts the same arguments and processes the same way? Worse still, do I have to, like, copy paste the code?


Solution

  • As I said in a comment you have to set the value of immutable types, like strings, in their __new__() method. I couldn't find a canonical source or example for you — I know about it from some books I read long ago, so decided just to make up one for you (based on your code):

    import logging
    import uuid
    
    
    class UniversalID(str):
        # You don't really need to define this, because it's what would happen anyway.
        def __new__(cls, *args, **kwargs):
            return super().__new__(cls, *args, **kwargs)
    
        def __init__(self, *args, **kwargs):
            try:
                uuid.UUID(hex=self)
            except ValueError as inv_str:
                logging.error(msg=f'Invalid id queried {self}')
                raise inv_str
    
    
    if __name__ == '__main__':
    
        sample1 = uuid.uuid1().hex
        try:
            isinstance(UniversalID(sample1), str)
        except ValueError as exc:
            print(f'ERROR: {exc} {sample1=!r}')
        else:
            print(f'{sample1=!r} is a valid hex uuid.')
    
        print()
        sample2 = 'not a hex uuid'
        try:
            isinstance(UniversalID(sample2), str)
        except ValueError as exc:
            print(f'ERROR: {exc} {sample2=!r}')
        else:
            print(f'{sample2=!r} is a valid hex uuid.')