Python-源码-语法学习

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-代码示例

psutilprocess 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 里推荐的"配置入口"。

两句话概括:

  1. BaseSettings 把"环境变量 / .env 文件 / 构造函数参数"等全部当成数据源,自动做类型转换与校验,最终生成一个类型安全的配置对象。
  2. SettingsConfigDict 取代了旧版的嵌套 class Config:,以字典式声明的方式一次性指定数据源、前缀、编码、大小写是否敏感等规则,写法更直观,IDE 补全更友好。

下面给出 3 个最常见的实战例子,复制即可运行。


示例 1:最简 .env 读取

目录结构

复制代码
.  
├── config.py  
└── .env  

.env

复制代码
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb  
DEBUG=false  

config.py

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_PORTMYAPP_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.getenvcast 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

⚠️ 注意事项

  1. 仅检查方法/属性是否存在,不检查签名是否完全匹配。
  2. 不会检查类型注解是否一致,只检查名字是否存在。
  3. 对性能有轻微影响,因为运行时要用反射检查属性。
  4. 不能用于检查泛型协议 (如 Protocol[T])的具体类型参数。

✅ 总结一句话

@runtime_checkable 让你用 isinstance() 在运行时判断一个对象是否"像"某个协议,而不需要继承它。


相关推荐
_一路向北_7 小时前
爬虫框架:Feapder使用心得
爬虫·python
知识分享小能手7 小时前
CentOS Stream 9入门学习教程,从入门到精通,CentOS Stream 9 配置网络功能 —语法详解与实战案例(10)
网络·学习·centos
皇族崛起8 小时前
【3D标注】- Unreal Engine 5.7 与 Python 交互基础
python·3d·ue5
瑶光守护者8 小时前
【学习笔记】5G RedCap:智能回落5G NR驻留的接入策略
笔记·学习·5g
你想知道什么?8 小时前
Python基础篇(上) 学习笔记
笔记·python·学习
曼巴UE58 小时前
UE5 C++ 动态多播
java·开发语言
SHOJYS8 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
weixin_409383128 小时前
简单四方向a*学习记录4 能初步实现从角色到目的地寻路
学习·a星
steins_甲乙8 小时前
C++并发编程(3)——资源竞争下的安全栈
开发语言·c++·安全
Swizard9 小时前
速度与激情:Android Python + CameraX 零拷贝实时推理指南
android·python·ai·移动开发