You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

Go 到 Python 迁移指南

本文档详细说明了从Go项目迁移到Python项目的对应关系。

技术栈对照表

Go Python 说明
Gin FastAPI Web框架
Gorilla WebSocket FastAPI原生WebSocket WebSocket支持
database/sql + pq SQLAlchemy + psycopg2 数据库访问
encoding/json Pydantic + json JSON序列化
os.Getenv python-dotenv + pydantic-settings 环境变量
log logging 日志
time datetime 时间处理
sync.Mutex threading.Lock/asyncio.Lock 并发锁
context.Context 直接使用async/await 上下文

项目结构对照表

Go                          Python
├── adapter/                app/adapters/
│   ├── adapter.go          base.py
│   └── tushare/            
│       ├── adapter.go      tushare_adapter.py
│       └── client.go       (集成到adapter)
├── api/
│   ├── types.go            app/models/types.py
│   ├── router.go           app/api/routes.py
│   ├── admin_types.go      app/models/admin_types.py
│   └── admin_router.go     app/api/admin_routes.py
├── cmd/
│   ├── server/main.go      app/main.py
│   └── sync/main.go        scripts/sync_data.py
├── internal/
│   ├── handler/
│   │   ├── handler.go      (合并到routes)
│   │   └── admin.go        (合并到admin_routes)
│   ├── service/
│   │   ├── service.go      (拆分到各service)
│   │   ├── stock.go        app/services/stock_service.py
│   │   ├── futures.go      app/services/futures_service.py
│   │   ├── admin.go        app/services/admin_service.py
│   │   ├── config.go       app/services/config_service.py
│   │   ├── adapter.go      app/services/adapter_service.py
│   │   └── test.go         app/services/test_service.py
│   ├── repository/
│   │   ├── repository.go   (合并)
│   │   ├── stock.go        app/repositories/stock_repository.py
│   │   └── futures.go      app/repositories/futures_repository.py
│   ├── model/model.go      app/repositories/models.py
│   ├── websocket/server.go app/websocket/server.py
│   └── monitor/monitor.go  app/monitor/monitor.py
├── pkg/
│   ├── config/config.go    app/core/config.py
│   ├── logger/logger.go    app/core/logger.py
│   └── errors/errors.go    app/core/errors.py
└── config.json             config.json (相同)

类型系统对照表

基础类型

Go Python
type Frequency string class Frequency(str, Enum)
type AdjustType string class AdjustType(str, Enum)
type AssetClass string class AssetClass(str, Enum)
struct KLineItem class KLineItem(BaseModel)
struct KLineData class KLineData(BaseModel)
interface Handler 通过FastAPI依赖注入实现

类型转换示例

Go:

type KLineItem struct {
    Time   time.Time `json:"time"`
    Open   float64   `json:"open"`
    Volume int64     `json:"volume"`
}

Python:

class KLineItem(BaseModel):
    time: datetime = Field(..., description="时间戳")
    open: float = Field(..., description="开盘价")
    volume: int = Field(..., description="成交量")
    
    class Config:
        json_encoders = {
            datetime: lambda v: v.isoformat()
        }

接口路由对照表

股票接口

Go路由 Python路由 方法
stock.GET("/klines/:symbol", r.queryStockKLines) @router.get("/stock/klines/{symbol}") GET
stock.GET("/symbols", r.listStockSymbols) @router.get("/stock/symbols") GET
stock.POST("/klines/batch", r.batchQueryStockKLines) @router.post("/stock/klines/batch") POST
stock.GET("/trading-dates", r.getStockTradingDates) @router.get("/stock/trading-dates") GET

参数绑定对比

Go (Gin):

func (r *Router) queryStockKLines(c *gin.Context) {
    var req KLineQueryRequest
    if err := c.ShouldBindQuery(&req); err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{...})
        return
    }
    req.Symbol = c.Param("symbol")
    // ...
}

Python (FastAPI):

@router.get("/stock/klines/{symbol}")
def query_stock_klines(
    symbol: str,  # 路径参数
    start: str = Query(...),  # 查询参数
    end: str = Query(...),
    db: Session = Depends(get_db)  # 依赖注入
):
    service = StockService(db)
    req = KLineQueryRequest(symbol=symbol, start=start, end=end)
    data = service.query_klines(req)
    return Response(code=0, message="success", data=data)

数据库访问对照表

查询K线数据

Go:

query := fmt.Sprintf(`
    SELECT ts, open, high, low, close, volume, amount
    FROM %s
    WHERE symbol_id = $1 AND ts >= $2 AND ts <= $3
    ORDER BY ts ASC
`, tableName)
rows, err := r.db.QueryContext(ctx, query, symbol, start, end)

Python (SQLAlchemy):

query = self.db.query(kline_model).filter(
    kline_model.symbol_id == symbol,
    kline_model.ts >= start,
    kline_model.ts <= end
).order_by(kline_model.ts.asc())
results = query.all()

批量插入

Go:

query := fmt.Sprintf(`
    INSERT INTO %s (symbol_id, ts, open, ...)
    VALUES %s
    ON CONFLICT (symbol_id, ts) DO UPDATE SET...
`, tableName, strings.Join(valueStrs, ","))
_, err := r.db.ExecContext(ctx, query, args...)

Python (SQLAlchemy):

for item in items:
    existing = self.db.query(kline_model).filter(...).first()
    if existing:
        # 更新
        existing.open = item.open
        ...
    else:
        # 插入
        new_record = kline_model(...)
        self.db.add(new_record)
self.db.commit()

WebSocket对照表

Go (Gorilla)

type Hub struct {
    clients map[*Client]bool
    subscriptions map[string]map[*Client]bool
}

func (h *Hub) Run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
        // ...
        }
    }
}

Python (FastAPI)

class WebSocketManager:
    def __init__(self):
        self.clients: Dict[str, WSClient] = {}
        self.subscriptions: Dict[str, Set[str]] = {}
        self.lock = asyncio.Lock()
    
    async def connect(self, websocket: WebSocket, client_id: str):
        await websocket.accept()
        async with self.lock:
            self.clients[client_id] = WSClient(id=client_id, websocket=websocket)

配置管理对照表

Go

type Config struct {
    Server   ServerConfig   `json:"server"`
    Database DatabaseConfig `json:"database"`
}

func Load(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    // ...
    json.Unmarshal(data, &cfg)
    return &cfg, nil
}

Python

class Config(BaseModel):
    server: ServerConfig = Field(default_factory=ServerConfig)
    database: DatabaseConfig = Field(default_factory=DatabaseConfig)

def load_config(config_path: str = "./config.json") -> Config:
    with open(config_path, 'r') as f:
        data = json.load(f)
    return Config.model_validate(data)

启动方式对照表

Go

go run ./cmd/server/main.go

Python

# 方式1: 直接运行
python -m app.main

# 方式2: 使用uvicorn
uvicorn app.main:app --reload --port 8080

# 方式3: 生产环境
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker

数据同步工具对照表

Go

go run ./cmd/sync -type stocks
go run ./cmd/sync -type klines -symbol 000001.SZ -start 20240301 -end 20240307

Python

python scripts/sync_data.py --type stocks
python scripts/sync_data.py --type klines --symbol 000001.SZ --start 20240301 --end 20240307

依赖管理对照表

Go (go.mod)

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/gorilla/websocket v1.5.0
    github.com/lib/pq v1.10.9
)

Python (requirements.txt)

fastapi==0.115.0
uvicorn[standard]==0.32.0
sqlalchemy==2.0.36
psycopg2-binary==2.9.10

测试接口对照表

接口 Go调用 Python调用
健康检查 curl http://localhost:8080/v1/admin/health 相同
查询股票K线 curl "http://localhost:8080/v1/stock/klines/000001.SZ?start=20250301&end=20250307" -H "X-API-Key: key" 相同
批量查询 curl -X POST ... -d '{"symbols":["000001.SZ"],...}' 相同

所有API接口和响应格式与Go版本完全一致客户端无需任何修改即可切换到Python后端。