标签 python 下的文章

单元测试可能令人生畏,但是这些 Python 模块会使你的生活变得更容易。

在这个教程中,你将学到如何对执行 HTTP 请求代码的进行单元测试。也就是说,你将看到用 Python 对 API 进行单元测试的艺术。

单元测试是指对单个行为的测试。在测试中,一个众所周知的经验法则就是隔离那些需要外部依赖的代码。

比如,当测试一段执行 HTTP 请求的代码时,建议在测试过程中,把真正的调用替换成一个假的的调用。这种情况下,每次运行测试的时候,就可以对它进行单元测试,而不需要执行一个真正的 HTTP 请求。

问题就是,怎样才能隔离这些代码?

这就是我希望在这篇博文中回答的问题!我不仅会向你展示如果去做,而且也会权衡不同方法之间的优点和缺点。

要求:

使用一个天气状况 REST API 的演示程序

为了更好的解决这个问题,假设你正在创建一个天气状况的应用。这个应用使用第三方天气状况 REST API 来检索一个城市的天气信息。其中一个需求是生成一个简单的 HTML 页面,像下面这个图片:

web page displaying London weather

伦敦的天气,OpenWeatherMap。图片是作者自己制作的。

为了获得天气的信息,必须得去某个地方找。幸运的是,通过 OpenWeatherMap 的 REST API 服务,可以获得一切需要的信息。

好的,很棒,但是我该怎么用呢?

通过发送一个 GET 请求到:https://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={api_key}&units=metric,就可以获得你所需要的所有东西。在这个教程中,我会把城市名字设置成一个参数,并确定使用公制单位。

检索数据

使用 requests 模块来检索天气数据。你可以创建一个接收城市名字作为参数的函数,然后返回一个 JSON。JSON 包含温度、天气状况的描述、日出和日落时间等数据。

下面的例子演示了这样一个函数:

def find_weather_for(city: str) -> dict:
    """Queries the weather API and returns the weather data for a particular city."""
    url = API.format(city_name=city, api_key=API_KEY)
    resp = requests.get(url)
    return resp.json()

这个 URL 是由两个全局变量构成:

BASE_URL = "https://api.openweathermap.org/data/2.5/weather"
API = BASE_URL + "?q={city_name}&appid={api_key}&units=metric"

API 以这个格式返回了一个 JSON:

{
  "coord": {
    "lon": -0.13,
    "lat": 51.51
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "clear sky",
      "icon": "01d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 16.53,
    "feels_like": 15.52,
    "temp_min": 15,
    "temp_max": 17.78,
    "pressure": 1023,
    "humidity": 72
  },
  "visibility": 10000,
  "wind": {
    "speed": 2.1,
    "deg": 40
  },
  "clouds": {
    "all": 0
  },
  "dt": 1600420164,
  "sys": {
    "type": 1,
    "id": 1414,
    "country": "GB",
    "sunrise": 1600407646,
    "sunset": 1600452509
  },
  "timezone": 3600,
  "id": 2643743,
  "name": "London",
  "cod": 200

当调用 resp.json() 的时候,数据是以 Python 字典的形式返回的。为了封装所有细节,可以用 dataclass 来表示它们。这个类有一个工厂方法,可以获得这个字典并且返回一个 WeatherInfo 实例。

这种办法很好,因为可以保持这种表示方法的稳定。比如,如果 API 改变了 JSON 的结构,就可以在同一个地方(from_dict 方法中)修改逻辑。其他代码不会受影响。你也可以从不同的源获得信息,然后把它们都整合到 from_dict 方法中。

@dataclass
class WeatherInfo:
    temp: float
    sunset: str
    sunrise: str
    temp_min: float
    temp_max: float
    desc: str

    @classmethod
    def from_dict(cls, data: dict) -> "WeatherInfo":
        return cls(
            temp=data["main"]["temp"],
            temp_min=data["main"]["temp_min"],
            temp_max=data["main"]["temp_max"],
            desc=data["weather"][0]["main"],
            sunset=format_date(data["sys"]["sunset"]),
            sunrise=format_date(data["sys"]["sunrise"]),
        )

现在来创建一个叫做 retrieve_weather 的函数。使用这个函数调用 API,然后返回一个 WeatherInfo,这样就可创建你自己的 HTML 页面。

def retrieve_weather(city: str) -> WeatherInfo:
    """Finds the weather for a city and returns a WeatherInfo instance."""
    data = find_weather_for(city)
    return WeatherInfo.from_dict(data)

很好,我们的 app 现在有一些基础了。在继续之前,对这些函数进行单元测试。

1、使用 mock 测试 API

根据维基百科 模拟对象 mock object 是通过模仿真实对象来模拟它行为的一个对象。在 Python 中,你可以使用 unittest.mock 库来 模拟 mock 任何对象,这个库是标准库中的一部分。为了测试 retrieve_weather 函数,可以模拟 requests.get,然后返回静态数据。

pytest-mock

在这个教程中,会使用 pytest 作为测试框架。通过插件,pytest 库是非常具有扩展性的。为了完成我们的模拟目标,要用 pytest-mock。这个插件抽象化了大量 unittest.mock 中的设置,也会让你的代码更简洁。如果你感兴趣的话,我在 另一篇博文中 会有更多的讨论。

好的,言归正传,现在看代码。

下面是一个 retrieve_weather 函数的完整测试用例。这个测试使用了两个 fixture:一个是由 pytest-mock 插件提供的 mocker fixture, 还有一个是我们自己的。就是从之前请求中保存的静态数据。

@pytest.fixture()
def fake_weather_info():
    """Fixture that returns a static weather data."""
    with open("tests/resources/weather.json") as f:
        return json.load(f)
def test_retrieve_weather_using_mocks(mocker, fake_weather_info):
    """Given a city name, test that a HTML report about the weather is generated
    correctly."""
    # Creates a fake requests response object
    fake_resp = mocker.Mock()
    # Mock the json method to return the static weather data
    fake_resp.json = mocker.Mock(return_value=fake_weather_info)
    # Mock the status code
    fake_resp.status_code = HTTPStatus.OK

    mocker.patch("weather_app.requests.get", return_value=fake_resp)

    weather_info = retrieve_weather(city="London")
    assert weather_info == WeatherInfo.from_dict(fake_weather_info)

如果运行这个测试,会获得下面的输出:

============================= test session starts ==============================
...[omitted]...
tests/test_weather_app.py::test_retrieve_weather_using_mocks PASSED      [100%]
============================== 1 passed in 0.20s ===============================
Process finished with exit code 0

很好,测试通过了!但是...生活并非一帆风顺。这个测试有优点,也有缺点。现在来看一下。

优点

好的,有一个之前讨论过的优点就是,通过模拟 API 的返回值,测试变得简单了。将通信和 API 隔离,这样测试就可以预测了。这样总会返回你需要的东西。

缺点

对于缺点,问题就是,如果不再想用 requests 了,并且决定回到标准库的 urllib,怎么办。每次改变 find_weather_for 的代码,都得去适配测试。好的测试是,当你修改代码实现的时候,测试时不需要改变的。所以,通过模拟,你最终把测试和实现耦合在了一起。

而且,另一个不好的方面是你需要在调用函数之前进行大量设置——至少是三行代码。

...
    # Creates a fake requests response object
    fake_resp = mocker.Mock()
    # Mock the json method to return the static weather data
    fake_resp.json = mocker.Mock(return_value=fake_weather_info)
    # Mock the status code
    fake_resp.status_code = HTTPStatus.OK
...

我可以做的更好吗?

是的,请继续看。我现在看看怎么改进一点。

使用 responses

mocker 功能模拟 requests 有点问题,就是有很多设置。避免这个问题的一个好办法就是使用一个库,可以拦截 requests 调用并且给它们 打补丁 patch 。有不止一个库可以做这件事,但是对我来说最简单的是 responses。我们来看一下怎么用,并且替换 mock

@responses.activate
def test_retrieve_weather_using_responses(fake_weather_info):
    """Given a city name, test that a HTML report about the weather is generated
    correctly."""
    api_uri = API.format(city_name="London", api_key=API_KEY)
    responses.add(responses.GET, api_uri, json=fake_weather_info, status=HTTPStatus.OK)

    weather_info = retrieve_weather(city="London")
    assert weather_info == WeatherInfo.from_dict(fake_weather_info)

这个函数再次使用了我们的 fake_weather_info fixture。

然后运行测试:

============================= test session starts ==============================
...
tests/test_weather_app.py::test_retrieve_weather_using_responses PASSED  [100%]
============================== 1 passed in 0.19s ===============================

非常好!测试也通过了。但是...并不是那么棒。

优点

使用诸如 responses 这样的库,好的方面就是不需要再给 requests 打补丁 patch 。通过将这层抽象交给库,可以减少一些设置。然而,如果你没注意到的话,还是有一些问题。

缺点

unittest.mock 很像,测试和实现再一次耦合了。如果替换 requests,测试就不能用了。

2、使用适配器测试 API

如果用模拟让测试耦合了,我能做什么?

设想下面的场景:假如说你不能再用 requests 了,而且必须要用 urllib 替换,因为这是 Python 自带的。不仅仅是这样,你了解了不要把测试代码和实现耦合,并且你想今后都避免这种情况。你想替换 urllib,也不想重写测试了。

事实证明,你可以抽象出执行 GET 请求的代码。

真的吗?怎么做?

可以使用 适配器 adapter 来抽象它。适配器是一种用来封装其他类的接口,并作为新接口暴露出来的一种设计模式。用这种方式,就可以修改适配器而不需要修改代码了。比如,在 find_weather_for 函数中,封装关于 requests 的所有细节,然后把这部分暴露给只接受 URL 的函数。

所以,这个:

def find_weather_for(city: str) -> dict:
    """Queries the weather API and returns the weather data for a particular city."""
    url = API.format(city_name=city, api_key=API_KEY)
    resp = requests.get(url)
    return resp.json()

变成这样:

def find_weather_for(city: str) -> dict:
    """Queries the weather API and returns the weather data for a particular city."""
    url = API.format(city_name=city, api_key=API_KEY)
    return adapter(url)

然后适配器变成这样:

def requests_adapter(url: str) -> dict:
    resp = requests.get(url)
    return resp.json()

现在到了重构 retrieve_weather 函数的时候:

def retrieve_weather(city: str) -> WeatherInfo:
    """Finds the weather for a city and returns a WeatherInfo instance."""
    data = find_weather_for(city, adapter=requests_adapter)
    return WeatherInfo.from_dict(data)

所以,如果你决定改为使用 urllib 的实现,只要换一下适配器:

def urllib_adapter(url: str) -> dict:
    """An adapter that encapsulates urllib.urlopen"""
    with urllib.request.urlopen(url) as response:
        resp = response.read()
    return json.loads(resp)
def retrieve_weather(city: str) -> WeatherInfo:
    """Finds the weather for a city and returns a WeatherInfo instance."""
    data = find_weather_for(city, adapter=urllib_adapter)
    return WeatherInfo.from_dict(data)

好的,那测试怎么做?

为了测试 retrieve_weather, 只要创建一个在测试过程中使用的假的适配器:

@responses.activate
def test_retrieve_weather_using_adapter(
    fake_weather_info,
):
    def fake_adapter(url: str):
        return fake_weather_info

    weather_info = retrieve_weather(city="London", adapter=fake_adapter)
    assert weather_info == WeatherInfo.from_dict(fake_weather_info)

如果运行测试,会获得:

============================= test session starts ==============================
tests/test_weather_app.py::test_retrieve_weather_using_adapter PASSED    [100%]
============================== 1 passed in 0.22s ===============================

优点

这个方法的优点是可以成功将测试和实现解耦。使用 依赖注入 dependency injection 在测试期间注入一个假的适配器。你也可以在任何时候更换适配器,包括在运行时。这些事情都不会改变任何行为。

缺点

缺点就是,因为你在测试中用了假的适配器,如果在实现中往适配器中引入了一个 bug,测试的时候就不会发现。比如说,往 requests 传入了一个有问题的参数,像这样:

def requests_adapter(url: str) -> dict:
    resp = requests.get(url, headers=<some broken headers>)
    return resp.json()

在生产环境中,适配器会有问题,而且单元测试没办法发现。但是事实是,之前的方法也会有同样的问题。这就是为什么不仅要单元测试,并且总是要集成测试。也就是说,要考虑另一个选项。

3、使用 VCR.py 测试 API

现在终于到了讨论我们最后一个选项了。诚实地说,我也是最近才发现这个。我用 模拟 mock 也很长时间了,而且总是有一些问题。VCR.py 是一个库,它可以简化很多 HTTP 请求的测试。

它的工作原理是将第一次运行测试的 HTTP 交互记录为一个 YAML 文件,叫做 cassette。请求和响应都会被序列化。当第二次运行测试的时候,VCT.py 将拦截对请求的调用,并且返回一个响应。

现在看一下下面如何使用 VCR.py 测试 retrieve_weather

@vcr.use_cassette()
def test_retrieve_weather_using_vcr(fake_weather_info):
    weather_info = retrieve_weather(city="London")
    assert weather_info == WeatherInfo.from_dict(fake_weather_info)

天呐,就这样?没有设置?@vcr.use_cassette() 是什么?

是的,就这样!没有设置,只要一个 pytest 标注告诉 VCR 去拦截调用,然后保存 cassette 文件。

cassette 文件是什么样?

好问题。这个文件里有很多东西。这是因为 VCR 保存了交互中的所有细节。

interactions:
- request:
    body: null
    headers:
      Accept:
      - '*/*'
      Accept-Encoding:
      - gzip, deflate
      Connection:
      - keep-alive
      User-Agent:
      - python-requests/2.24.0
    method: GET
    uri: https://api.openweathermap.org/data/2.5/weather?q=London&appid=<YOUR API KEY HERE>&units=metric
  response:
    body:
      string: '{"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":800,"main":"Clear","description":"clearsky","icon":"01d"}],"base":"stations","main":{"temp":16.53,"feels_like":15.52,"temp_min":15,"temp_max":17.78,"pressure":1023,"humidity":72},"visibility":10000,"wind":{"speed":2.1,"deg":40},"clouds":{"all":0},"dt":1600420164,"sys":{"type":1,"id":1414,"country":"GB","sunrise":1600407646,"sunset":1600452509},"timezone":3600,"id":2643743,"name":"London","cod":200}'
    headers:
      Access-Control-Allow-Credentials:
      - 'true'
      Access-Control-Allow-Methods:
      - GET, POST
      Access-Control-Allow-Origin:
      - '*'
      Connection:
      - keep-alive
      Content-Length:
      - '454'
      Content-Type:
      - application/json; charset=utf-8
      Date:
      - Fri, 18 Sep 2020 10:53:25 GMT
      Server:
      - openresty
      X-Cache-Key:
      - /data/2.5/weather?q=london&amp;units=metric
    status:
      code: 200
      message: OK
version: 1

确实很多!

真的!好的方面就是你不需要留意它。VCR.py 会为你安排好一切。

优点

现在看一下优点,我可以至少列出五个:

  • 没有设置代码。
  • 测试仍然是分离的,所以很快。
  • 测试是确定的。
  • 如果你改了请求,比如说用了错误的 header,测试会失败。
  • 没有与代码实现耦合,所以你可以换适配器,而且测试会通过。唯一有关系的东西就是请求必须是一样的。

缺点

再与模拟相比较,除了避免了错误,还是有一些问题。

如果 API 提供者出于某种原因修改了数据格式,测试仍然会通过。幸运的是,这种情况并不经常发生,而且在这种重大改变之前,API 提供者通常会给他们的 API 提供不同版本。

另一个需要考虑的事情是 就地 in place 端到端 end-to-end 测试。每次服务器运行的时候,这些测试都会调用。顾名思义,这是一个范围更广、更慢的测试。它们会比单元测试覆盖更多。事实上,并不是每个项目都需要使用它们。所以,就我看来,VCR.py 对于大多数人的需求来说都绰绰有余。

总结

就这么多了。我希望今天你了解了一些有用的东西。测试 API 客户端应用可能会有点吓人。然而,当武装了合适的工具和知识,你就可以驯服这个野兽。

我的 Github 上可以找到这个完整的应用。

这篇文章最早发表在 作者的个人博客,授权转载


via: https://opensource.com/article/21/9/unit-test-python

作者:Miguel Brito 选题:lujun9972 译者:Yufei-Yan 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Python 依然势不可挡

根据 TIOBE 指数 8 月份榜单,Python 的“市场份额”在本月又上升了 2%,达到 15.42% 的历史最高水平。自去年 10 月 Python 首次夺得该指数第一的位置以来,已经过去了 10 个月,成为除 Java 和 C 之外唯一保持能第一位置的语言。现在很难找到一个 Python 没有被广泛使用的编程领域。唯一的例外是嵌入式系统,因为 Python 是动态类型的,速度太慢。8 月榜单的前三名是:Python(15.42%)、C(14.59%)、Java(12.40%)。

消息来源:TIOBE
老王点评:现在不知道多少人开始考虑学习 Python 了?

针对星链终端的攻击已经开始了

自 2018 年以来,星链已经发射了 3000 多颗小卫星进入轨道,还有数千颗卫星计划发射。安全研究人员创建了一个可以连接到星链终端天线上的定制黑客工具,使用的是现成的零件,成本约为 25 美元。它能够暂时短路系统,以帮助绕过星链的安全保护措施,进入星链系统中先前被锁定的部分。虽然星链已经发布了一个更新,以增加攻击的难度,但除非该公司创建一个新版本的主芯片,否则根本问题无法修复。安全研究人员说,所有现有的用户终端都很脆弱。

消息来源:Wired
老王点评:最难办的就是这种需要硬件上解决的问题。

微软在 2021 年度的漏洞赏金支付上超过了谷歌

从 2021 年 7 月 1 日开始,到 2022 年 6 月 30 日,微软为 335 名研究人员颁发了 1370 万美元的奖励。而谷歌在 2021 年“破纪录”地奖励了 870 万美元。微软今年做了一些改变,为其 Office 365 产品线中出现的高影响漏洞多支付了高达 26,000 美元,其他奖项则增加了 30%。而 另外一方面,谷歌在 Linux 安全方面投入了更多的资金,毕竟现在谷歌几乎所有产品都是基于 Linux 的。

消息来源:The Register
老王点评:从某种意义上看,这是否可以代表 Linux 总体的安全状态要比 Windows 好?

学习为什么 Python 中的可观测性很重要,以及如何在你的软件开发生命周期中实现它。

 title=

你写的应用会执行很多代码,而且是以一种基本上看不到的方式执行。所以你是怎么知道:

  • 代码是否在运行?
  • 是不是在正常工作?
  • 谁在使用它,如何使用?

可观测性是一种能力,可以通过查看数据来告诉你,你的代码在做什么。在这篇文章中,主要关注的问题是分布式系统中的服务器代码。并不是说客户端应用代码的可观测性不重要,只是说客户端往往不是用 Python 写的。也不是说可观测性对数据科学不重要,而是在数据科学领域的可观测性工具(大多是 Juptyter 和快速反馈)是不同的。

为什么可观测性很重要

所以,为什么可观测性重要呢?在软件开发生命周期(SDLC)中,可观测性是一个关键的部分。

交付一个应用不是结束,这只是一个新周期的开始。在这个周期中,第一个阶段是确认这个新版本运行正常。否则的话,很有可能需要回滚。哪些功能正常运行?哪些功能有细微的错误?你需要知道发生了什么,才能知道接下来要怎么做。这些东西有时候会以奇怪的方式不能正常运行。不管是天灾,还是底层基础设施的问题,或者应用进入了一种奇怪的状态,这些东西可能在任何时间以任何理由停止工作。

在标准 SDLC 之外,你需要知道一切都在运行中。如果没有,有办法知道是怎么不能运行的,这是非常关键的。

反馈

可观测性的第一部分是获得反馈。当代码给出它正在做什么的信息时,反馈可以在很多方面提供帮助。在模拟环境或测试环境中,反馈有助于发现问题,更重要的是,以更快的方式对它们进行分类。这可以改善在验证步骤中的工具和交流。

当进行 金丝雀部署 canary deployment 或更改特性标志时,你需要知道是否要继续,还是等更长时间,或者回滚,反馈就显得很重要了。

监控

有时候你怀疑有些东西不太对。也许是一个依赖服务有问题,或者是社交网站爆出了大量你的网站的问题。也许在相关的系统中有复杂的操作,然后你想确保你的系统能完美处理。在这些情况下,你就想把可观测性系统的数据整合到控制面板上。

当写一个应用的时候,这些控制面板需要是设计标准的一部分。只有当你的应用能把数据共享给这些控制面板,它们才会把这些数据显示出来。

警报

看控制面板超过 15 分钟就像看着油漆变干一样。任何人都不应该遭受这种折磨。对于这种任务,我们要有报警系统。报警系统将可观测性数据与预期数据进行对比,当它们不匹配的时候就发出通知。完全深入研究时间管理超出了本文的范围。然而,从两方面来说,可观测应用是 报警友好的 alert-friendly

  • 它们有足够多,足够好的数据,发出的警报才是高质量的。
  • 警报有足够的数据,或者接收者可以很容易的得到数据,这样有助于找到源头。

高质量警报有三个特点:

  • 较少的错报:如果有警报,那一定是有问题了。
  • 较少的漏报:如果有问题,那一定有警报触发。
  • 及时性:警报会迅速发出以减少恢复时间。

这三个特点是互相有冲突的。你可以通过提高监测的标准来减少错误警报,代价是增加了漏报。你也可以通过降低监测的门槛来减少漏报,代价是增加错报。通过收集更多数据,你也可以同时减少错报和漏报,而代价是降低了及时性。

同时改善这三个参数就更难了。这就要求高质量的可观测性数据。更高质量的数据可以同时改善这三个特点。

日志

有的人喜欢嘲笑用打印来调试的方法。但是,在一个大多数软件都不在你本机运行的世界里,你所能做的只有打印调试。日志记录就是打印调试的一种形式。尽管它有很多缺点,但 Python 日志库提供了标准化的日志记录。更重要的是,它意味着你可以通过这些库去记录日志。

应用程序要负责配置日志的记录方式。讽刺地是,在应用程序对配置日志负责了多年以后,现在越来越不是这样了。在现代容器 编排 orchestration 环境中,现代应用程序记录标准错误和标准输出,并且信任 编排 orchestration 系统可以合理的处理日志。

然而,你不应该依赖库,或者说,其他任何地方。如果你想让操作的人知道发生了什么,使用日志,而不是打印

日志级别

日志记录的一个最重要功能就是 日志级别。不同的日志级别可以让你合理的过滤并分流日志。但是这只有在日志级别保持一致的情况下才能做到。最后,你应该在整个应用程序中保持日志级别的一致性。

选择不兼容语义的库可以通过在应用层面的适当配置来追溯修复,这只需要通过使用 Python 中最重要的通用风格做到:getLogger(__name-_)

大多数合理的库都会遵循这个约定。 过滤器 Filters 可以在日志对象发出之前就地修改它们。你可以给处理程序附加一个过滤器,这个处理程序会根据名称修改消息,使其具有合适的级别。

import logging
LOGGER=logging.getLogger(__name__)

考虑到这一点,你现在必须明确日志级别的语义。这其中有很多选项,但是下面这些是我的最爱:

  • Error:发送一个即时警告。应用程序处于一个需要操作人员引起注意的状态。(这意味着包含 CriticalError
  • Warning:我喜欢把这些称作“工作时间警报”。这种情况下,应该有人在一个工作日内关注一下。
  • Info:这是在正常工作流程中发出的。如果怀疑有问题的时候,这个是用来帮助人们了解应用程序在做什么的。
  • Debug:默认情况下,这个不应该在生产环境中出现。在模拟环境或开发环境下,可以发出来,也可以不发。如果需要更多的信息,在生产环境也可以特地被打开。

任何情况下都不要在日志中包含 个人身份信息 Personal Identifiable Information (PII)或密码。无论日志级别是什么,都是如此,比如级别更改,激活调试级别等等。日志聚合系统很少是 PII 安全 PII-safe 的,特别是随着 PII 法规的不断发展(HIPAA、GDPR 等等)。

日志聚合

现代系统几乎都是分布式的。 冗余 redundancy 扩展性 scaling ,有时是 管辖权 jurisdictional 需要更多的水平分布。微服务意味着垂直分布。登录到每个机器去查看日志已经是不现实的了。出于合理的控制原因,允许开发人员登录到机器中会给予他们更多的权限,这不是个好主意。

所有的日志都应该被发到一个聚合器。有一些商业的方案,你可以配置一个 ELK 栈,或者也可以使用其他的数据库(SQL 或则 no-SQL)。作为一个真正的低技术解决方案,你可以将日志写入文件,然后将它们发送到对象存储中。有很多解决方案,但是最重要的事情是选择一个,并且将所有东西聚合到一起。

记录查询

在将所有东西记录到一个地方后,会有很多日志。具体的聚合器可以定义如何写查询,但是无论是通过从存储中搜索还是写 NoSQL 查询,记录查询以匹配源和细节都是很有用的。

指标抓取

指标抓取 Metric Scraping 是一个 服务器拉取 server pull 模型。指标服务器定时和应用程序连接,并且拉取指标。

最后,这意味着服务器需要连接和找到所有相关的应用服务器。

以 Prometheus 为标准

如果你的指标聚合器是 Prometheus,那么 Prometheus 格式做为一个 端点 endpoint 是很有用的。但是,即使聚合器不是 Prometheus,也是很有用的。几乎所有的系统都包含与 Prometheus 端点兼容的 垫片 shim

使用客户端 Python 库给你的应用程序加一个 Prometheus 垫片,这将使它能够被大多数的指标聚合器所抓取。当 Prometheus 发现一个服务器,它就期望找到一个指标端点。这经常是应用程序路由的一部分,通常在 /metrics 路径下。不管 Web 应用的平台是什么,如果你能在一个端点下运行一个定制类型的定制字节流,Prometheus 就可以将它抓取。

对于大多数流行的框架,总有一个中间件插件或者类似的东西收集指标,如延迟和错误率。通常这还不够。你需要收集定制的应用数据:比如,每个端点的缓存 命中/缺失 hit/miss 率,数据库延迟,等等。

使用计数器

Prometheus 支持多个数据类型。一个重要且巧妙的类型就是计数器。计数器总是在前进 —— 但有一点需要注意。

当应用重置,计数器会归零。计数器中的这些“ 历时 epochs ”通过将计数器“创建时间”作为元数据发送来管理。Prometheus 知道不去比较两个不同 历时 epochs 的计数器。

使用仪表值

仪表值会简单很多:它们测量瞬时值。用它们来测量会上下起伏的数据:比如,分配的总内存大小,缓存大小,等等。

使用枚举值

枚举值对于整个应用程序的状态是很有用的,尽管它们可以以更精细的方式被收集。比如,你正使用一个 功能门控 feature-gating 框架,一个有多个状态(比如,使用中、关闭、 屏蔽 shadowing 等)的功能,也许使用枚举会更有用。

分析

分析不同于指标,因为它们要对应连续的事件。比如,在网络服务器中,事件是一个外部请求及其产生的工作。特别是,在事件完成之前事件分析是不能被发送的。

事件包含特定的指标:延迟,数量,以及可能产生的对其他服务请求的细节,等等。

结构化日志

现在一个可能的选择是将日志结构化。发送事件只发送带有正确格式的有效 载荷 payload 的日志。这个数据可以从日志聚合器请求,然后解析,并且放入一个合适的系统,这样可以对它的可见性。

错误追踪

你可以使用日志来追踪错误,也可以用分析来追踪错误。但是一个专门的错误系统还是值得的。一个为错误而优化的系统可以发送更多的错误,因为错误毕竟还是罕见的。这样它就可以发送正确的数据,并且用这些数据,它能做更多智能的事情。Python 中的错误追踪系统通常和一般的异常处理关联,然后收集数据,并且把它发到一个专门的错误聚合器。

使用 Sentry

很多情况下,自己运行 Sentry 是正确的做法。当错误发生时,就说明有些东西就出问题了。可靠地删除敏感数据是不可能的,因为一定有会出现敏感数据被发送到不应该的地方。

通常,这种工作量并不会很大:异常并不常出现。最后,这个系统并不需要很高的质量,也不需要高可靠性的备份。昨天的错误应该已经修复了,希望如此,如果没有,你还会发现的!

快速、安全、可重复:三者都要

可观测的系统开发起来更快,因为它们可以给你提供反馈。它们运行起来也更安全,因为当出问题的时候,它们也会更早的让你知道。最后,因为有反馈回路,可观测性也有助于围绕它构建可重复的过程。可观测性可以让你了解你的应用程序。而更了解它们,就胜利了一半。

磨刀不误砍柴功

构建所有的可观测层是一件困难的事情。总会让人感觉是在浪费的工作,或者更像是“可以有,但是不急”。

之后再做这个可以吗?也许吧,但是不应该。正确的构建可观测性可以加速后面所有阶段的开发:测试、监控,甚至是培训新人。在一个和科技行业一样动荡的行业,减少培训新人的工作量绝对是值得的。

事实上,可观测性很重要,所以尽早把它写出来,然后就可以在整个过程中进行维护。反过来,它也会帮你维护你的软件。


via: https://opensource.com/article/21/11/observability-python

作者:Moshe Zadka 选题:lujun9972 译者:MCGA 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

如何在 Linux 终端中运行一个 Python 程序?像这样,对吗?

python program.py

然而,如果你试图在 Ubuntu(和其他一些发行版)中使用 python 命令,它会抛出一个错误。

command ‘python’ not found, did you mean:
command ‘python3’ from deb python3
command ‘python’ from deb python-is-python3

如果你注意这个错误信息,它说明了很多东西。这里的 python 命令实际上是 python3

如果你不理解,不用担心。我将在这里详细解释。

为什么在 Ubuntu 上没有发现 python 命令?

这是因为 Python 语言不是以 python 的形式安装的,而是以 python3python2 的形式安装的(在一些老的 Ubuntu 版本中)。

在遥远的过去的某个时间点,Python 实际上是作为 python 包/可执行文件提供的。当 Python 发布第二版时,Ubuntu 和其他发行版不得不同时支持 Python 1.x 和 2.x 版本。

因此,他们将较新的 Python 版本命名为 python2,以区分这两个版本。其他应用或库也在其代码中指定 pythonpython2

最终,Python 1 版本被完全停用,但软件包继续被命名为 python2

类似地,当 Python 3 版本发布时,发行版开始同时提供 python2python3 包。

Python 2 不再被支持,Python 3.x 是你在 Ubuntu 上安装的版本。该软件包仍被命名为 python3

总结一下,你已经在 Ubuntu 上安装了 Python。它是以 python3 软件包方式使用的。

那么,当你 在 Ubuntu 上看到 “Python command not found” 的错误 时,你有什么选择?让我来介绍一下。

确保你的系统中已经安装了 Python

它应该已经安装了,但仔细检查一下也无妨。

Ubuntu 18.04 也有 Python 2,但 20.04 及更高版本只有 Python 3。不过,你有哪个版本:

type python python2 python3

正如你在下面的截图中看到的,我的系统上安装了 Python 3 版本。

Checking Python version in Ubuntu

如果你没有安装任何 Python 版本,你可以用以下命令安装 Python 3 版本。

sudo apt install python3

使用 python3 而不是 python

如果对你来说不是太麻烦,在需要的地方使用 python3 命令而不是 python

想检查已安装的 Python 版本吗?请这样输入:

python3 --version

然后你会在输出中得到版本的详细信息:

~$ python3 --version
Python 3.10.4

如果你必须运行一个 Python 程序,请像这样执行它:

python3 program.py

这在大多数情况下应该对你有用。但是,如果你使用的是一些(旧的)Python 应用,期望在其代码中运行 Python 可执行文件,你就会有问题。别担心,你也可以绕过它。

将 python3 链接为 python

你可以在你的 .bashrc 文件中创建一个永久别名,像这样:

alias python='python3'

这样,你可以运行 python 命令,而你的系统运行 python3

这在大多数情况下都会起作用,除非某些程序期望运行 /usr/bin/python。现在,你可以在 /usr/bin/python/usr/bin/python3 之间建立符号链接,但对于 Ubuntu 用户来说,存在一个更简单的选择。

对于 Ubuntu 20.04 和更高版本,如果你安装了 python-is-python3 软件包,你有一个软件包可以自动完成所有链接创建。这也是原始错误信息所提示的。

sudo apt install python-is-python3

install python is python3 ubuntu

你可以看到符号链接已经被创建,你可以使用 python 命令(实际上是运行 python3),没有任何问题。

checking python ubuntu

我希望这能澄清 Ubuntu 中 Python 软件包的问题。如果你有任何问题或建议,请告诉我。


via: https://itsfoss.com/python-not-found-ubuntu/

作者:Abhishek Prakash 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

学习这个 Python 教程,轻松提取网页的有关信息。

浏览网页可能占了你一天中的大部分时间。然而,你总是需要手动浏览,这很讨厌,不是吗?你必须打开浏览器,然后访问一个网站,单击按钮,移动鼠标……相当费时费力。如果能够通过代码与互联网交互,岂不是更好吗?

在 Python 的 requests 模块的帮助下,你可以使用 Python 从互联网中获取数据:

import requests

DATA = "https://opensource.com/article/22/5/document-source-code-doxygen-linux"
PAGE = requests.get(DATA)

print(PAGE.text)

在以上代码示例中,你首先导入了 requests 模块。接着,你创建了两个变量:其中一个叫做 DATA,它用来保存你要下载的 URL。在之后的代码中,你将能够在每次运行应用程序时提供不同的 URL。不过,就目前而言,最简单的方法是“硬编码”一个测试 URL,以达到演示目的。

另一个变量是 PAGE。代码读取了存储在 DATA 中的 URL,然后把它作为参数传入 requests.get 函数,最后用变量 PAGE 来接收函数的返回值。requests 模块及其 .get 函数的功能是:“读取”一个互联网地址(一个 URL)、访问互联网,并下载位于该地址的任何内容。

当然,其中涉及到很多步骤。幸运的是,你不必自己弄清楚,这也正是 Python 模块存在的原因。最后,你告诉 Python 打印 requests.get 存储在 PAGE 变量的 .text 字段中的所有内容。

Beautiful Soup

如果你运行上面的示例代码,你会得到示例 URL 的所有内容,并且,它们会不加选择地输出到你的终端里。这是因为在代码中,你对 requests 收集到的数据所做的唯一事情,就是打印它。然而,解析文本才是更加有趣的。

Python 可以通过其最基本的功能来“读取”文本,但解析文本允许你搜索模式、特定单词、HTML 标签等。你可以自己解析 requests 返回的文本,不过,使用专门的模块会容易得多。针对 HTML 和 XML 文本,我们有 Beautiful Soup 库。

下面这段代码完成了同样的事情,只不过,它使用了 Beautiful Soup 来解析下载的文本。因为 Beautiful Soup 可以识别 HTML 元素,所以你可以使用它的一些内置功能,让输出对人眼更友好。

例如,在程序的末尾,你可以使用 Beautiful Soup 的 .prettify 函数来处理文本(使其更美观),而不是直接打印原始文本:

from bs4 import BeautifulSoup
import requests

PAGE = requests.get("https://opensource.com/article/22/5/document-source-code-doxygen-linux")
SOUP = BeautifulSoup(PAGE.text, 'html.parser')

# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    # do a thing here
    print(SOUP.prettify())

通过以上代码,我们确保了每个打开的 HTML 标签都输出在单独的一行,并带有适当的缩进,以帮助说明标签的继承关系。实际上,Beautiful Soup 能够通过更多方式来理解 HTML 标签,而不仅仅是将它打印出来。

你可以选择打印某个特定标签,而不是打印整个页面。例如,尝试将打印的选择器从 print(SOUP.prettify()) 更改为:

print(SOUP.p)

这只会打印一个 <p> 标签。具体来说,它只打印遇到的第一个 <p> 标签。要打印所有的 <p> 标签,你需要使用一个循环。

循环

使用 Beautiful Soup 的 find_all 函数,你可以创建一个 for 循环,从而遍历 SOUP 变量中包含的整个网页。除了 <p> 标签之外,你可能也会对其他标签感兴趣,因此最好将其构建为自定义函数,由 Python 中的 def 关键字(意思是 “定义” define )指定。

def loopit():
    for TAG in SOUP.find_all('p'):
        print(TAG)

你可以随意更改临时变量 TAG 的名字,例如 ITEMi 或任何你喜欢的。每次循环运行时,TAG 中都会包含 find_all 函数的搜索结果。在此代码中,它搜索的是 <p> 标签。

函数不会自动执行,除非你显式地调用它。你可以在代码的末尾调用这个函数:

# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    # do a thing here
    loopit()

运行代码以查看所有的 <p> 标签和它们的内容。

只获取内容

你可以通过指定只需要 “ 字符串 string ”(它是 “ 单词 words ” 的编程术语)来排除打印标签。

def loopit():
    for TAG in SOUP.find_all('p'):
        print(TAG.string)

当然,一旦你有了网页的文本,你就可以用标准的 Python 字符串库进一步解析它。例如,你可以使用 lensplit 函数获得单词个数:

def loopit():
    for TAG in SOUP.find_all('p'):
        if TAG.string is not None:
            print(len(TAG.string.split()))

这将打印每个段落元素中的字符串个数,省略那些没有任何字符串的段落。要获得字符串总数,你需要用到变量和一些基本数学知识:

def loopit():
    NUM = 0
    for TAG in SOUP.find_all('p'):
        if TAG.string is not None:
            NUM = NUM + len(TAG.string.split())
    print("Grand total is ", NUM)

Python 作业

你可以使用 Beautiful Soup 和 Python 提取更多信息。以下是有关如何改进你的应用程序的一些想法:

  • 接受输入,这样你就可以在启动应用程序时,指定要下载和分析的 URL。
  • 统计页面上图片(<img> 标签)的数量。
  • 统计另一个标签中的图片(<img> 标签)的数量(例如,仅出现在 <main> div 中的图片,或仅出现在 </p> 标签之后的图片)。

via: https://opensource.com/article/22/6/analyze-web-pages-python-requests-beautiful-soup

作者:Seth Kenlon 选题:lkxed 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在 Linux 中运行一个 Python 程序只需要简单地在终端中执行 Python 文件就行。

但这对人们来说不是很方便,也不能帮助你调试你的程序。太原始了。

有几个 IDE 和文本编辑器可以用于 Python 开发。Linux 用户可以使用 PyCharm 社区版

我最近发现了另一个专门为 Python 初学者制作的 IDE。我喜欢这个应用的想法,因此我在这里与你分享。

Thonny 是一个跨平台、开源的 Python IDE,适合初学者使用

Thonny 在用户界面和用户体验方面,感觉就像 Python 版本的 Eclipse。考虑到大多数 C++ 和 Java 的初学者都是从 Eclipse 开始的,而且许多人后来一直使用它,这也不完全是一件坏事。

它不是一个新的工具。它已经出现好几年了。我没有用 Python 进行编码,所以直到最近才发现它。

Thonny 专注于 Python,提供了帮助 Python 初学者了解其程序行为的功能。让我们来看看这些功能。

即装即用

Thonny 自带 Python,所以你不需要为安装 Python 做额外的努力。这对 Linux 用户来说不是什么大事,因为大多数发行版都默认安装了 Python。

界面很简单。它给你一个编辑器,你可以写你的 Python 程序,然后点击运行按钮或使用 F5 键来运行程序。输出显示在底部。

thonny hello world

查看变量

在 “ 查看 View -> 变量 Variables ”,你可以看到所有变量的值。不需要将它们全部打印出来。

thonny variable pane

内置调试器

通过使用调试器一步步运行你的程序。你可以从顶部的菜单或使用 Ctrl + F5 键访问它。在这里你甚至不需要设置断点。你可以用 F6 进入大步骤,或用 F7 进入小步骤。

thonny step by step f6

在小步骤中,你可以看到 Python 是如何看待你的表达式的。这对新的程序员理解他们的程序为什么以某种方式表现非常有帮助。

thonny step by step f7

不止这样。对于函数调用,它会打开一个新的窗口,里面有独立的局部变量表和代码指针。超级酷!

语法错误高亮

初学者经常会犯一些简单的语法错误,如缺少小括号、引号等。Thonny 会在编辑器中立即指出来。

本地变量也可以从视觉上与全局变量区分开来。

自动补全

你不需要输入所有的东西。Thonny 支持自动补全代码,这有助于加快编码。

thonny auto complete

访问系统 shell

在工具中,你可以访问系统 shell。在这里你可以安装新的 Python 包或学习从命令行处理 Python。

thonny shell terminal

请注意,如果你使用 Flatpak 或 Snap,Thonny 可能无法访问系统 shell。

从 GUI 管理 Pip

进入工具和管理包。它会打开一个窗口,你可以从这个 GUI 中安装 Pip 软件包。

thonny manage packages

对于学习 Python 来说,功能足够好,对吗?让我们看看如何安装它。

在 Linux 上安装 Thonny

Thonny 是一个跨平台的应用。它可用于 Windows、macOS 和 Linux。

它是一个流行的应用,你可以在大多数 Linux 发行版的仓库中找到它。只要在你的系统的软件中心寻找它。

另外,你也可以随时使用你的 Linux 发行版的包管理器。

在 Debian 和基于 Ubuntu 的发行版上,你可以使用 apt 命令来安装它。

sudo apt install thonny

它会下载一堆依赖关系和大约 300MB 的软件包。

安装后,你可以在菜单中搜索它,并从那里安装它。

总结

Thonny 对于初级 Python 程序员来说是个不错的工具。不是说专家不能使用它,但它更适合在学校和学院使用。学生们会发现它有助于学习 Python 和理解他们的代码是如何以某种方式表现出来的。事实上,它最初是在爱沙尼亚的塔尔图大学开发的。

总的来说,对于 Python 学习者来说是一个很好的软件。


via: https://itsfoss.com/thonny-python-ide/

作者:Abhishek Prakash 选题:lkxed 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出