1

I'm trying to make a class based on an instance of another class. For this I use the __new__ method:

import logging

class Logger:
    def __new__(cls):
        logger = logging.getLogger('main')

        # make a queue in a thread to put log messages in a PyQt text browser console
        return logger

    def close(self):
        pass
        # close the thread

def main():
    logger = Logger()
    # more things
    logger.close()

if __name__ == '__main__':
    main()

I am getting an AttributeError:

AttributeError: 'Logger' object has no attribute 'close'

My idea was to make a class that wraps around the instance returned from logging.getLogger('main') and be able to call both its original methods (like setLevel) and add my own.

The use of the logging module is not vital for my question, but it is an example of me not knowing how to use subclassing in this case.

My questions are:

  1. What is going wrong? How could I make this work while using the __new__ method?
  2. I've been wanting to do this more often. Is this a stupid thing to do in the first place? What would be a better way?
boudewijn21
  • 338
  • 1
  • 9
  • What do you actually need to do? Why not just derive from the original class and add your methods? And are you maybe looking for a proxy pattern instead? – AKX Nov 15 '19 at 11:01
  • 2
    The thing that's going wrong is you're using `__new__` to return an instance of an _entirely_ different class than your `Logger`. – AKX Nov 15 '19 at 11:01
  • I am not sure what you want to achieve here. Why not just use inheritence? – Karl Nov 15 '19 at 11:01
  • Perhaps you are looking for a proxy: https://stackoverflow.com/questions/26091833/proxy-object-in-python – quamrana Nov 15 '19 at 11:02
  • I like the proxy idea a lot. At least in the sense that I can instantiate an object inside my class and then use both its methods and my class's methods on the newly created object. – boudewijn21 Nov 15 '19 at 14:59
  • My idea was that sometimes I find it difficult to use inheritance because I don't know how to pass the arguments, but I now realise from Gloweye's answer that I can just pass those through the constructor of my subclass. – boudewijn21 Nov 15 '19 at 15:01
  • Another way would be to make the instance (from logging in this case) an attribute of the class that I'm making. Often, bunching all the methods together probably isn't so nice anyway... – boudewijn21 Nov 15 '19 at 15:02
  • Does this also mean you cannot combine `__new__` and another method in the same class? – boudewijn21 Nov 15 '19 at 15:05

1 Answers1

2

You should probably just subclass them:

from logging import Logger

def MyLogger(Logger):

    def close(self):
        pass

if __name__ == "__main__":
    logger = MyLogger("some_name")
    logger.close()

That said, I have no clue why you'd need to manually close a logger. They'll handle their own shutdown at object deletion, which also happens when exiting Python. And you can remove them from their own structure without issues if you want to remove them halfway for some reason.


PyQt5

In a comment, OP clarified that this is meant to work with PyQt5. Here's what I've been using for the past year.

Widget for display of logging:

# my_package.gui.logwidget.py

from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QTextEdit
from PyQt5.QtCore import QSize

from my_package.logger import handler


class LogWidget(QTextEdit):
    """
    Creates a simple textEdit widget that will automatically subscribe to the QLogger.
    """
    # pylint: disable=R0903, R0201
    def __init__(self, parent=None):
        super().__init__(parent)
        handler.recordReady.connect(self.append)
        self.setReadOnly(True)
        # For reasons mere mortals like me cannot imagine, to get a real Monospaced font,
        # we need to set it on a font that doesn't exist.
        font = QFont("MichaelMcDoesntExist")
        font.setStyleHint(QFont.Monospace)
        self.setFont(font)

    def minimumSizeHint(self) -> QSize:
        return QSize(800, 200)

Actual logger:

# my_package.logger.py

import logging

from PyQt5.QtCore import QObject, pyqtSignal


class QLogHandler(QObject, logging.Handler):
    """
    QObject subclass of logging.Handler. Will emit the log messages so QObjects can listen to it to catch log
    messages.

    Signal:
        recordReady:
            Will emit a string that is the formatted log message.
    """
    recordReady = pyqtSignal(str)

    def emit(self, record):
        self.recordReady.emit(self.format(record))

    def __repr__(self):
        return f"<{self.__class__.__name__} : {logging.getLevelName(self.level)}>"


handler = QLogHandler()  # Global ref to connect to it's signals

Python's builtin logging module already supports threadsafe logging objects, so all you need to get it to work is to have a single loghandler, and any number of logging display widgets.

Gloweye
  • 1,294
  • 10
  • 20
  • Good to know that you can subclass a Logger like this. I edited my question to show that I am actually making a thread with a queue so I can put the log messages in a PyQt text browser console. And I then want to close that thread – boudewijn21 Nov 15 '19 at 12:12
  • Python's loggers are already thread-safe. I can update my answer to also include what I did with PyQt5 for logging. – Gloweye Nov 15 '19 at 12:25
  • I am testing your two solutions. The first one works, but I am unable to use getLogger to get a reference to my logger again. Is that possible? – boudewijn21 Nov 18 '19 at 14:49
  • It might. I'm not entirely sure how the underlying python code under that function works. You'll have to read either the module docs, or read the actual source. I must admit I haven't used that method myself - subclassing handler like in my PyQt specific solution was always sufficient for my own needs. – Gloweye Nov 18 '19 at 14:54
  • I have adapted the second one to my PyQt5 GUI. It works beautifully and with a lot nicer code than the original queue I used. Thank you! – boudewijn21 Nov 18 '19 at 15:06
  • I figured out that I don't really need to subclass the Logger. I just made a function in a separate module, so that the logger initialization is nicely tucked away, and then get the logger through getLogger. With your code I don't need to close the queue thread anymore. – boudewijn21 Nov 18 '19 at 15:11
  • Ah, yes. That's cause PyQt signals and slots allow us to abstract queue's away. At least, as long as we don't do *to* much work in our slots. – Gloweye Nov 18 '19 at 15:14