man zet
man zet

Reputation: 901

Is it possible to raise Exception if function is not called

For some given class in python like:

class Foo:
  def __init__(self, ...):
    ...
    pass
    
  def not_raising_1(self, ...):
    ...
    pass
    
  def not_raising_2(self, ...):
    ...
    pass

is it possible to enforce that the user has to call not_raising_1() or not_raising_2() after creating an object of type Foo. So I'm thinking of a behavior:

foo = Foo(...)  # will raise a Exception saying you need to call not_raising_1 or not_raising_1

foo = Foo(...).not_raising_1(...) # will NOT raise a Excpetion

foo = Foo(...).not_raising_2(...) # will NOT raise a Excpetion

I know that a pragmatic solution would obviously be to put what ever should happen in not_raising_1() or not_raising_2() with some parameter in the constructor of Foo. But I'm here not asking for a pragmatic solution but am just curios if someone can think of some creative solution to get the described behavior.

Upvotes: 1

Views: 1302

Answers (5)

Thomas
Thomas

Reputation: 10065

It's not possible. You could use a workaround as that one suggested by Ajay Signh Rana or chepner but I would, personally, not recommend it as it is hard to grasp when reading the code.

Your goal should be to increase readability and usability of the class for yourself and other programmers that uses this class. Use well-known patterns and concepts whenever possible and if applicable.

Reading your question, I understand that the object is not ready to use until one of the other methods is invoked. You could consider Julian Fock's answer and use a class method.

Or use any of the other Creational Design Patterns:

Depending on the reason why you want to achieve this behaviour, you could consider to implement the Builder pattern:

A third alternative would be, as you mention yourself, that you pass some parameters along when invoking the constructor and call, depending on the parameter, either of the other methods within the constructor.

Which approach is usable and applicable for your situation depends on your needs and bigger picture than the example in your Question. You should choose the approach that suits your needs best and is most readable.

Upvotes: 1

chepner
chepner

Reputation: 531480

First, for the record: if the two methods must be called before the object is ready to be used, that means that calling them is part of the initialization of the object, and so they should be called by __init__ itself:

class Foo:
    def __init__(self, ...):
        ...
        self.not_raising_1()
        self.not_raising_2() 
    
    def not_raising_1(self, ...):
        ...
    
    def not_raising_2(self, ...):
        ...

But, moving on to the question as asked...

The problem is not well defined.

Unless you call the methods inside __init__ itself, it is trivially true that neither method has been called the instant __init__ exits.

Further, once __init__ exits, the class Foo has no visibility into what happens outside its own definition. You need some sort of explicit state that maintains what happens after Foo.__init__ exits. Foo.not_raising_1 could examine that state to determine if anything else happened before it was called.

But that raises another problem: who will update that state? Every single bit of code would have to cooperate with Foo. Is this illegal?

x = Foo()
y = 3
x.not_raising_1()

Then how are you going to make Python update your state when it executes y = 3? The hooks just aren't there.

And finally, who is going to raise the exception if x.not_raising_1 is never called?


Refine the problem.

Rather than ask if the functions are never called, you can ensure they are called inside a with statement using an appropriately defined context manager. This context manager can ensure that not_raising_1 and not_raising_2 are called before the with statement completes, as well as ensure that they are only used inside a with statement. You can't enforce that the object is used as a context manager, but you can ensure that it is only used in a with statement.

class Foo:
    def __init__(self, ...):
        ...
        self._in_with_statement = False
        self._r1_called = False
        self._r2_called = False
    
    def not_raising_1(self, ...):
        self._r1_called = True
        if not self._in_with_statement
            raise RuntimeException("Foo instance must be used with context manager")
    
    def not_raising_2(self, ...):
        self._r2_called = True
        if not self._in_with_statement
            raise RuntimeException("Foo instance must be used with context manager")

    def something_else(self):
        if not self._r1_called or not self._r2_called:
            raise RuntimeException("Failed to call not_raising_1 and/or not_raising_2")
        ...

    def __enter__(self):
        self._in_with_statement = True

    def __exit__(self):
        self._in_with_statement = False
        if not self._r1_called or not self._r2_called:
            raise RuntimeException("Failed to call not_raising_1 and/or not_raising_2")
        self._r1_called = False
        self._r2_called = False

Here, __init__ sets the condition that neither method has yet been called, nor are we yet executing in a with statement. The instance itself acts as the external state that monitors how the instance is used.

The two required methods require themselves to be executed inside a with statement (by checking if __enter__ has been called).

Every other method can check if the required methods have been called.

The __enter__ method simply marks the object as now being in a with statement, allowing the required methods to be called.

The __exit_ method ensures that the required methods were eventually called, and resets the state of the object as being outside a context manger.


I think this is as strong a guarantee as you can enforce, short of a class that uses the inspect module to examine the script's source code looking for violations.

Upvotes: 3

Mohammad
Mohammad

Reputation: 3396

As others have suggested, it's not something that you should consider in actual code. But Just as an excercise, I tried doing something similar:

class NoError(Exception):
  pass

class Foo:
  def __init__(self):
    pass
    
  def not_raising_1(self):
    raise NoError()
    
  def not_raising_2(self):
     raise NoError()

How to use:

try:
  Foo()
  raise Exception('please use either not_raising_1 or not_raising_2')
except NoError:
  print('No error')
  # actual code

Upvotes: -1

Ajay Singh Rana
Ajay Singh Rana

Reputation: 573

I did get your question but as others suggested it cannot be done. But yeah you wann raise an exception and it should be raised if the function isn't call then you must create another function that checks if the previous functions were called or not and if not you can raise the exception.

I would approach this problem by creating a variable that changes it's value based on the funciton calls and if the functions haven't been called we can determine that as well. Try:

class SomeError(Exception):
  pass

class Foo:
  def __init__(self, ...):
    self.flag = False    # set the flag to false for each object initially
    ...
    pass
    
  def not_raising_1(self, ...):
    self.flag = True    # set it to true once the function has been called
    ...
    pass
    
  def not_raising_2(self, ...):
    self.flag = True    # repeat for this on too
    ...
    pass

  def raise_exception(self):
    if(not self.flag):
        raise SomeError

obj1 = Foo()
obj1.not_raising_1()
obj1.raise_exception()    # won't do anything

obj2 = Foo()
obj2.raise_exception()    # would raise exception as either of the two functions weren't called

Upvotes: 0

Julian Fock
Julian Fock

Reputation: 108

You could use a classmethod like this:

class Foo:
    def __init__(self, flag=True):
        if flag:
            raise CustomError()

    @classmethod
    def not_raising_1(cls):
        return cls(flag=False)

Thus foo = Foo() or foo = Foo(...).not_raising_1(...) would still raise the exception, but foo = Foo.not_raising_1(...) would work.

Upvotes: 2

Related Questions