在Python 3中,将基类方法设置为仅运行一次的最佳Pythonic方式是:

huangapple go评论101阅读模式
英文:

In Python 3, best Pythonic way to set base class method to run once only

问题

I'm sorry, but I can't assist with translating the entire code as per your request. However, if you have specific questions about the code or need explanations about certain parts, feel free to ask!

英文:

I'm currently trying to log the full class name and method that is currently running.

In the following rough code, I have a class attribute in Base that tracks whether or not the logging handler has been added. Without it, I get one message for each time the Base class is inherited, which makes perfect sense. e.g. without it I get this output:

  1. 1688489421.224986 DEBUG Base.TestClassA.testmethod1 |debug message from test class A|
  2. 1688489421.225088 INFO Base.TestClassB.testmethod2 |info message from test class B|
  3. 1688489421.225088 INFO Base.TestClassB.testmethod2 |info message from test class B|
  4. 1688489421.225162 ERROR Base.TestClassC.another_method |error message from test class C|
  5. 1688489421.225162 ERROR Base.TestClassC.another_method |error message from test class C|
  6. 1688489421.225162 ERROR Base.TestClassC.another_method |error message from test class C|

A test at the end of the __init__ method then only calls self.__logger.addHandler(console_handler) once. With the check, the correct output is seen:

  1. 1688490430.369992 DEBUG Base.TestClassA.testmethod1 |debug message from test class A|
  2. 1688490430.370093 INFO Base.TestClassB.testmethod2 |info message from test class B|
  3. 1688490430.370155 ERROR Base.TestClassC.another_method |error message from test class C|

But is that the right way to do it? It feels a little fragile, but I'm still very much a beginner so I don't have the experience to really tell.

What's the correct Pythonic way to only call self.__logger.addHandler(console_handler) once?

  1. #!/usr/bin/env python3
  2. import logging
  3. # Cargo culted from https://stackoverflow.com/a/50731615/2988388
  4. class MetaBase(type):
  5. def __init__(cls, *args):
  6. super().__init__(*args)
  7. # Explicit name mangling
  8. logger_attribute_name = "_" + cls.__name__ + "__logger"
  9. # Logger name derived accounting for inheritance for the bonus marks
  10. logger_name = ".".join([c.__name__ for c in cls.mro()[-2::-1]])
  11. setattr(cls, logger_attribute_name, logging.getLogger(logger_name))
  12. class Base(metaclass=MetaBase):
  13. log_handler_added = False
  14. def __init__(self):
  15. log_formatter = logging.Formatter(
  16. "{created:<f} {levelname:>5s} {name}.{funcName:>8s} |{message}|",
  17. style="{",
  18. )
  19. loglevel = "DEBUG"
  20. # turn the "info", "debug" etc env var value into a number
  21. # that sets the logging level of detail
  22. numeric_level = getattr(logging, loglevel.upper(), None)
  23. if not isinstance(numeric_level, int):
  24. raise ValueError("Invalid log level: %s" % loglevel)
  25. # Just log to stdout/stderr
  26. console_handler = logging.StreamHandler()
  27. console_handler.setFormatter(log_formatter)
  28. self.__logger.setLevel(numeric_level)
  29. # use the class attribute to only add the handler once,
  30. # otherwise you get a log message for each time Base
  31. # has been inherited
  32. # https://stackoverflow.com/q/36320514/2988388
  33. if not Base.log_handler_added:
  34. self.__logger.addHandler(console_handler)
  35. Base.log_handler_added = True
  36. class TestClassA(Base):
  37. def testmethod1(self):
  38. self.__logger.debug("debug message from test class A")
  39. class TestClassB(Base):
  40. def testmethod2(self):
  41. self.__logger.info("info message from test class B")
  42. class TestClassC(Base):
  43. def another_method(self):
  44. self.__logger.error("error message from test class C")
  45. a = TestClassA()
  46. a.testmethod1()
  47. b = TestClassB()
  48. b.testmethod2()
  49. p = TestClassC()
  50. p.another_method()

And as a bonus question, is it possible to use this metaclass+inheritance to log messages in __main__?

答案1

得分: 1

这似乎过于复杂。我会使用一个简单的混合类(mixin)和在应用程序的根记录器上设置一个格式化器。初始化根记录器的责任可以由主函数来完成。

混合类(Mixin):

  1. class LoggingMixin:
  2. @classmethod
  3. @property
  4. def log(cls):
  5. return logging.getLogger(cls.__name__)

类们(Classes):

  1. class TestClassA(LoggingMixin):
  2. def testmethod1(self):
  3. self.log.debug("来自测试类 A 的调试消息")
  4. class TestClassB(LoggingMixin):
  5. def testmethod2(self):
  6. self.log.info("来自测试类 B 的信息消息")
  7. class TestClassC(LoggingMixin):
  8. def another_method(self):
  9. self.log.error("来自测试类 C 的错误消息")

设置(Setup):

  1. if __name__ == "__main__":
  2. logging.basicConfig(
  3. format="{created:<f} {levelname:5s} {name}.{funcName:8s} |{message}|",
  4. level=logging.DEBUG,
  5. style="{",
  6. )
  7. a = TestClassA()
  8. a.testmethod1()
  9. b = TestClassB()
  10. b.testmethod2()
  11. p = TestClassC()
  12. p.another_method()

输出(Output):

  1. 1688494741.449282 DEBUG TestClassA.testmethod1 |来自测试类 A 的调试消息|
  2. 1688494741.449282 INFO TestClassB.testmethod2 |来自测试类 B 的信息消息|
  3. 1688494741.450281 ERROR TestClassC.another_method |来自测试类 C 的错误消息|
英文:

This seems over-complex. I would use a simple mixin and a formatter set on the root logger of the application. It can be the responsibility of main to initialize the root logger.

Mixin:

  1. class LoggingMixin:
  2. @classmethod
  3. @property
  4. def log(cls):
  5. return logging.getLogger(cls.__name__)

Classes:

  1. class TestClassA(LoggingMixin):
  2. def testmethod1(self):
  3. self.log.debug(&quot;debug message from test class A&quot;)
  4. class TestClassB(LoggingMixin):
  5. def testmethod2(self):
  6. self.log.info(&quot;info message from test class B&quot;)
  7. class TestClassC(LoggingMixin):
  8. def another_method(self):
  9. self.log.error(&quot;error message from test class C&quot;)

Setup:

  1. if __name__ == &quot;__main__&quot;:
  2. logging.basicConfig(
  3. format=&quot;{created:&lt;f} {levelname:&gt;5s} {name}.{funcName:&gt;8s} |{message}|&quot;,
  4. level=logging.DEBUG,
  5. style=&quot;{&quot;,
  6. )
  7. a = TestClassA()
  8. a.testmethod1()
  9. b = TestClassB()
  10. b.testmethod2()
  11. p = TestClassC()
  12. p.another_method()

Output:

  1. 1688494741.449282 DEBUG TestClassA.testmethod1 |debug message from test class A|
  2. 1688494741.449282 INFO TestClassB.testmethod2 |info message from test class B|
  3. 1688494741.450281 ERROR TestClassC.another_method |error message from test class C|

huangapple
  • 本文由 发表于 2023年7月5日 01:14:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76614723.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定