当类定义尚未完成时,Python 不允许我使用类内部的方法

发布于 2024-10-09 23:02:13 字数 3960 浏览 3 评论 0原文

我使用 Python 2.6 作为批处理脚本的替代品。它将通过双击启动,因此所有到标准输出的输出都将被用户丢失/忽略。因此,我决定添加日志记录,为了简单起见,我为此编写了一个类。我的想法是,我可以在代码中的任何位置使用 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”的方式做事。请帮助我做正确的事情,并请给出一个可行的完整答案,而不是简单地指出我知识中的漏洞。我相信我已经提供了足够的代码示例。抱歉,如果没有 Settings 类,它就无法运行,但希望您明白这一点。

# 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.')

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

I am using Python 2.6 as a batch script replacement. It will be started by double-clicking, so all output to stdout will be lost/ignored by the user. So, I decided to add logging, and to make matters simple, I wrote a class for that. The idea is that I can use Logging.Logger anywhere in my code, and the logger will be ready to go.

I wish to have no more than 10 old log files in a directory, so I clear out the old ones manually. I have not found a functionality like that through API, plus I am paranoid, and want to log everything, event the fact that there were files with unexpected names inside the log directory.

So, below is my attempt at such class, but I get an error when I try to test (run) it:

>>> ================================ 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
>>> 

Yes, I come from Java/C# background. I am probably not doing things "the Pythonic" way. Please help me do the right thing, and please give a complete answer that would work, instead of simply pointing out holes in my knowledge. I believe I have provided enough of a code sample. Sorry, it would not run without a Settings class, but hopefully you get the idea.

# 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.')

Relevant questions, style suggestions and complete answers are welcome. Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

自控 2024-10-16 23:02:13

您收到该异常是因为您调用的是 _assert() 而不是 Logging._assert()。错误消息告诉您它正在模块的全局命名空间而不是类命名空间中查找 _assert();要让它在后者中查看,您必须明确指定它。

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

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

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

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

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

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

You're getting that exception because you're calling _assert() rather than Logging._assert(). The error message tells you it's looking for _assert() in the module's global namespace rather than in the class namespace; to get it to look in the latter, you have to explicitly specify it.

Of course, in this case, you're trying to do it while the class is still being defined and the name is not available until the class is complete, so it's going to be tough to make that work.

A solution would be to un-indent the following two lines (which I've edited to use the fully qualified name) so that they are outside the class definition; they will be executed right after it.

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

A style suggestion that will help: rather than making a class with a bunch of static methods, consider making them top-level functions in the module. Users of your module (including yourself) will find it easier to get to the functionality they want. There's no reason to use a class just to be a container; the module itself is already just such a container.

A module is basically a single *.py file (though you can create modules that have multiple files, this will do for now). When you do an import, what you're importing is a module. In your example, tkMessageBox and logging are both modules. So just make a separate file (making sure its name does not conflict with existing Python module names), save it in the same directory as your main script, and import it in your main script. If you named it mylogging.py then you would import mylogging and access functions in it as mylogging.clearOldLogs() or whatever (similar to how you'd address them now as a class).

"Global" names in Python are not truly global, they're only global to the module they're defined in. So a module is a good way to compartmentalize your functionality, especially parts (like logging) that you anticipate reusing in a lot of your future scripts.

沫雨熙 2024-10-16 23:02:13

将该行替换

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

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

This is因为您将 _assert 定义为类的静态方法,并且即使您正在调用静态方法,也必须将其引用为 ClassName.methodName来自此类实例的方法。

Replace the line

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

with

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

This is because you define _assert as a static method of the class, and static method has to be referred to as ClassName.methodName even if you are calling it from a method for an instance of this class.

一袭水袖舞倾城 2024-10-16 23:02:13

一些评论。

  1. 不要使用双下划线,除非您绝对确定必须使用双下划线以及原因。

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

  3. 类仅在类定义的末尾创建。 Python 就是这样。但您的错误与此无关。

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

    # Settings 是一个单独的类(不依赖于这个类)。    
    记录器 = __createLogger(Settings.LOGGING)
    __clearOldLogs(设置.日志记录)
    

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

  5. __createLogger 是一个奇怪的函数。这个类不是叫Logger吗?这不应该只是 Logger 类的 __init__ 吗?

Some comments.

  1. Do not use double underscores unless you are absolutely sure that you have to and why.

  2. To access the _assert method, you call it with self. Like so: self._assert(Logger, 'Logger does not exist yet! Why?') In a static method, as in your example, you use the class name: Logger._assert(). Python is very explicit.

  3. Classes are only created at the END of the class definition. That's just how it is with Python. But your error is not related to that.

  4. I'm not sure what this code is supposed to do:

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

    But I suspect you should put it in the __init__ method. I do not immediately see any code that needs access to the class during class construction.

  5. __createLogger is a strange function. Isn't the class called Logger? Shouldn't that just be the __init__ of the Logger class?

最单纯的乌龟 2024-10-16 23:02:13

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

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

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

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

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

I think it just needs to be Logging._assert. Python doesn't do namespace resolution the way java does: an unqualified name is either method-local or global. Enclosing scopes like classes won't be searched automatically.

The chapter and verse on how Python deals with names is in section 4.1. Naming and binding in the language reference manual. The bit that's relevant here is probably:

The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods

Contrast this with names defined in functions, which are inherited downwards (into method-local functions and classes):

If the definition occurs in a function block, the scope extends to any blocks contained within the defining one, unless a contained block introduces a different binding for the name

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文