1

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.

man zet
  • 826
  • 9
  • 26
  • 1
    There is no good solution for this, no. If `Foo(...)` should raise an exception, so will `Foo(...).anything_else()`. – L3viathan Jul 31 '21 at 12:33
  • 3
    this feels like 'code smell' - e.g. can you put the logic for not_raising in `__init__` or something? – Chris_Rands Jul 31 '21 at 12:33
  • 2
    What is the problem with the obvious and pragmatic solution? – mkrieger1 Jul 31 '21 at 12:36
  • @mkrieger1 there is no problem the obvious solution. I was just curious if it is possible this way ... – man zet Jul 31 '21 at 12:37
  • 1
    `Foo(...)` will be executed *before* `.anything_else()`, so not possible – Chris_Rands Jul 31 '21 at 12:39
  • 4
    If the two methods must be called, why don't you just call them from inside `__init__`? – chepner Jul 31 '21 at 13:36
  • How much time is allowed to pass between `Foo()` returning and `not_raising_1` being called? Is `x = Foo(); y = 3; x.not_raising_1()` a violation? – chepner Jul 31 '21 at 13:38
  • @chepner thats actually an interesting question. I would say yes. You're probably thinking of raising a Exception in some asynchronous ways ...? – man zet Jul 31 '21 at 13:47
  • I'm thinking that's the *only* solution, depending on how strictly you want to enforce the requirement that the two methods be called *immediately* after the object is initialized. See my answer for solving a relaxed version of the problem with a context manager. – chepner Jul 31 '21 at 13:59
  • I wonder why shall the user be forced to call some function on creating an object..? Isn't it the programmers duty to not overload the user with implementing funcitonalities rather than using the functionalities..? Sorry,but i guess i would be pointing a gun at the user asking him to call a function at the moment the object is being created or the object will be shot dead with an exception. – Ajay Singh Rana Jul 31 '21 at 14:51
  • 1
    @AjaySinghRana your obviously right. I was just wondering it it is possible ... Never said it was a good idea ;) – man zet Jul 31 '21 at 16:46

5 Answers5

3

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.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Interesting approach but, personally, I find it hard to grasp and somehow unusual. I associate the `with` statement primarily with allocating and releasing resources. That it is used in a different context may not seem obvious to most programmers, i.e. it may not be something other programmers will recognize at first glance. – Thomas Jul 31 '21 at 14:39
  • The idea of the `with` statement is to *create* an environment in which the instance of `Foo` can automatically run code at the entry to, and exit from, that environment. Resource management is a special use case of a context manager, where the entry and exit code specifically allocates and deallocates a resource. – chepner Jul 31 '21 at 16:00
  • 1
    Actually, the `with` statement should be used as context manager; [it's semantically equivalent to `try...except...finally` block](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement). I think that your solution is valid - so +1 - but yet, this is not the correct usage of with. I think, however, that the most correct solution should be to call the wanted methods inside the `__init__` phase. – crissal Aug 01 '21 at 12:42
  • There's no "correct" usage of `with`: you are free to define `__enter__` and `__exit__` as you see fit, and the `with` statement provides the guarantees around when each method is called, what arguments are passed to `__exit__`, and how the return value of each is used. – chepner Aug 01 '21 at 20:49
2

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.

Julian Fock
  • 98
  • 10
1

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.

Thomas
  • 8,357
  • 15
  • 45
  • 81
0

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
Ajay Singh Rana
  • 573
  • 4
  • 19
-1

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
Mohammad
  • 3,276
  • 2
  • 19
  • 35