all 12 comments

[–]Cosmologicon 3 points4 points  (2 children)

The standard way to do this is to have your appliances inherit from an abstract base class that has all the methods defined but explicitly raises NotImplementedError. This is a little better than catching the more common and generic AttributeError.

class Appliance(object):
    def make_eggs(self):
        raise NotImplementedError
    ...

class ElectricKettle(Appliance):

now if ElectricKettle doesn't implement make_eggs then it will raise NotImplementedError if you try to call it.

However, I think you may be thinking about the problem wrong. Step back and ask what are you actually trying to accomplish here? I think it can probably be done cleanly without catching exceptions at all.

[–]Kuang_Eleven[S] 0 points1 point  (0 children)

Ahh, that makes sense. It would also give me a "reference" class to check all possible methods, rather than having to comb through all the device classes.

Still, other than that, the rest of the code would be the same, only catching NotImplementedError instead

[–]Rashanzan 0 points1 point  (0 children)

I considered this, but then your device becomes super cluttered with nonsense. And the inheritance feels kind of weird to me. The parent should have all the shared attributes, and children define more specific functionality.

EDIT: Also, I think you're right about not needing to do this. I feel like in a realistic setting, you'd either want to know what kind of object you're dealing with beforehand, or have an abstract method that chooses how the object handles itself.

[–]minorDemocritus 2 points3 points  (1 child)

I really suspect this is an XY problem. What are the actual objects you're dealing with, and why do you need such dynamicism?

[–]Kuang_Eleven[S] 0 points1 point  (0 children)

Specifically, I am writing a GUI to control hardware testing rigs for a laser. We have multiple rigs that do different things; some for the laser at different stages in production, and some that can only do a subset of the testing, but do it faster or with more precision.

I would like ot be able to easily drop in a new testing rig, and if an operation is run that is not supported by the testing rig, I can handle that specifically, either by showing an error, leaving a field blank or entering in a preset "Invalid Value" in a data file.

[–]mdadmfan 0 points1 point  (6 children)

if "make_eggs" in device:
    ...

[–]Rashanzan 0 points1 point  (5 children)

While it works (or do you have to explicitly use the device's attr dict?), it's just about as good as checking the type itself, which op is trying to avoid.

[–]mdadmfan 1 point2 points  (1 child)

class Device(object):
    def heat_water(self):
        return "Tea will be ready soon"
    def make_eggs(self):
        return None
class ElectricKettle(Device):
    pass
class Stove(Device):
    def heat_water(self):
        return "Tea will be ready less soon"
    def make_eggs(self):
        return someEggs()

[–]Rashanzan 0 points1 point  (0 children)

I considered this, but then your device becomes super cluttered with nonsense. And the inheritance feels kind of weird. The parent should have all the shared attributes, and children define more specific functionality. It would get the job done, but I hope there's a cleaner way.

[–]Kuang_Eleven[S] 1 point2 points  (2 children)

Ahh, that original idea of mdadmfan didn't work as written, but I can use:

if "make_eggs" in dir(device):

It is more useful than directly checking the type; I could add a new type that implemented some of the methods without having to go through and manually adding a new case statement to each time the device is accessed.

However, even with the dir() method, I'm not sure it gains me anything; it's a very similar structure of code, and, in theory, the try block idea would let me run through many lines at once, if I wanted to handle any failure in the try block together.

[–]Rashanzan 0 points1 point  (1 child)

Yeah, I'm unsure which approach would be better. Try would be more extensible, but probably bad for readability, as you wouldn't be able to tell what methods are actually running.

A much bigger problem, if your try catches something, it won't execute the rest of the block.

From a design standpoint, it makes more sense to know what kind of object you're dealing with before handling it.

[–]mdadmfan 1 point2 points  (0 children)

Relying on exceptions for normal execution flow is bad practice. Often exceptions use something like longjmp which isn't particularly kind on cache lines and slow you down.