Why am I able to instantiate my Abstract Base Class in Python? - python

As I understand it, I can use the abc module in Python to create abstract classes that can't be instantiated (amongst other nice properties). I tried to use this to create a hierarchy of Exception classes to represent various exit codes for my application, but I'm still able to instantiate my base class, even though I don't want that to happen. Here's some code that demonstrates the problem:
#!/usr/bin/env python3
import abc
class ExitCodeException(Exception):
__metaclass__ = abc.ABCMeta
def __init__(self, message):
super().__init__()
self._message = message
#abc.abstractmethod
def getExitCode(self):
"""Return the exit code for this exception"""
return
class FatalException(ExitCodeException):
def getExitCode(self):
return 1
raise ExitCodeException("Oh no!")
I was expecting my program to quit with an exception saying that ExitCodeException couldn't be instantiated, but instead I just get the standard stack trace I'd expect if ExitCodeException weren't abstract:
Traceback (most recent call last)
File "./email2pdf_classexception", line 21, in <module>
raise ExitCodeException("Oh no!")
__main__.ExitCodeException
How can I fix this?

As discussed in the comments by #BartoszKP and #Debanshu Kundu above, it appears the concrete superclass Exception is what causes the issue here. As such, I've come up with a slightly different pattern which seems to work (as I understand it, this is an older-style of pattern from Python 2, but still seems valid):
#!/usr/bin/env python3
class ExitCodeException(Exception):
def __new__(cls, *args, **kwargs):
if cls is ExitCodeException:
raise NotImplementedError("Base class may not be instantiated")
return Exception.__new__(cls, *args, **kwargs)
def __init__(self, message):
super().__init__()
self._message = message
def getExitCode(self):
"""Return the exit code for this exception"""
return
class FatalException(ExitCodeException):
def getExitCode(self):
return 1
raise FatalException("Oh no!")
This works as intended; if I change the code to instantiate ExitCodeException directly, it fails.

Related

Dynamically add function to class through decorator

I'm trying to find a way to dynamically add methods to a class through decorator.
The decorator i have look like:
def deco(target):
def decorator(function):
#wraps(function)
def wrapper(self, *args, **kwargs):
return function(*args, id=self.id, **kwargs)
setattr(target, function.__name__, wrapper)
return function
return decorator
class A:
pass
# in another module
#deco(A)
def compute(id: str):
return do_compute(id)
# in another module
#deco(A)
def compute2(id: str):
return do_compute2(id)
# **in another module**
a = A()
a.compute() # this should work
a.compute2() # this should work
My hope is the decorator should add the compute() function to class A, any object of A should have the compute() method.
However, in my test, this only works if i explicitly import compute into where an object of A is created. I think i'm missing something obvious, but don't know how to fix it. appreciate any help!
I think this will be quite simpler using a decorator implemented as a class:
class deco:
def __init__(self, cls):
self.cls = cls
def __call__(self, f):
setattr(self.cls, f.__name__, f)
return self.cls
class A:
def __init__(self, val):
self.val = val
#deco(A)
def compute(a_instance):
print(a_instance.val)
A(1).compute()
A(2).compute()
outputs
1
2
But just because you can do it does not mean you should. This can become a debugging nightmare, and will probably give a hard time to any static code analyser or linter (PyCharm for example "complains" with Unresolved attribute reference 'compute' for class 'A')
Why doesn't it work out of the box when we split it to different modules (more specifically, when compute is defined in another module)?
Assume the following:
a.py
print('importing deco and A')
class deco:
def __init__(self, cls):
self.cls = cls
def __call__(self, f):
setattr(self.cls, f.__name__, f)
return self.cls
class A:
def __init__(self, val):
self.val = val
b.py
print('defining compute')
from a import A, deco
#deco(A)
def compute(a_instance):
print(a_instance.val)
main.py
from a import A
print('running main')
A(1).compute()
A(2).compute()
If we execute main.py we get the following:
importing deco and A
running main
Traceback (most recent call last):
A(1).compute()
AttributeError: 'A' object has no attribute 'compute'
Something is missing. defining compute is not outputted. Even worse, compute is never defined, let alone getting bound to A.
Why? because nothing triggered the execution of b.py. Just because it sits there does not mean it gets executed.
We can force its execution by importing it. Feels kind of abusive to me, but it works because importing a file has a side-effect: it executes every piece of code that is not guarded by if __name__ == '__main__, much like importing a module executes its __init__.py file.
main.py
from a import A
import b
print('running main')
A(1).compute()
A(2).compute()
outputs
importing deco and A
defining compute
running main
1
2

Initializing an attribute in a child class that is used in the parent class

I am using a 3rd party Python library (wxPython), which has a buggy class in one of its modules.
The problematic code section looks like this:
def OnText(self, event):
value = self.GetValue()
if value != self.__oldvalue:
pass # Here some more code follows ...
self.__oldvalue = value
The problem is the if statement, because at the first call to this method self.__oldvalue has not been initialized yet. So for a workaround until this bug has been fixed by the library devs I thought I could fix this with a little workaround. I simply wanted to derive a child class from that faulty class and initialize self.__oldvalue in this constructor:
class MyIntCtrl(wx.lib.intctrl.IntCtrl):
def __init__(self, *args, **kw):
self.__oldvalue = None
super().__init__(*args, **kw)
However, now when I use this new class MyIntCtrl instead of the original IntCtrl class, I do get exactly the same error as before:
Traceback (most recent call last):
File "/usr/local/lib/python3.6/dist-packages/wx/lib/intctrl.py", line 509, in OnText
if value != self.__oldvalue:
AttributeError: 'MyIntCtrl' object has no attribute '_IntCtrl__oldvalue'
Now I am wondering: What am I doing wrong, how else can I fix this issue in a child class?
Any member of class which starts with __ (double underscore) is private, you can use single underscore _ or not use underscores in naming for access them in derived classes.
class Parent:
def __init__(self):
self.__private_field = "private field"
self._protected_field = "protected field"
self.public_field = "public field"
class Child(Parent):
def __init__(self):
pass
def do(self):
print(self.__private_field) # It will throw exception
print(self._protected_field) # It will not throw exception
print(self.public_field) # It will not throw exception
Or you can bypass private/protected members by calling them like:
print(_Parent__private_field)

Python Abstract Attribute

I will only have a single Abstract Class in this particular module and so I'm trying to avoid importing the "ABC" package. See below for my attempt and the issue I'm running into. I only want to use basic self.attribute = {etc...} assignment in the __init__ method of the subclass but I want to ensure it is done via the AbstractClass. I've seen some questions here but the answers all reference "ABC" package which I would agree is the best solution but not for simply one class in an entire program...
from .util import EventType, NpcType
class Event(object):
#property
def requirements(self):
raise NotImplementedError('subclasses must have requirements')
#requirements.setter
def requirements(self, value):
pass
def stage(self):
raise NotImplementedError('subclasses must override stage()')
class NRMSAL(Event):
def __init__(self):
self.requirements = {
'npc_type': [NpcType.TRAPPER],
'last_event': [],
'cash_available': False,
'item_available': True
}
def stage(self):
pass
In the above example I receive the following error when attempting to access the attribute at run time:
from drapi.event import NRMSAL
test = NRMSAL()
print test.requirements
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/Users/rickmartin/Dropbox/Projects/NpcProject/drapi/event.py", line 7, in requirements
raise NotImplementedError('subclasses must have requirements')
NotImplementedError: subclasses must have requirements
You're expecting each child class to have self.requirements right? So change the following code to this.
class Event(object):
#property
def requirements(self):
try:
return self._requirements
except AttributeError:
raise NotImplementedError('subclasses must have requirements')
That way it will return self.requirements. If self.requirements hasn't been implemented by the child class it will raise a not implemented error.
EDIT: Updated return to avoid never-ending loop.

Construct object via __init__ and ignore constructor exception

I have a Python class whose __init__ method raises a custom exception called WrongFileSpecified.
However, when I write a unit test, I want to assign the attributes of the instance object from a test fixture. So normally what I would be doing is reading data off a file and then working with the instance object.
But with the test, I cannot use any test files, so I basically need to hard code the data in the instance object in the setUp method of the unit test. Is there any way to get a instance created without __init__ complaining about the exception?
Sample code:
class A(object):
def __init__(self, folderPath):
#check folder path using os.isdir() otherwise raise exception
#...
self.folderPath = folderPath
#Call load record
self._load_records() #uses self.folderPath and raises exceptions as well
#Note i cannot avoid raising these exceptions, its required
class TestA(unittest.TestCase):
.......
obj = None
def setUp(self):
obj = A('fake folder path')
obj.val1 = "testparam1"
obj.param2 = "testparam2"
def test_1(self):
.....
You can create an empty object, bypassing __init__ by using __new__.
obj = obj_type.__new__(obj_type)
Note that obj_type is the appropriate type object. This is a little hacky but it works. You are reponsible for setting the object's members.
Edit: here is an example.
class Foo():
def __init__(self):
self.x = 1
self.y = 2
def say_hello(self):
print('Hello!')
r = Foo.__new__(Foo)
r.say_hello()
print(r.x)
Console output:
Hello!
Traceback (most recent call last):
File "C:\WinPython-64bit-3.3.5.7\python-
3.3.5.amd64\Scripts\projects\luc_utils\dev\test\
unit_test_serialization.py", line 29, in <module>
print(r.x)
AttributeError: 'Foo' object has no attribute 'x'
Here are two options:
Refactor the file loading out to a class method, which is the Pythonic method of providing an alternate constructor (see below); or
Provide an additional parameter to __init__ to suppress the exceptions when necessary (e.g. def __init__(self, folderPath, suppress=False), or validate=True, whichever makes more sense for your usage).
The latter is a bit awkward, in my opinion, but would mean that you don't have to refactor existing code creating A instances. The former would look like:
class A(object):
def __init__(self, ...):
"""Pass whatever is loaded from the file to __init__."""
...
#classmethod
def from_file(cls, folderPath):
"""Load the data from the file, or raise an exception."""
...
and you would replace e.g. a = A(whatever) with a = A.from_file(whatever).
There is a very useful module called mock, you can check it out later, I feel that in this case it will be too much. Instead, you should consider redesigning your class, like this, for example:
class A(object):
def __init__(self, folderPath):
self.folderPath = folderPath
def _load_records(self)
#check folder path using os.isdir() otherwise raise exception
...
#uses self.folderPath and raises exceptions as well
...
#classmethod
def load_records(cls, folderpath):
obj = cls(folderpath)
obj._load_records()
return obj
# Usage
records = A.load_records('/path/to/records')
Then you can do:
class TestA(unittest.TestCase):
.......
obj = None
def setUp(self):
self.obj = A('fake folder path')
self.obj.val1 = "testparam1"
self.obj.param2 = "testparam2"
def test_1(self):
self.assertRaises(self.obj._load_records, HorribleFailureError)
Also i highly recommend to check out pytest, it has wonderful facilities for test fixtures, including fixtures for files and folders.

"issubclass() arg 2 must be a class or tuple" in assertRaises

I have a module InvalidObj
class InvalidObj(Exception):
def__init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class Hello(object):
def __init__(self):
self.a = 10
self.b = 20
def aequalb(self):
if self.a != self.b:
raise InvalidObj("This is an error")
I am trying to do a unittest where a function throws the InvalidObj exception
class test_obj(unittest.TestCase):
def test_obj(self):
at = Hello()
self.assertRaises(InvalidObj("This is an error"), a.aequalb)
On running the above test_obj class, it gives me an error "issubclass() arg 2 must be a class or tuple". But if I change the line to,
self.assertRaises(InvalidObj, at.aequalb)
This runs fine. Isn't the error supposed to return the message passed to it when it is raised?
No, it is not supposed to work the way you expected. First argument is a class (or a tuple), the second is callable, the rest are as described in the documentation.
Even though exception accepts arguments, unittest does not give you deep comparisons between exceptions (otherwise, it would be pretty complex to say that two separate instances of the same class are equivalent).
To solve your issue, just test attribute separately:
with self.assertRaises(InvalidObj) as cm:
at.aequalb()
self.assertEqual("This is an error", cm.exception.value)
Note: Above I have used assertRaises() method as context manager. It behaves like that, when only one argument is given. For more details please visit mentioned documentation.

Categories

Resources