-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
prior: fix AttributeError for bad connections #309
base: master
Are you sure you want to change the base?
Conversation
Thank you for reporting, debugging, and fixing this. I just need a clarification: would it suffice to swap the lines declaring the |
Great remark! You are right: I'll be happy to redo the commit to only contain the essential changes. |
Wait - no: the essence is that the the object is in a 'valid' state always, i.e. before any exception can be thrown, the |
When constructing a ProScanIII object on a port that does not contain a prior controller, the _devices member is not defined. This causes the __del__ operation of the object to fail, because it will end up in the base class' __del__, which requires the `devices` property, which requires the `_devices` member to be present. Conceptually, the `abc.Controller` class requires the implementation to be a valid object. the `super().__init__` method may call any of the public base members, so we have to make sure they are valid before calling it. One may also argue that the object is not allowed to be constructed at all if not connected to a proper device; that would be an alternative fix.
I went ahead and simplified the change. |
Thank you. That is clear. I've been thinking about this issue on the rest of the project and how could have prevented this from happening (and how we ensure it doesn't happen in other devices). What I'm thinking is that the root issue is that
You mention on the initial comment "One may also argue that the object is not allowed to be constructed at all if not connected to a proper device; that would be an alternative fix.". Can you expand on that, please? |
Yes sure. That was a more theoretical idea, and it doesn't directly map onto Python, but onto languages like C++, where class hierarchy construction/destruction order is determined by the language. If we take step back from the design here, we can see an interaction between the general device management logic and the specific details for each device. This relation has been established via inheritance, which makes 'general' functions depend on 'specific' details to be completely initialized. The choice for inheritance throws in an extra complication: the language runtime will call some 'general' functions (like This is where another design (aggregation i.s.o. inheritance) would have avoided this possibility: class Controller:
def __init__(self, specifics):
self._specifics = specifics
def shutdown(self):
for d in self._specifics.devices:
d.shutdown()
class Prior:
def __init__(self, port):
if not Prior.handshake(port):
raise RuntimeError
bad_prior = Prior("wrong-port") # would raise here
controller = Controller(bad_prior) # would not be reached => no possible interaction |
That is what I was referring to: if an object is not initialized, it is not valid for use. As such, any exception raised in So maybe the root cause is that the I like to refer to exception safety concepts in C++, e.g. at microsoft or cppreference. |
I see your point about this not being a problem if we were using C++. But this is a Python project for a reason :) Using C++ would bring its own set of problems particularly when considering the target audience of this library and the ecosystem we want to interact with. Also, note that With regards to aggregation vs inheritance, I don't think that'd be the correct design since this controller "is a" device. It doesn't "have a" device. Although if the class was a device manager, then it could be said to "have a" device. Would you agree with me that the root cause of this is that when (to be honest with your PR, I'm not sure about moving |
When constructing a
ProScanIII
object on a port that does not connect to a Prior controller, the_devices
member is not defined.This causes the
__del__
operation of the object to fail, because it will end up in the base class'__del__
, which requires thedevices
property, which is overridden to use theself._devices
member.Conceptually, the base class requires the implementation to be a valid object. One may also argue that the object is not allowed to be constructed at all if not connected to a proper device; that would be an alternative fix.
I tested (Before/After) with this snippet: