2

我使用 Python 2.6 作为批处理脚本替换。它将通过双击启动,因此用户将丢失/忽略所有到 stdout 的输出。所以,我决定添加日志,为了简单起见,我为此编写了一个类。这个想法是我可以Logging.Logger在我的代码中的任何地方使用,并且记录器将准备就绪。

我希望一个目录中的旧日志文件不超过 10 个,所以我手动清除旧日志文件。我还没有通过 API 找到这样的功能,而且我很偏执,想要记录所有内容,如果日志目录中有文件名意外的事实。

所以,下面是我对此类的尝试,但是当我尝试测试(运行)它时出现错误:

>>> ================================ RESTART ================================
>>> 

Traceback (most recent call last):
  File "C:/AutomationScripts/build scripts/Deleteme.py", line 6, in <module>
    class Logging:
  File "C:/AutomationScripts/build scripts/Deleteme.py", line 42, in Logging
    __clearOldLogs(dummySetting)
  File "C:/AutomationScripts/build scripts/Deleteme.py", line 38, in __clearOldLogs
    _assert(Logger, 'Logger does not exist yet! Why?')
NameError: global name '_assert' is not defined
>>> 

是的,我来自 Java/C# 背景。我可能没有以“Pythonic”的方式做事。请帮我做正确的事,并请给出一个可行的完整答案,而不是简单地指出我知识中的漏洞。我相信我已经提供了足够的代码示例。抱歉,如果没有设置类,它将无法运行,但希望您能理解。

# This file has been tested on Python 2.6.*. For Windows only.

import logging          # For actual logging
import tkMessageBox     # To display an error (if logging is unavailable)

class Logging:
    """
    Logging class provides simplified interface to logging
    as well as provides some handy functions.
    """

    # To be set when the logger is properly configured.
    Logger = None

    @staticmethod
    def _assert(condition, message):
        """ Like a regular assert, except that it should be visible to the user. """
        if condition: return
        # Else log and fail
        if Logger:
            Logger.debug(traceback.format_stack())
            Logger.error('Assert failed: ' + message)
        else:
            tkMessageBox.showinfo('Assert failed: ' + message, traceback.format_stack())            
        assert(condition)

    @staticmethod
    def _removeFromEnd(string, endString):
        _assert(string.endswith(endString),
                "String '{0}' does not end in '{1}'.".format(string, endString))
        return string[:-len(endString)]

    def __clearOldLogs(logSettings):
        """
        We want to clear old (by date) files if we get too many.
        We should call this method only after variable 'Logger' has been created.
        """
        # The following check should not be necessary from outside of
        # Logging class, when it is fully defined
        _assert(Logger, 'Logger does not exist yet! Why?')
        # Do more work here

    def __createLogger(logSettings):
        logFileName = logSettings.LogFileNameFunc()
        #_assert(False, 'Test')
        logName = _removeFromEnd(logFileName, logSettings.FileExtension)
        logFileName = os.path.join(logSettings.BaseDir, logFileName)
        # If someone tried to log something before basicConfig is called,
        # Python creates a default handler that goes to the console and will
        # ignore further basicConfig calls. Remove the handler if there is one.
        root = logging.getLogger()
        if root.handlers:
            for handler in root.handlers:
                root.removeHandler(handler)
        logging.basicConfig(filename = logFileName, name = logName, level = logging.DEBUG, format = "%(asctime)s - %(levelname)s - %(message)s")
        logger = logging.getLogger(logName)
        return logger

    # Settings is a separate class (not dependent on this one).    
    Logger = __createLogger(Settings.LOGGING)
    __clearOldLogs(Settings.LOGGING)

if __name__ == '__main__':
    # This code section will not run when the class is imported.
    # If it is run directly, then we will print debugging info.
    logger = Logging.Logger
    logger.debug('Test debug message.')
    logger.info('Test info message.')
    logger.warning('Test warning message.')
    logger.error('Test error message.')
    logger.critical('Test critical message.')

欢迎提出相关问题、风格建议和完整答案。谢谢!

4

4 回答 4

5

你得到那个例外是因为你打电话_assert()而不是Logging._assert(). 错误消息告诉您它_assert()在模块的全局命名空间中而不是在类命名空间中查找;要让它在后者中查看,您必须明确指定它。

当然,在这种情况下,您正在尝试在类仍在定义时执行此操作,并且名称在类完成之前不可用,因此很难完成这项工作。

一个解决方案是取消缩进以下两行(我已对其进行编辑以使用完全限定名称),以便它们位于类定义之外;他们将在它之后被执行。

Logger = Logging.__createLogger(Settings.LOGGING)
Logging.__clearOldLogs(Settings.LOGGING)

一个有帮助的风格建议:与其使用一堆静态方法创建一个类,不如考虑将它们作为模块中的顶级函数。您的模块的用户(包括您自己)会发现更容易获得他们想要的功能。没有理由仅仅将类用作容器;模块本身已经是这样一个容器。

一个模块基本上是一个*.py文件(尽管您可以创建具有多个文件的模块,但现在就可以了)。当您执行 时import,您要导入的是一个模块。在您的示例中,tkMessageBox并且logging都是模块。因此,只需创建一个单独的文件(确保其名称与现有 Python 模块名称不冲突),将其保存在与主脚本相同的目录中,然后将其导入主脚本中。如果您将其命名,mylogging.py那么您将可以import mylogging访问其中的函数mylogging.clearOldLogs()或其他任何内容(类似于您现在将它们作为一个类来处理的方式)。

Python 中的“全局”名称并不是真正的全局名称,它们仅对定义它们的模块是全局的。因此,模块是划分功能的好方法,尤其是您预计会大量重用的部分(如日志记录)你未来的剧本。

于 2010-12-29T17:41:31.657 回答
2

换行

_assert(Logger, 'Logger does not exist yet! Why?')

Logging._assert(Logger, 'Logger does not exist yet! Why?')

这是因为您将其定义_assert为类的静态方法,并且ClassName.methodName即使您从此类实例的方法中调用静态方法,也必须引用静态方法。

于 2010-12-29T17:41:21.730 回答
1

我认为它只需要 Logging._assert。Python 不像 java 那样进行命名空间解析:非限定名称要么是方法本地的,要么是全局的。不会自动搜索诸如类之类的封闭范围。

关于 Python 如何处理名称的章节和章节在4.1 节。语言参考手册中的命名和绑定。这里相关的一点可能是:

类块中定义的名称范围仅限于类块;它没有扩展到方法的代码块

将此与函数中定义的名称进行对比,这些名称向下继承的(到方法局部函数和类中):

如果定义出现在功能块中,则范围扩展到包含在定义块中的任何块,除非包含的块为名称引入了不同的绑定

于 2010-12-29T17:38:17.473 回答
1

一些评论。

  1. 不要使用双下划线,除非您完全确定必须这样做以及为什么这样做。

  2. 要访问 _assert 方法,请使用 self 调用它。像这样: self._assert(Logger, 'Logger 还不存在!为什么?') 在静态方法中,如在您的示例中,您使用类名:Logger._assert()。Python 非常明确。

  3. 类仅在类定义的 END 处创建。这就是 Python 的情况。但是您的错误与此无关。

  4. 我不确定这段代码应该做什么:

    # Settings is a separate class (not dependent on this one).    
    Logger = __createLogger(Settings.LOGGING)
    __clearOldLogs(Settings.LOGGING)
    

    但我怀疑你应该把它放在__init__方法中。在类构建期间,我没有立即看到任何需要访问类的代码。

  5. __createLogger是一个奇怪的功能。这个类不是叫Logger吗?那不应该只是__init__Logger 类的吗?

于 2010-12-29T17:45:16.040 回答