在Python中,如何获得一个扩展类元素以显示在autocomplete中?

在Python中,如何获得一个扩展类元素以显示在autocomplete中?,python,pycharm,Python,Pycharm,我是新手,所以使用我在网上找到的一个示例添加了一些自定义日志记录级别。这将包含在一个库中,该库将导入到各种脚本中。它按预期工作,但添加的级别没有显示在自动完成列表中(使用PyCharm),PyCharm抱怨记录器中有未解析的属性引用。当我编码并输入“LOGGER”时,我会看到可供选择的正常错误、警告、信息等,但我的自定义级别“verbose”不在列表中。随着时间的推移,将会有更多的自定义级别添加,这也将推广到一个开发团队,所以我需要让它工作 知道为什么在我的自动完成列表中不能选择verbose吗

我是新手,所以使用我在网上找到的一个示例添加了一些自定义日志记录级别。这将包含在一个库中,该库将导入到各种脚本中。它按预期工作,但添加的级别没有显示在自动完成列表中(使用PyCharm),PyCharm抱怨记录器中有未解析的属性引用。当我编码并输入“LOGGER”时,我会看到可供选择的正常错误、警告、信息等,但我的自定义级别“verbose”不在列表中。随着时间的推移,将会有更多的自定义级别添加,这也将推广到一个开发团队,所以我需要让它工作

知道为什么在我的自动完成列表中不能选择verbose吗

这是我的档案

px_logger.py

from logging import getLoggerClass, addLevelName, setLoggerClass, NOTSET

public class PxLogger(getLoggerClass()):
    def __init__(self, name, level=NOTSET):
        super(PxLogger, self).__init__(name, level)

        addLevelName(5, "VERBOSE")

    def verbose(self, msg, *args, **kwargs):
        """Custom logger level - verbose"""
        if self.isEnabledFor(5):
            self._log(5, msg, args, **kwargs)
my_script.py

import json
import logging.config
from px_logger import PxLogger

logging.setLoggerClass(PxLogger)
LOGGER = logging.getLogger(__name__)

with open('../logging.json') as f:  # load logging config file
    CONFIG_DICT = json.load(f)
logging.config.dictConfig(CONFIG_DICT)

LOGGER.verbose('Test verbose message')
屏幕输出

VERBOSE - Test verbose message

PyCharm提供了多种实现方法

在内部,它用于确定标准库对象和通用第三方包的类型。 这也是PyCharm获取from的返回值类型的原因,这就是为什么它不会在自动完成中显示子类“
verbose
方法,因为它假定
LOGGER
logging.LOGGER
的实例

告诉PyCharm的类型检查器
LOGGER
PxLogger
的一个实例的最简单方法是在分配期间在代码中添加类型注释这仅适用于Python 3.5+

LOGGER: PxLogger = logging.getLogger(__name__)
如果您更进一步,您将封装自定义记录器类的定义,它将被分配为全局记录器类,并在模块内为
logging.getLogger
封装定义

这将使您的同事只需导入您的模块,而不是
日志记录
,并像使用原始
日志记录
一样使用它,而无需担心将哪个类设置为记录器类或如何注释保存其记录器的变量

沿着这条路走的时候,有三个选项包括类型检查器的类型提示

px_logger.py

# basically, import from logging whatever you may need and overwrite where necessary
from logging import getLogger as _getLogger, Logger, addLevelName, setLoggerClass, NOTSET
from typing import Optional # this only for the Python 3.5+ solution

class PxLogger(Logger):  # Note: subclass logging.Logger explicitly
    def __init__(self, name, level=NOTSET):
        super(PxLogger, self).__init__(name, level)

        addLevelName(5, "VERBOSE")

    def verbose(self, msg, *args, **kwargs):
        """Custom logger level - verbose"""
        if self.isEnabledFor(5):
            self._log(5, msg, args, **kwargs)

setLoggerClass(PxLogger)

"""
Possible approaches, implement one of the below.
The first is Python 3.5+ only.
The second and third work for both, Python 2 and Python 3.
"""
# using type annotation syntax (py35+)
def getLogger(name: Optional[str]=None) -> PxLogger:
    _logr: PxLogger = _getLogger(name)
    return _logr

# using (legacy) docstring syntax (py2and3)
def getLogger(name=None)
    """
    :param name: str
    :rtype: PxLogger
    """ 
    return _getLogger(name)

# using a stub file (py2and3)
def getLogger(name=None):
    return _getLogger(name)
import logging.config
import px_logger

LOGGER = px_logger.getLogger(__name__)

# I used basicConfig here for simplicity, dictConfig should work just as well
logging.basicConfig(level=5,
                    format='%(asctime)s - %(levelname)s [%(filename)s]: %(name)s %(funcName)20s - Message: %(message)s',
                    datefmt='%d.%m.%Y %H:%M:%S',
                    filename='myapp.log',
                    filemode='a')

LOGGER.verbose('Test verbose message')
Python 2和3存根文件方法需要在包中实际模块文件
px\u logger.py
旁边有一个名为
py\u logger.py
的文件

px_logger.pyi

# The PEP-484 syntax does not matter here. 
# The Python interpreter will ignore this file, 
# it is only relevant for the static type checker
import logging

class PxLogger(logging.Logger):
    def verbose(self, msg, *args, **kwargs) -> None: ...

def getLogger(name) -> PxLogger: ...
对于所有三种方法,您的模块
my_脚本
看起来都是一样的:

我的脚本.py

# basically, import from logging whatever you may need and overwrite where necessary
from logging import getLogger as _getLogger, Logger, addLevelName, setLoggerClass, NOTSET
from typing import Optional # this only for the Python 3.5+ solution

class PxLogger(Logger):  # Note: subclass logging.Logger explicitly
    def __init__(self, name, level=NOTSET):
        super(PxLogger, self).__init__(name, level)

        addLevelName(5, "VERBOSE")

    def verbose(self, msg, *args, **kwargs):
        """Custom logger level - verbose"""
        if self.isEnabledFor(5):
            self._log(5, msg, args, **kwargs)

setLoggerClass(PxLogger)

"""
Possible approaches, implement one of the below.
The first is Python 3.5+ only.
The second and third work for both, Python 2 and Python 3.
"""
# using type annotation syntax (py35+)
def getLogger(name: Optional[str]=None) -> PxLogger:
    _logr: PxLogger = _getLogger(name)
    return _logr

# using (legacy) docstring syntax (py2and3)
def getLogger(name=None)
    """
    :param name: str
    :rtype: PxLogger
    """ 
    return _getLogger(name)

# using a stub file (py2and3)
def getLogger(name=None):
    return _getLogger(name)
import logging.config
import px_logger

LOGGER = px_logger.getLogger(__name__)

# I used basicConfig here for simplicity, dictConfig should work just as well
logging.basicConfig(level=5,
                    format='%(asctime)s - %(levelname)s [%(filename)s]: %(name)s %(funcName)20s - Message: %(message)s',
                    datefmt='%d.%m.%Y %H:%M:%S',
                    filename='myapp.log',
                    filemode='a')

LOGGER.verbose('Test verbose message')
Autocomplete适用于所有三种方法:

方法2和方法3已经在PyCharm 2018.1 CE中使用Python 2.7.15和3.6.5进行了测试

注意: 在这个答案的上一个版本中,我指出docstring方法虽然显示了PxLogger类的自定义
verbose()
方法,但缺少基类的方法。这是因为您从
logging.getLoggerClass
返回的任何内容派生PxLogger,即任意类。 如果显式地将PxLogger设置为
logging.Logger
的子类,则类型检查器知道基类,并且可以正确解析其自动完成方法


无论如何,我不建议对
getLoggerClass
的返回值进行子类化。您需要确定您从中得到了什么,而不是依赖于返回正确内容的函数。

PyCharm提供了多种实现方法

在内部,它用于确定标准库对象和通用第三方包的类型。 这也是PyCharm获取from的返回值类型的原因,这就是为什么它不会在自动完成中显示子类“
verbose
方法,因为它假定
LOGGER
logging.LOGGER
的实例

告诉PyCharm的类型检查器
LOGGER
PxLogger
的一个实例的最简单方法是在分配期间在代码中添加类型注释这仅适用于Python 3.5+

LOGGER: PxLogger = logging.getLogger(__name__)
如果您更进一步,您将封装自定义记录器类的定义,它将被分配为全局记录器类,并在模块内为
logging.getLogger
封装定义

这将使您的同事只需导入您的模块,而不是
日志记录
,并像使用原始
日志记录
一样使用它,而无需担心将哪个类设置为记录器类或如何注释保存其记录器的变量

沿着这条路走的时候,有三个选项包括类型检查器的类型提示

px_logger.py

# basically, import from logging whatever you may need and overwrite where necessary
from logging import getLogger as _getLogger, Logger, addLevelName, setLoggerClass, NOTSET
from typing import Optional # this only for the Python 3.5+ solution

class PxLogger(Logger):  # Note: subclass logging.Logger explicitly
    def __init__(self, name, level=NOTSET):
        super(PxLogger, self).__init__(name, level)

        addLevelName(5, "VERBOSE")

    def verbose(self, msg, *args, **kwargs):
        """Custom logger level - verbose"""
        if self.isEnabledFor(5):
            self._log(5, msg, args, **kwargs)

setLoggerClass(PxLogger)

"""
Possible approaches, implement one of the below.
The first is Python 3.5+ only.
The second and third work for both, Python 2 and Python 3.
"""
# using type annotation syntax (py35+)
def getLogger(name: Optional[str]=None) -> PxLogger:
    _logr: PxLogger = _getLogger(name)
    return _logr

# using (legacy) docstring syntax (py2and3)
def getLogger(name=None)
    """
    :param name: str
    :rtype: PxLogger
    """ 
    return _getLogger(name)

# using a stub file (py2and3)
def getLogger(name=None):
    return _getLogger(name)
import logging.config
import px_logger

LOGGER = px_logger.getLogger(__name__)

# I used basicConfig here for simplicity, dictConfig should work just as well
logging.basicConfig(level=5,
                    format='%(asctime)s - %(levelname)s [%(filename)s]: %(name)s %(funcName)20s - Message: %(message)s',
                    datefmt='%d.%m.%Y %H:%M:%S',
                    filename='myapp.log',
                    filemode='a')

LOGGER.verbose('Test verbose message')
Python 2和3存根文件方法需要在包中实际模块文件
px\u logger.py
旁边有一个名为
py\u logger.py
的文件

px_logger.pyi

# The PEP-484 syntax does not matter here. 
# The Python interpreter will ignore this file, 
# it is only relevant for the static type checker
import logging

class PxLogger(logging.Logger):
    def verbose(self, msg, *args, **kwargs) -> None: ...

def getLogger(name) -> PxLogger: ...
对于所有三种方法,您的模块
my_脚本
看起来都是一样的:

我的脚本.py

# basically, import from logging whatever you may need and overwrite where necessary
from logging import getLogger as _getLogger, Logger, addLevelName, setLoggerClass, NOTSET
from typing import Optional # this only for the Python 3.5+ solution

class PxLogger(Logger):  # Note: subclass logging.Logger explicitly
    def __init__(self, name, level=NOTSET):
        super(PxLogger, self).__init__(name, level)

        addLevelName(5, "VERBOSE")

    def verbose(self, msg, *args, **kwargs):
        """Custom logger level - verbose"""
        if self.isEnabledFor(5):
            self._log(5, msg, args, **kwargs)

setLoggerClass(PxLogger)

"""
Possible approaches, implement one of the below.
The first is Python 3.5+ only.
The second and third work for both, Python 2 and Python 3.
"""
# using type annotation syntax (py35+)
def getLogger(name: Optional[str]=None) -> PxLogger:
    _logr: PxLogger = _getLogger(name)
    return _logr

# using (legacy) docstring syntax (py2and3)
def getLogger(name=None)
    """
    :param name: str
    :rtype: PxLogger
    """ 
    return _getLogger(name)

# using a stub file (py2and3)
def getLogger(name=None):
    return _getLogger(name)
import logging.config
import px_logger

LOGGER = px_logger.getLogger(__name__)

# I used basicConfig here for simplicity, dictConfig should work just as well
logging.basicConfig(level=5,
                    format='%(asctime)s - %(levelname)s [%(filename)s]: %(name)s %(funcName)20s - Message: %(message)s',
                    datefmt='%d.%m.%Y %H:%M:%S',
                    filename='myapp.log',
                    filemode='a')

LOGGER.verbose('Test verbose message')
Autocomplete适用于所有三种方法:

方法2和方法3已经在PyCharm 2018.1 CE中使用Python 2.7.15和3.6.5进行了测试

注意: 在这个答案的上一个版本中,我指出docstring方法虽然显示了PxLogger类的自定义
verbose()
方法,但缺少基类的方法。这是因为您从
logging.getLoggerClass
返回的任何内容派生PxLogger,即任意类。 如果显式地将PxLogger设置为
logging.Logger
的子类,则类型检查器知道基类,并且可以正确解析其自动完成方法


无论如何,我不建议对
getLoggerClass
的返回值进行子类化。您需要确定您从中派生的是什么,而不是依赖于返回正确内容的函数。

用键入符号注释变量

LOGGER: PxLogger = logging.getLogger(__name__)
编辑:

上述解决方案适用于Python 3.6+。使用