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.
Related
I am trying to make a class Sprite that inherits generic class T, with the bound of being a subclass of class Object. Class Text is a subclass of class Object. This is class Text, provided by outer library:
# text.py
class Text(Object):
# ignored properties...
def __init__(self, text="placeholder text", **kwargs):
super().__init__(object_type=Text.object_type, text=text, **kwargs)
This is my self-written class Sprite:
# sprite.py
from typing import TypeVar, Generic
T = TypeVar('T', bound=Object)
class Sprite(Generic[T]):
def __init__(self, **kwargs):
super(Sprite, self).__init__(self, clickable=True, evt_handler=self.click_wrapper, **kwargs)
And such a Sprite instance is initialized by:
sprite = Sprite[Text](
text="This is a sprite!",
object_id="spriteTest",
# other similar arguments...
)
And this is the error thrown:
Exception thrown in main()! Terminating main...
Traceback (most recent call last):
File "sprite.py", line 79, in main
sprite = Sprite[Text](
File "C:\ProgramData\Anaconda3\lib\typing.py", line 687, in __call__
result = self.__origin__(*args, **kwargs)
File "sprite.py", line 47, in __init__
super(Sprite, self).__init__(self, clickable=True, evt_handler=self.click_wrapper, **kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
Why is this not working?
I believe you are misunderstanding Generic Type variables here. Let me first try to reduce your bug down to its most minimal variant:
class Sprite:
def __init__(self, **kwargs):
super().__init__(**kwargs)
Sprite(text="My text")
This very simple program throws the exact same Exception as you have:
Traceback (most recent call last):
File ".../72690805.py", line 57, in <module>
Sprite(text="My text")
File ".../72690805.py", line 55, in __init__
super().__init__(**kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
The key here is that it is object for who you cannot specify anything other than one argument. With other words, the superclass of Sprite is object in both of our cases (i.e. the default Python object, not your Object class). Your Sprite class simply does not have a non-default superclass.
You seem to be of the understanding that your super(...).__init__(...) call will initialize Text whenever you use Sprite[Text](...), but that is not the case. Let me give a common example of a Generic type variable in use:
from typing import List, TypeVar, Generic
T = TypeVar('T')
class Queue(Generic[T]):
def __init__(self) -> None:
super().__init__()
self.queue: List[T] = []
def put(self, task: T) -> None:
self.queue.append(task)
def get(self) -> T:
return self.queue.pop(-1)
queue = Queue()
queue.put(12)
queue.put(24)
queue.put(36)
# queue.put('a') # <- A type checker should disallow this
print(queue.get())
print(queue.get())
print(queue.get())
Here, we have a simple Queue class, with put and get methods. These functions are supplemented with Type hints via T, and now type checkers know that e.g. Queue[int]().get returns an int.
However, the super().__init__() is still just the standard initialization of the Python object. We're not suddenly initializing an int, which is equivalent to what you seem to be trying.
To wrap up; whenever you find yourself using functionality from the typing module to try and get something working, then you're making a mistake. As far as I'm aware, all the functionality from typing is merely "cosmetic", and is ignored by Python. It exists to allow developers to use type checkers to ensure that they are not making mistakes, e.g. calling queue.put('a') when queue was initialized with Queue[int](). To reiterate, this put of a character will still execute in Python, and it will place the character in the queue, even though a Type checker would tell you that it's wrong.
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)
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.
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.
What's wrong with the following code (under Python 2.7.1):
class TestFailed(BaseException):
def __new__(self, m):
self.message = m
def __str__(self):
return self.message
try:
raise TestFailed('Oops')
except TestFailed as x:
print x
When I run it, I get:
Traceback (most recent call last):
File "x.py", line 9, in <module>
raise TestFailed('Oops')
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType
But it looks to me that TestFailed does derive from BaseException.
__new__ is a staticmethod that needs to return an instance.
Instead, use the __init__ method:
class TestFailed(Exception):
def __init__(self, m):
self.message = m
def __str__(self):
return self.message
try:
raise TestFailed('Oops')
except TestFailed as x:
print x
Others have shown you how to fix your implementation, but I feel it important to point out that the behavior you are implementing is already the standard behavior of exceptions in Python so most of your code is completely unnecessary. Just derive from Exception (the appropriate base class for runtime exceptions) and put pass as the body.
class TestFailed(Exception):
pass
Use __init__() instead of __new__() to "initialize" classes. In most cases overriding __new__ is not necessary. It is called before __init__ during object creation.
See also Python's use of __new__ and __init__?
The __new__ implementation should return an instance of the class, but it's currently returning None (by default).
However, it looks like you should be using __init__ here, rather than __new__.