Having to handle exceptions is common in Python and so is having to define your own. Yet, I have seen competing ways of doing so in various projects. The inconsistency comes from s being something that can easily be subclassed and extended, but also something that can be easily instantiated and used in their base form. Exception Here is a common way to define custom exceptions in Python: self.msg = msg : MyException( ) MyException e: print(e) print(repr(e)) : class MyException (Exception) : def __init__ (self, msg) try raise "Something went wrong" except as # <<< Something went wrong # <<< MyException('Something went wrong') self.msg = msg : MyException( ) MyException e: print(e) print(repr(e)) : class MyException (Exception) : def __init__ (self, msg) try raise "Something went wrong" except as # <<< Something went wrong # <<< MyException('Something went wrong') In general, this seems to work fine. In fact, it works “better than it should”. , Python knows how to properly execute the and methods, even though we didn’t write any code for them. Somehow str repr So is there a problem with this approach? Let’s try something slightly different: self.msg = msg : MyException(msg= ) MyException e: print(e) print(repr(e)) # Same as before : class MyException (Exception) : def __init__ (self, msg) try # Now we use a keyword argument raise "Something went wrong" except as # <<< # <<< MyException() Oh no! It looks like we broke the and methods. How did this happen? str repr Although nothing prevents us from assigning attributes to an object (the part), there is a special place in ’s heart for the constructor’s positional arguments: Exception self.msg = msg Exception e = Exception( , , ) e.__dict__ e.args str(e) repr(e) e.a = e.__dict__ e.args str(e) repr(e) 1 2 3 # <<< {} # <<< (1, 2, 3) # <<< '(1, 2, 3)' # <<< 'Exception(1, 2, 3)' 'b' # <<< {'a': 'b'} # <<< (1, 2, 3) # <<< '(1, 2, 3)' # <<< 'Exception(1, 2, 3)' But not so much for keyword arguments: e = Exception( , b= ) 1 2 # <<< --------------------------------------------------------------------------- # <<< TypeError Traceback (most recent call last) # <<< <ipython-input-9-0f7c585491d4> in <module> # <<< ----> 1 e = Exception(1, b=2) # <<< # <<< TypeError: Exception() takes no keyword arguments When we defined our own method, making it able to accept the keyword argument, there was a difference between the resulting objects when we passed positional arguments versus when we passed keyword arguments. In short, the following look like they should be identical, but they aren’t: __init__ msg MyException( ).args MyException(msg= ).args "Something went wrong" # <<< ('Something went wrong',) "Something went wrong" # <<< () (I suspect that there is some sort of “pre-initializer” in the base Exception class, possibly a __new__ method, that captures the positional arguments to the args attribute and then invokes our __init__ method) One thing we could do to fix this inconsistency is implement the methods we “broke”: self.msg = msg self.msg e = MyException(msg= ) str(e) repr(e) : class MyException (Exception) : def __init__ (self, msg) : def __str__ (self) return : def __repr__ (self) return f"MyException( )" {self.msg} 'Something went wrong' # <<< 'Something went wrong' # <<< MyException('Something went wrong') However, this is not my suggestion. First of all, it’s boring. But I also feel like it goes against the “spirit” of how Python exceptions are supposed to be structured. Maybe some exception handling code later on will inspect the attribute, expecting relevant information to be there. args What I propose is the following: super().__init__(msg) self.args[ ] : class MyException (Exception) : def __init__ (self, msg) @property : def msg (self) return 0 This way, you can initialize the exception with either positional or keyword arguments and it will behave the same way: = MyException(msg= ) e.__dict__ e.args e.msg str(e) repr(e) 'Something went wrong' # <<< {} # <<< ('Something went wrong',) # <<< 'Something went wrong' # <<< 'Something went wrong' # <<< "MyException('Something went wrong')" However, now you can’t change the attribute/property. msg e.msg = "Something else went wrong" # <<< --------------------------------------------------------------------------- # <<< AttributeError Traceback (most recent call last) # <<< <ipython-input-29-32de7ec53be2> in <module> # <<< ----> 1 e.msg = "Something else went wrong" # <<< # <<< AttributeError: can't set attribute Generally, , but if you want them to be, I would suggest doing it through properties as well: I don’t see why exception objects should be mutable super().__init__(msg) self.args[ ] self.args = (value, ) e = MyException(msg= ) e.msg = repr(e) : class MyException (Exception) : def __init__ (self, msg) @property : def msg (self) return 0 @msg.setter : def msg (self, value) 'Something went wrong' "Something else went wrong" # <<< "MyException('Something else went wrong')" With multiple (keyword) arguments, you can do: super().__init__(msg, status_code) args = list(self.args) args[position] = value self.args = tuple(args) msg = property( self: self.args[ ], self, value: self._set( , value)) status_code = property( self: self.args[ ], self, value: self._set( , value)) : class MyException (Exception) : def __init__ (self, msg, status_code=None) : def _set (self, position, value) lambda 0 lambda 0 lambda 1 lambda 1 This is a bit boilerplate-y but overall I think it’s worth it to ensure the objects remain consistent. Things can be made better with a utility, I guess: Exception args = list(self.args) args[position] = value self.args = tuple(args) property( self: self.args[position], self, value: _set(self, position, value)) .utils exc_property super().__init__(msg, status_code) msg = exc_property( ) status_code = exc_property( ) # utils.py : def _set (self, position, value) : def exc_property (position) return lambda lambda # exceptions.py from import : class MyException (Exception) : def __init__ (self, msg, status_code=None) 0 1 This way, you get the and method implemented for free (you can still override them if you want), the attribute will behave the same way regardless of whether you initialize your exception with positional or keyword arguments and you can access attributes as if they were normal attributes set to through assignment. str repr args self This looks like a lot of work, but: As I said, exceptions generally have no reason to be mutable, so you shouldn’t have to implement the setters Using the trick, you will only write the slightly messier code only once, the subclasses themselves will remain short and sweet exc_property Exception This story has been produced by Konstantinos Bairaktaris, Senior Software Engineer at Transifex.