Python-源码-语法学习

1-Literal字面量
类似Java的枚举,如果一个常量没有定义,返回的时候会报错->可以用来【请求参数】或者【返回参数】的校验
1-源码部分
类比Java的构造参数,支持传入两个参数(text和type)
- class类的定义
python
class ContentPartTextParam(BaseModel):
text: str
type: Literal['text'] = 'text'
def __str__(self) -> str:
return f'文本: {_truncate(self.text)}'
def __repr__(self) -> str:
return f'ContentPartTextParam(text={_truncate(self.text)})'
class ContentPartImageParam(BaseModel):
image_url: ImageURL
type: Literal['image_url'] = 'image_url'
def __str__(self) -> str:
return str(self.image_url)
def __repr__(self) -> str:
return f'ContentPartImageParam(image_url={repr(self.image_url)})'
- class类的使用
python
"""测试带图像内容的缓存功能。"""
user_with_image = UserMessage(
content=[
ContentPartTextParam(text='这里有一张图片:', type='text'),
ContentPartImageParam(image_url=ImageURL(url='https://example.com/image.jpg'), type='image_url'),
],
cache=True,
)
messages, _ = AnthropicMessageSerializer.serialize_messages([user_with_image])
assert len(messages) == 1
assert isinstance(messages[0]['content'], list)
assert len(messages[0]['content']) == 2
1-使用示例
python
from typing import Literal, get_args
Mode = Literal['r', 'rb', 'w', 'wb']
def validate_mode(mode: str) -> Mode:
if mode not in get_args(Mode):
raise ValueError(f"Invalid mode: {mode}")
return mode # 返回的是 Mode 类型
def validate_mode_success():
# r在定义的Literal中存在
validate_mode('r')
def validate_mode_fail():
# x在定义的Literal中不存在
try:
validate_mode('x') # ❌ ValueError
except ValueError as e:
print(f"捕获到ValueError异常: {e}")
# 在这里可以处理异常,比如记录日志、提供默认值或其他恢复操作
except Exception as e:
print(f"捕获到其他异常: {e}")
# 处理其他可能的异常
if __name__ == "__main__":
validate_mode_success()
validate_mode_fail()
2-repr方法编写
这个不是我再装鬼,这个是BrowserUse源码中用于代码调试的技巧(本质上类似于Java的toString方法,虽然是个不错的楔子,但是不建议你在这里哐哐乱用;知道是什么鬼,然后绕道走就OK了)
1-源码部分
代码中除了编写str方法,还编写了一个repr方法;在当前的方法中是把当前的类名打印出来了;其实就是另一个版本的toString方法,让你在调试的过程中可以看到更多的信息(知道就OK了,没有必要玩出花)
python
class ContentPartTextParam(BaseModel):
text: str
type: Literal['text'] = 'text'
def __str__(self) -> str:
return f'文本: {_truncate(self.text)}'
def __repr__(self) -> str:
return f'ContentPartTextParam(text={_truncate(self.text)})'
class ContentPartImageParam(BaseModel):
image_url: ImageURL
type: Literal['image_url'] = 'image_url'
def __str__(self) -> str:
return str(self.image_url)
def __repr__(self) -> str:
return f'ContentPartImageParam(image_url={repr(self.image_url)})'
2-测试用例
Java中真实的使用场景是把一个对象转化为json序列化String,方便后续再转化为Bean对象;所以这里不做深入研究
python
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __repr__(self) -> str:
"""返回对象的字符串表示形式,可用于重新创建对象
Return the string representation of the object, which can be used to recreate the object"""
return f"Person({self.name!r}, {self.age!r})"
if __name__ == "__main__":
p = Person("Alice", 25)
r = repr(p)
print(r) # Person('Alice', 25)
print("--------")
# 为了使 eval 正确工作,我们需要在当前命名空间中导入 datetime
print(eval(r)) # 反序列化回来
3-psutil系统管理
1-源码部分
config在读取env的时候,有一个方法,判断是不是运行在docker容器中,其中使用到psutil.Process(1).cmdline()
python
@cache
def is_running_in_docker() -> bool:
"""检测是否在Docker容器中运行,用于优化Chrome启动参数(如dev shm使用、GPU设置等)"""
try:
if Path('/.dockerenv').exists() or 'docker' in Path('/proc/1/cgroup').read_text().lower():
return True
except Exception:
pass
try:
# 如果初始进程(PID 1)看起来像uvicorn/python/uv/etc.,则我们处于Docker中
# 如果初始进程(PID 1)看起来像bash/systemd/init/etc.,则我们很可能不在Docker中
init_cmd = ' '.join(psutil.Process(1).cmdline())
if ('py' in init_cmd) or ('uv' in init_cmd) or ('app' in init_cmd):
return True
except Exception:
pass
try:
# 如果运行的进程少于10个,则几乎可以肯定是容器内
if len(psutil.pids()) < 10:
return True
except Exception:
pass
return False
2-代码示例
psutil(process and system utilities )是一个跨平台库,用于检索系统运行时信息 和管理系统进程 。它提供了大量实用功能,无需额外依赖,即可获取 CPU、内存、磁盘、网络、传感器等硬件信息,还能管理进程(如列出、终止、获取进程详情等)。
✅ 核心功能与代码示例
1. 获取 CPU 使用率(百分比)
python
import psutil
# 获取当前 CPU 使用率(间隔 1 秒)
cpu_percent = psutil.cpu_percent(interval=1)
print(f"CPU 使用率: {cpu_percent}%")
2. 获取内存使用情况
python
import psutil
# 获取虚拟内存信息
mem = psutil.virtual_memory()
print(f"总内存: {mem.total / (1024**3):.2f} GB")
print(f"已用内存: {mem.used / (1024**3):.2f} GB")
print(f"内存使用率: {mem.percent}%")
3. 获取磁盘使用情况
python
import psutil
# 获取根分区磁盘使用情况
disk = psutil.disk_usage('/')
print(f"总磁盘空间: {disk.total / (1024**3):.2f} GB")
print(f"已用磁盘空间: {disk.used / (1024**3):.2f} GB")
print(f"磁盘使用率: {disk.percent}%")
4. 获取网络接口信息
python
import psutil
# 获取所有网络接口的地址信息
net_if_addrs = psutil.net_if_addrs()
for interface, addrs in net_if_addrs.items():
print(f"接口: {interface}")
for addr in addrs:
print(f" {addr.family.name}: {addr.address}")
5. 列出所有运行中的进程 PID
python
import psutil
# 获取所有进程 PID
pids = psutil.pids()
print(f"当前运行的进程数: {len(pids)}")
print("前 10 个进程 PID:", pids[:10])
6. 获取某个进程的详细信息
python
import psutil
# 获取当前 Python 进程的详细信息
p = psutil.Process()
print(f"进程名: {p.name()}")
print(f"进程 PID: {p.pid}")
print(f"内存占用: {p.memory_info().rss / 1024**2:.2f} MB")
print(f"CPU 使用率: {p.cpu_percent(interval=1)}%")
7. 终止指定进程
python
import psutil
# 假设已知 PID 为 1234
try:
p = psutil.Process(1234)
p.terminate()
print(f"进程 {p.pid} 已终止")
except psutil.NoSuchProcess:
print("进程不存在")
✅ 总结:psutil 能做什么?
| 功能类别 | 示例用途 |
|---|---|
| CPU | 获取使用率、核心数、频率 |
| 内存 | 获取总/已用/可用内存 |
| 磁盘 | 获取分区、使用率、IO 统计 |
| 网络 | 获取网卡信息、连接数、流量 |
| 进程 | 列出、查询、终止、监控资源使用 |
| 传感器 | 获取温度、风扇转速(部分平台支持) |
4-pydantic_settings使用
以前我都是使用pydantic进行env环境变量的读取,BrowserUse源码中把2者结合起来进行使用
1-源码-新版本
在读取env环境变量的过程中,作者把新旧两种环境变量的读取方式都用到了
python
from pydantic_settings import BaseSettings, SettingsConfigDict
class FlatEnvConfig(BaseSettings):
"""所有环境变量以扁平命名空间形式呈现。"""
model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', case_sensitive=True, extra='allow')
# 日志与遥测
BROWSER_USE_LOGGING_LEVEL: str = Field(default='info')
CDP_LOGGING_LEVEL: str = Field(default='WARNING')
BROWSER_USE_DEBUG_LOG_FILE: str | None = Field(default=None)
BROWSER_USE_INFO_LOG_FILE: str | None = Field(default=None)
ANONYMIZED_TELEMETRY: bool = Field(default=True)
BROWSER_USE_CLOUD_SYNC: bool | None = Field(default=None)
BROWSER_USE_CLOUD_API_URL: str = Field(default='https://api.browser-use.com')
BROWSER_USE_CLOUD_UI_URL: str = Field(default='')
# 路径配置
XDG_CACHE_HOME: str = Field(default='~/.cache')
XDG_CONFIG_HOME: str = Field(default='~/.config')
BROWSER_USE_CONFIG_DIR: str | None = Field(default=None)
# LLM API密钥
OPENAI_API_KEY: str = Field(default='')
ANTHROPIC_API_KEY: str = Field(default='')
GOOGLE_API_KEY: str = Field(default='')
DEEPSEEK_API_KEY: str = Field(default='')
GROK_API_KEY: str = Field(default='')
NOVITA_API_KEY: str = Field(default='')
AZURE_OPENAI_ENDPOINT: str = Field(default='')
AZURE_OPENAI_KEY: str = Field(default='')
SKIP_LLM_API_KEY_VERIFICATION: bool = Field(default=False)
DEFAULT_LLM: str = Field(default='')
# 运行时提示
IN_DOCKER: bool | None = Field(default=None)
IS_IN_EVALS: bool = Field(default=False)
WIN_FONT_DIR: str = Field(default='C:\\Windows\\Fonts')
BROWSER_USE_VERSION_CHECK: bool = Field(default=True)
# MCP特定环境变量
BROWSER_USE_CONFIG_PATH: str | None = Field(default=None)
BROWSER_USE_HEADLESS: bool | None = Field(default=None)
BROWSER_USE_ALLOWED_DOMAINS: str | None = Field(default=None)
BROWSER_USE_LLM_MODEL: str | None = Field(default=None)
# 代理环境变量
BROWSER_USE_PROXY_URL: str | None = Field(default=None)
BROWSER_USE_NO_PROXY: str | None = Field(default=None)
BROWSER_USE_PROXY_USERNAME: str | None = Field(default=None)
BROWSER_USE_PROXY_PASSWORD: str | None = Field(default=None)
2-源码-旧版本
python
class OldConfig:
"""原始的惰性加载配置类,用于环境变量。"""
# 目录创建跟踪缓存
_dirs_created = False
@property
def BROWSER_USE_LOGGING_LEVEL(self) -> str:
return os.getenv('BROWSER_USE_LOGGING_LEVEL', 'info').lower()
@property
def ANONYMIZED_TELEMETRY(self) -> bool:
return os.getenv('ANONYMIZED_TELEMETRY', 'true').lower()[:1] in 'ty1'
@property
def BROWSER_USE_CLOUD_SYNC(self) -> bool:
return os.getenv('BROWSER_USE_CLOUD_SYNC', str(self.ANONYMIZED_TELEMETRY)).lower()[:1] in 'ty1'
@property
def BROWSER_USE_CLOUD_API_URL(self) -> str:
url = os.getenv('BROWSER_USE_CLOUD_API_URL', 'https://api.browser-use.com')
assert '://' in url, 'BROWSER_USE_CLOUD_API_URL 必须是有效URL'
return url
@property
def BROWSER_USE_CLOUD_UI_URL(self) -> str:
url = os.getenv('BROWSER_USE_CLOUD_UI_URL', '')
# 允许空字符串作为默认值,仅在设置时验证
if url and '://' not in url:
raise AssertionError('BROWSER_USE_CLOUD_UI_URL 必须是有效URL(如果已设置)')
return url
# 路径配置
@property
def XDG_CACHE_HOME(self) -> Path:
return Path(os.getenv('XDG_CACHE_HOME', '~/.cache')).expanduser().resolve()
@property
def XDG_CONFIG_HOME(self) -> Path:
return Path(os.getenv('XDG_CONFIG_HOME', '~/.config')).expanduser().resolve()
@property
def BROWSER_USE_CONFIG_DIR(self) -> Path:
path = Path(os.getenv('BROWSER_USE_CONFIG_DIR', str(self.XDG_CONFIG_HOME / 'browseruse'))).expanduser().resolve()
self._ensure_dirs()
return path
@property
def BROWSER_USE_CONFIG_FILE(self) -> Path:
return self.BROWSER_USE_CONFIG_DIR / 'config.json'
@property
def BROWSER_USE_PROFILES_DIR(self) -> Path:
path = self.BROWSER_USE_CONFIG_DIR / 'profiles'
self._ensure_dirs()
return path
@property
def BROWSER_USE_DEFAULT_USER_DATA_DIR(self) -> Path:
return self.BROWSER_USE_PROFILES_DIR / 'default'
@property
def BROWSER_USE_EXTENSIONS_DIR(self) -> Path:
path = self.BROWSER_USE_CONFIG_DIR / 'extensions'
self._ensure_dirs()
return path
def _ensure_dirs(self) -> None:
"""如果目录不存在则创建(仅一次)"""
if not self._dirs_created:
config_dir = (
Path(os.getenv('BROWSER_USE_CONFIG_DIR', str(self.XDG_CONFIG_HOME / 'browseruse'))).expanduser().resolve()
)
config_dir.mkdir(parents=True, exist_ok=True)
(config_dir / 'profiles').mkdir(parents=True, exist_ok=True)
(config_dir / 'extensions').mkdir(parents=True, exist_ok=True)
self._dirs_created = True
# LLM API密钥配置
@property
def OPENAI_API_KEY(self) -> str:
return os.getenv('OPENAI_API_KEY', '')
@property
def ANTHROPIC_API_KEY(self) -> str:
return os.getenv('ANTHROPIC_API_KEY', '')
@property
def GOOGLE_API_KEY(self) -> str:
return os.getenv('GOOGLE_API_KEY', '')
@property
def DEEPSEEK_API_KEY(self) -> str:
return os.getenv('DEEPSEEK_API_KEY', '')
@property
def GROK_API_KEY(self) -> str:
return os.getenv('GROK_API_KEY', '')
@property
def NOVITA_API_KEY(self) -> str:
return os.getenv('NOVITA_API_KEY', '')
@property
def AZURE_OPENAI_ENDPOINT(self) -> str:
return os.getenv('AZURE_OPENAI_ENDPOINT', '')
@property
def AZURE_OPENAI_KEY(self) -> str:
return os.getenv('AZURE_OPENAI_KEY', '')
@property
def SKIP_LLM_API_KEY_VERIFICATION(self) -> bool:
return os.getenv('SKIP_LLM_API_KEY_VERIFICATION', 'false').lower()[:1] in 'ty1'
@property
def DEFAULT_LLM(self) -> str:
return os.getenv('DEFAULT_LLM', '')
# 运行时提示
@property
def IN_DOCKER(self) -> bool:
return os.getenv('IN_DOCKER', 'false').lower()[:1] in 'ty1' or is_running_in_docker()
@property
def IS_IN_EVALS(self) -> bool:
return os.getenv('IS_IN_EVALS', 'false').lower()[:1] in 'ty1'
@property
def BROWSER_USE_VERSION_CHECK(self) -> bool:
return os.getenv('BROWSER_USE_VERSION_CHECK', 'true').lower()[:1] in 'ty1'
@property
def WIN_FONT_DIR(self) -> str:
return os.getenv('WIN_FONT_DIR', 'C:\\Windows\\Fonts')
3-示例代码
from pydantic_settings import BaseSettings, SettingsConfigDict 是 Pydantic 官方在 v2 里推荐的"配置入口"。
两句话概括:
BaseSettings把"环境变量 / .env 文件 / 构造函数参数"等全部当成数据源,自动做类型转换与校验,最终生成一个类型安全的配置对象。SettingsConfigDict取代了旧版的嵌套class Config:,以字典式声明的方式一次性指定数据源、前缀、编码、大小写是否敏感等规则,写法更直观,IDE 补全更友好。
下面给出 3 个最常见的实战例子,复制即可运行。
示例 1:最简 .env 读取
目录结构
.
├── config.py
└── .env
.env
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
DEBUG=false
python
from pydantic_settings import BaseSettings, SettingsConfigDict
# 当Settings被new出来之后,已经按照SettingsConfigDict的约定把所有的属性都赋值进去了
class Settings(BaseSettings):
database_url: str
debug: bool = False # 给默认值
# 用 SettingsConfigDict 一句话告诉 BaseSettings 去哪里读
model_config = SettingsConfigDict(env_file='.env',
env_file_encoding='utf-8',
case_sensitive=False)
settings = Settings() # 实例化时立即加载 .env
print(settings.database_url) # postgresql://user:pass@localhost:5432/mydb
print(settings.debug) # False
运行 python config.py 即可看到值已被正确解析并转换成对应类型 。
示例 2:加前缀、支持热插拔环境变量
有时同一份代码在 docker / k8s / 本地会传不同前缀的环境变量,例如 MYAPP_PORT、MYAPP_HOST。
python
import os
from pydantic_settings import BaseSettings, SettingsConfigDict
# 当Settings被new出来之后,已经按照SettingsConfigDict的约定【指定ENV的前缀】把所有的属性都赋值进去了
class Settings(BaseSettings):
host: str = '0.0.0.0'
port: int = 8000
model_config = SettingsConfigDict(env_prefix='MYAPP_', # 只认 MYAPP_ 开头
case_sensitive=False)
# 模拟 docker run -e MYAPP_PORT=8080
os.environ['MYAPP_PORT'] = '8080'
print(Settings().port) # 8080 (环境变量覆盖默认值)
如果同时存在 MYAPP_HOST=1.2.3.4,也会被自动读入 。
示例 3:多环境(dev / prod)同一套代码
利用 env_prefix 动态切换,连文件名都不用改。
.env
DEV_DATABASE_URL=dev_db
PROD_DATABASE_URL=prod_db
python
import os
from pydantic_settings import BaseSettings, SettingsConfigDict
# 当Settings被new出来之后,已经按照SettingsConfigDict的约定【指定ENV的前缀】把所有的属性都赋值进去了
env = os.getenv('ENV', 'DEV') # 本地默认 DEV,CI 里可注入 PROD
prefix = f'{env}_'
class Settings(BaseSettings):
database_url: str
model_config = SettingsConfigDict(env_prefix=prefix,
env_file='.env',
extra='forbid') # 禁止额外字段
print(Settings().database_url) # 输出 dev_db 或 prod_db
通过 ENV=PROD python run.py 即可秒级切换成生产库配置 。
小结
BaseSettings负责"读 + 类型校验",SettingsConfigDict负责"告诉它怎么读"。- 不再需要手写
os.getenv、cast bool等样板代码;配置项即字段,类型即约束。 - 官方已在 v2 把"嵌套 Config 类"标记为旧风格,新代码请统一使用
model_config = SettingsConfigDict(...)。
5-@runtime_checkable
1-源码部分
以下的代码编写了一个BaseChatModel的Protocol协议;以后可以直接根据使用isinstance(PopLLm(), BaseChatModel)来判断这个类是不是实现了BaseChatModel中所有的方法-->目的是:只要LLM基类方法不变,就可以方便的切换到其他框架(以前是langchain,现在切换到openai了)
python
"""
我们已将所有代码从 langchain 切换到 openai.types.chat.chat_completion_message_param。
为了便于过渡,我们提供了以下支持:
"""
from typing import Any, Protocol, TypeVar, overload, runtime_checkable
from pydantic import BaseModel
from browser_use.llm.messages import BaseMessage
from browser_use.llm.views import ChatInvokeCompletion
T = TypeVar('T', bound=BaseModel)
@runtime_checkable
class BaseChatModel(Protocol):
_verified_api_keys: bool = False
model: str
@property
def provider(self) -> str: ...
@property
def name(self) -> str: ...
@property
def model_name(self) -> str:
# 兼容旧版接口
return self.model
@overload
async def ainvoke(self, messages: list[BaseMessage], output_format: None = None) -> ChatInvokeCompletion[str]: ...
@overload
async def ainvoke(self, messages: list[BaseMessage], output_format: type[T]) -> ChatInvokeCompletion[T]: ...
async def ainvoke(
self, messages: list[BaseMessage], output_format: type[T] | None = None
) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]: ...
@classmethod
def __get_pydantic_core_schema__(
cls,
source_type: type,
handler: Any,
) -> Any:
"""
允许此 Protocol 在 Pydantic 模型中使用 -> 对于类型安全的代理设置非常有用。
返回一个允许任何对象的模式(因为这是 Protocol)。
"""
from pydantic_core import core_schema
# 返回一个接受任意对象的模式,适用于 Protocol 类型
return core_schema.any_schema()
2-代码示例
@runtime_checkable 是 Python 的 typing 模块(Python 3.8+)中的一个装饰器,用于在运行时检查某个对象是否实现了某个协议(Protocol) 。它属于 静态类型系统(PEP 544) 的一部分,但允许你在运行时动态判断对象是否符合某个协议。
✅ 使用场景
当你定义了一个 Protocol,比如:
python
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
def draw(self) -> None:
...
你可以用 isinstance(obj, Drawable) 来检查 obj 是否实现了 draw 方法,而不需要继承自任何基类。
✅ 示例代码
python
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
def draw(self) -> None:
...
class Circle:
def draw(self) -> None:
print("Drawing a circle")
class Square:
pass # 没有 draw 方法
print(isinstance(Circle(), Drawable)) # True
print(isinstance(Square(), Drawable)) # False
⚠️ 注意事项
- 仅检查方法/属性是否存在,不检查签名是否完全匹配。
- 不会检查类型注解是否一致,只检查名字是否存在。
- 对性能有轻微影响,因为运行时要用反射检查属性。
- 不能用于检查泛型协议 (如
Protocol[T])的具体类型参数。
✅ 总结一句话
@runtime_checkable让你用isinstance()在运行时判断一个对象是否"像"某个协议,而不需要继承它。