0

i want to create objects in a loop. After the object is created i call methods on these objects. But the creation could fail and so the method call is not possible or even necessary. I want to log the failure, but do not want to stop the loop.

I came up with a stupid example:

import logging

class DivisionCreationError(Exception):
    pass

class Division(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
        try:
            self.a / self.b
        except ZeroDivisionError:
            logging.warning('Not possible')
            raise DivisionCreationError
        else:
            self.result = self.a / self.b
        
    def get_result(self):
        return self.result
        
if __name__ == '__main__':
    
    numbers = [(1,2), (1,0), (1,3)]
    
    for i in numbers:
        try:
            created_object = Division(i[0], i[1])
        except DivisionCreationError:
            pass
        else:
            print(created_object.get_result())

This is just an example.

How can i log the failed creation of the objection and still keep the loop running. Is the proceeding in my example okay? I catch an exception and than throw a custom one, which i catch again.

I wouldn't mind if on failure i do not get an object at all and could do something like

for i in numbers:
    created_object = Division(i[0], i[1])
    if created_object:
        print(created_object.get_result())

But could not find a way to do that.

Thank you.

Paul
  • 252
  • 3
  • 12
  • 1
    `except DivisionCreationError: continue`? – Axe319 Oct 03 '22 at 19:09
  • Do you need to call methods on the object just created before the creation of the next one, or could you just collect all the objects that you can create and then loop over the collection calling the methods? – quamrana Oct 03 '22 at 19:11
  • @quamrana i need to call the methods before the creation of the next one. – Paul Oct 03 '22 at 19:15

3 Answers3

2

I think, you can customize how your object should be built like this

import logging

class DivisionCreationError(Exception):
    pass

class DivisionMeta(type):
    def __call__(self, *args, **kwds):
        res = None
        try:
            res = super().__call__(*args, **kwds)
        except DivisionCreationError as err:
            pass
        return res
    


class Division(object, metaclass=DivisionMeta):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
        try:
            self.a / self.b
        except ZeroDivisionError:
            logging.warning('Not possible')
            raise DivisionCreationError
        else:
            self.result = self.a / self.b
        
    def get_result(self):
        return self.result
        
if __name__ == '__main__':
    
    numbers = [(1,2), (1,0), (1,3)]
    
    for i in numbers:
        created_object = Division(i[0], i[1])
        if created_object:
            print(created_object.get_result())

We are using metaclasses to change the way how my object should be built. So when you are initializing your object then we are calling __call__ method that will call __init__ method of division where we are raising DivisionCreationError. When we catch this error we got to know that object is not initialized correctly so we made that object None and then we returned it from our metaclass. This is how this solution is working.

Deepak Tripathi
  • 3,175
  • 1
  • 8
  • 21
  • trying to understand here, you are returning object None if a/b not possible, copying from : https://stackoverflow.com/questions/61806609/is-there-a-way-to-define-a-class-instance-is-none when the class constructor is called with wrong arguments division not possible , the None object is returned instead of an instance of Division ? – pippo1980 Oct 03 '22 at 20:44
  • 1
    Let me explain how this works. – Deepak Tripathi Oct 04 '22 at 02:57
  • 1
    Thanks for explain it. For a novice like me its kind of counter intuitive that in Divisionmeta super() is actually Division. Found nice explanations on SO https://stackoverflow.com/questions/45536595/understanding-call-with-metaclasses , https://stackoverflow.com/questions/12971641/need-to-understand-the-flow-of-init-new-and-call – pippo1980 Oct 04 '22 at 04:13
2

I suspect this is just an example, but anyway in this particular case I would do something like:

import logging

class DivisionCreationError(Exception):
    pass

class Division:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, val):
        if val == 0:
            raise DivisionCreationError # IMHO a ValueError is more appropriate
        else:
            self._b = val

    @property 
    def result(self):
        return self.a / self.b

    def __repr__(self):
        return f'Division(a={self.a}, b={self.b})'
        
if __name__ == '__main__':
    numbers = [(1, 2), (1, 0), (1, 3)] 
    for nums in numbers:
        try:
            created_object = Division(*nums)
        except DivisionCreationError:
            logging.warning(f'Not possible: {nums}')
        else:
            print(created_object.result)
        finally: # this is just to show that in case of error no new object
            print(f'New object: {created_object}')
            created_object = None

output:

0.5
New object: Division(a=1, b=2)
WARNING:root:Not possible: (1, 0)
New object: None
0.3333333333333333
New object: Division(a=1, b=3)

Note that logging is done outside the class. Let the user of the class decide how they want to handle any error that may raise. Also note that it will raise error if attribute of already created object is changed to bad value.

buran
  • 13,682
  • 10
  • 36
  • 61
0

your code looks fine with me, just finish setting up the logger:

import logging
import os


# LOGLEVEL = os.environ.get('LOGLEVEL', 'WARNING').upper() # https://powerfulpython.com/blog/nifty-python-logging-trick/
# logging.basicConfig(level=os.environ.get("LOGLEVEL"))    # https://powerfulpython.com/blog/nifty-python-logging-trick/

logging.basicConfig(level=logging.INFO)

root_log = logging.getLogger("my-logger")
log = logging.getLogger("my-logger")
log.info("...starting log !!!")

class DivisionCreationError(Exception):
    pass

class Division(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
        try:
            self.a / self.b
            log.info('data : '+str(self.a)+'/'+str(self.b))
        except ZeroDivisionError:
            log.info('data : '+str(self.a)+'/'+str(self.b))
            log.warning('Not possible')
            raise DivisionCreationError
        else:
            self.result = self.a / self.b
        
    def get_result(self):
        log.info('result : '+str(self.result))
        return self.result
        
if __name__ == '__main__':
    
    numbers = [(1,2), (1,0), (1,3)]
    
    for i in numbers:
        print(i)
        try:
            created_object = Division(i[0], i[1])
        except DivisionCreationError:
            pass
        else:
            print('--> ',created_object.get_result())


logging to stdout, result is output from IDE dont know how/why but keeps log away from prints:

INFO:my-logger:...starting log !!!
INFO:my-logger:data : 1/2
INFO:my-logger:result : 0.5
INFO:my-logger:data : 1/0
WARNING:my-logger:Not possible
INFO:my-logger:data : 1/3
INFO:my-logger:result : 0.3333333333333333
(1, 2)
-->  0.5
(1, 0)
(1, 3)
-->  0.3333333333333333

output from console:

INFO:my-logger:...starting log !!!
(1, 2)
INFO:my-logger:data : 1/2
INFO:my-logger:result : 0.5
-->  0.5
(1, 0)
INFO:my-logger:data : 1/0
WARNING:my-logger:Not possible
(1, 3)
INFO:my-logger:data : 1/3
INFO:my-logger:result : 0.3333333333333333
-->  0.3333333333333333
pippo1980
  • 2,181
  • 3
  • 14
  • 30