Reputation: 901
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
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
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...
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?
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
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
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
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