boudewijn21
boudewijn21

Reputation: 358

How to make a class based on an instance of another class using __new__?

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?

Upvotes: 1

Views: 370

Answers (1)

Gloweye
Gloweye

Reputation: 1428

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.

Upvotes: 2

Related Questions