目录

前言

一、package基本概念

1.Regular packages

 2.namespace package

二、层次结构

core

 api

点关注,防走丢,如有纰漏之处,请留言指教,非常感谢

参阅



前言

稍微正式一点的项目,都会通过将代码组织成有层次结构的组织,以便于管理和持续维护。Python 通过(package)包的方式来组织代码,包是一种特殊的模块(module)。平时写的一些python代码都比较随意,更多的是直接作为脚本来使用,作为一个功能来用,但是一般真正的python项目工程化能力才是作为代码工程师应该掌握的能力。这样的层级结构才是作为一个项目或者是作为模块长久维护的最好形式。


一、package基本概念

一般要理解一种语言的工程化方法可以参考已经开源的一些优秀项目,就以我们通过pip install的项目,常用的pandas来看:

python simpleguitk包冲突 python packing_ci

这就是一个标准的工程化python项目,这样的工程化项目作为package包使用泛用性和可持续维护性才高,单单只写功能代码和一些基础的方法和类来构建项目是很不好维护的。那么我们就以该项目来看python工程化项目需要哪些板块。

将某些功能相近的文件组织在同一文件夹下,就需要运用包的概念。包对应于文件夹,使用包的方式跟模块也类似,唯一需要注意的是,当文件夹当作包使用时,文件夹需要包含__init__.py文件,主要是为了避免将文件夹名当作普通的字符串。__init__.py的内容可以为空,一般用来进行包的某些初始化工作或者设置__all__值,__all__是在from package-name import *这语句使用的,全部导出定义过的模块。

  Python使用缩进对齐组织代码的执行,所有没有缩进的代码(非函数定义和类定义),都会在载入时自动执行,这些代码,可以认为是Python的main函数。

 每个文件(模块)都可以任意写一些没有缩进的代码,并且在载入时自动执行,为了区分主执行文件还是被调用的文件,Python引入了一个变量__name__,当文件是被调用时,__name__的值为模块名,当文件被执行时,__name__为'__main__'。这个特性,为测试驱动开发提供了支持,可以在每个模块中写上测试代码,这些测试代码仅当模块被Python直接执行时才会运行,代码和测试完美的结合在一起。

1.Regular packages

pandas库就是一个标准的regular packages, Regular packages 指目录下有__init__.py文件,并且允许嵌套,即目录下可以嵌套一个同样是package的子目录。

这样的包被其他模块导入的时候,会先执行目录下__init__.py里的代码。Regular packages 可以嵌套,也就是目录下的子目录也可以是一个包。例如:

python_package_dir_example = '''
parent/
    __init__.py
    one/
        __init__.py
        one/
            __init__.py
    two/
        __init__.py
    three/
        __init__.py
'''

参考部分pandas中__init__源码就可看到:

# flake8: noqa

__docformat__ = "restructuredtext"

# Let users know if they're missing any of our hard dependencies
hard_dependencies = ("numpy", "pytz", "dateutil")
missing_dependencies = []

for dependency in hard_dependencies:
    try:
        __import__(dependency)
    except ImportError as e:
        missing_dependencies.append(f"{dependency}: {e}")

if missing_dependencies:
    raise ImportError(
        "Unable to import required dependencies:\n" + "\n".join(missing_dependencies)
    )
del hard_dependencies, dependency, missing_dependencies

# numpy compat
from pandas.compat import (
    np_version_under1p18 as _np_version_under1p18,
    is_numpy_dev as _is_numpy_dev,
)

 大部分是关于pandas的依赖以及初始化设置。从pandas的很多文件夹下面都可以看到__init__.py这个文件。能看到初始化的各种函数以及接口:

python simpleguitk包冲突 python packing_ci_02

__all__

在模块(*.py)中使用导出__all__列表里的类、函数、变量等成员,否则将导出所有不以下划线开头(私有)的成员,在模块中使用__all__属性可避免在相互引用时的命名冲突。

"""
Public API for Rolling Window Indexers.
"""

from pandas.core.indexers import check_array_indexer
from pandas.core.window.indexers import (
    BaseIndexer,
    FixedForwardWindowIndexer,
    VariableOffsetWindowIndexer,
)

__all__ = [
    "check_array_indexer",
    "BaseIndexer",
    "FixedForwardWindowIndexer",
    "VariableOffsetWindowIndexer",
]

 2.namespace package

在Python 3.3之前,一个目录想被当成package被导入,必须包含__init__.py文件;而在Python 3.3及以后的版本中,__init__.py文件可以不需要,直接使用import后者from语法,就能直接导入目录,这样的目录称为namespace package。

换句话说,>=Python 3.3之后,存在两种package,一种是常规的package,即包含__init__.py的目录,一种是namespace package,即不包含__init__.py的目录。

当引入namespace package之后,Python的搜寻规则总结如下(这里以绝对导入为例,如果是相对导入,sys.path换成当前目录即可):

  1. 如果按照sys.path的路径搜寻到一个常规的package,那么Python导入这个package,本次搜寻返回;
  2. 如果按照sys.path的路径搜寻到一个module文件,那么导入这个文件,本次搜寻返回;
  3. 如果按照sys.path的路径搜寻到一个目录,并且不包含__init__.py文件,那么就将这个目录记录下来(即namespace package),同时继续按照sys.path中下一个路径开始搜寻
  4. 如果经过1,2,3步骤都没有搜寻到指定的目录,那么,继续按照sys.path中的一个路径开始搜寻

当遍历完成所有的sys.path中的路径,同时也没有发现常规的package和module文件,那么,就按照namespace package的__path__属性包含的路径,再进行一次搜寻,如果找到,就导入,否则,导入失败。

二、层次结构

很多开源项目的层次结构都有所不同,但是都有几个相同的目录。我们知道在python的类中,没有真正的私有化,不管是方法还是属性,为了编程的需要,约定加了下划线 _ 的属性和方法不属于API,不应该在类的外面访问,也不会被from M import * 导入。

故出现了目录为_xx的目录一般都为包内函数调用,一般仅作为私有方法使用,解释器不会强制执行私有化的操作。

core

一般核心代码都在core目录里面:

python simpleguitk包冲突 python packing_ci_03

 api

程序的接口配置:

""" public toolkit API """
from pandas.api import (  # noqa
    extensions,
    indexers,
    types,
)

 其他根据目录名就可理解,大体了解到这里1基本可以自己构建一个简易的项目了。

点关注,防走丢,如有纰漏之处,请留言指教,非常感谢

以上就是本期全部内容。我是fanstuck ,有问题大家随时留言讨论 ,我们下期见。


参阅

python开源——工程项目结构