Python 通过反射的环境变量

Python Environment Variables Through Reflection

在Python 3.7 中,我写了一个Environment 帮助程序class,它允许我通过反射加载OS 环境变量。它还根据派生 class.

中声明的类型提示,将环境变量转换为本机布尔值或元组。

它有效,但是 Visual Studio 代码中的 linter 给我这个问题报告:

Class 'Environment' has no '__annotations__' member

这里是帮手class。

import os
from abc import ABC

class Environment(ABC):

    @classmethod
    def from_os(cls):
        convert = {
            bool: lambda d: d == "1",
            (): lambda d: tuple(d.split(',')),
            str: lambda d: d
        }
        values = [ 
            convert[value](os.environ[key]) 
            for key,value in cls.__annotations__.items() 
            ]
        return cls(*values)

同样,代码确实有效。我该怎么做才能让 Pylint 开心?也许有一种不同的方法可以迭代属性(和属性类型提示),以便在不使用 __annotations__?

的情况下获得相同的结果

我可以通过欺骗和添加 pylint 抑制提示让 linter 停止抱怨:

   for key,value in cls.__annotations__.items() # pylint: disable=no-member

...但这不是我正在寻找的解决方案。

如果有帮助,下面是我的助手 class 如何用于获取任意一组环境变量的示例:

from dataclasses import dataclass

@dataclass
class FWMonitoringEnv(Environment):
    # The names of these attributes are used
    # to find and load a corresponding environment variable.
    # This happens when "FWMonitoringEnv.from_os()" is called.
    preempt : bool
    split_routes : bool
    tag_key : str
    vpc_summary_route : str
    route_table_id : str
    fw_trust_enis : ()
    fw_mgmt_ips : ()
    api_key_name : str
    region : str

在代码的其他地方我简单地执行了这个:

env = FWMonitoringEnv.from_os()

虽然我正在做这件事,但我还想解决另一个代码卫生问题。有没有办法让我的 Environment class 强制派生的 class 成为 @dataclass?例如,可以使用 Python 3.7 类型提示来完成吗?

只需使用 getattr

getattr(cls, "__annotations__").items() 

以及整个源代码块

import os
from abc import ABC

class Environment(ABC):

    @classmethod
    def from_os(cls):
        convert = {
            bool: lambda d: d == "1",
            (): lambda d: tuple(d.split(',')),
            str: lambda d: d
        }
        values = [ 
            convert[value](os.environ[key]) 
            for key,value in getattr(cls, "__annotations__").items() 
            ]
        return cls(*values)

This will make the linter happy and your code working :) Also it is a good idea to add an assertion check that the cls class has the required magic field __annotations__.

您可以使用 https://docs.python.org/3/library/inspect.html#inspect.signature 方法获取所有 class 注释。

例子

pprint(inspect.signature(cls).parameters)

mappingproxy(OrderedDict([('preempt', <Parameter "preempt: bool">),
                          ('split_routes', <Parameter "split_routes: bool">),
                          ('tag_key', <Parameter "tag_key: str">),
                          ('vpc_summary_route',
                           <Parameter "vpc_summary_route: str">),
                          ('route_table_id', <Parameter "route_table_id: str">),
                          ('fw_trust_enis', <Parameter "fw_trust_enis: ()">),
                          ('fw_mgmt_ips', <Parameter "fw_mgmt_ips: ()">),
                          ('api_key_name', <Parameter "api_key_name: str">),
                          ('region', <Parameter "region: str">)]))

当合并到您的代码中时,它看起来如下:

    def from_os(cls):
        convert = {
            bool: lambda d: d == '1',
            (): lambda d: tuple(d.split(',')),
            str: lambda d: d
        }
        values = [ 
            convert[val.annotation](os.environ[val.name]) 
            for val in signature(cls).parameters.values()
            ]
        return cls(*values)

我相信这是获得它的更好方法 "true" :-)