余晖落尽暮晚霞,黄昏迟暮远山寻
本站
当前位置:网站首页 > 编程知识 > 正文

golang如何将http请求流转到gin(golang post请求)

xiyangw 2022-11-24 16:40 109 浏览 0 评论

gin是作为golang web开发中被广泛使用到的框架,了解其内部的实现有助于我们更好地理解gin的设计思想。

这篇文章主要探讨两个问题。

  • http请求如何流转到gin
  • gin为什么比golang的http路由寻找更快

开始之前我们先来看看分别用golang原生的http包实现一个http服务和使用gin实现的代码,先看看原生http包实现的http服务

package main


import (
  "net/http"
)


func main() {
  http.HandleFunc("/ping", func(writer http.ResponseWriter, request *http.Request) {
    writer.Write([]byte(`{"message":"ok"}`))
  })
  http.ListenAndServe(":9090", nil)
}

这段代码做了两件事情,注册路由、启动服务监听9090端口。接下来我们对这段代码进一步分析,在第8行的地方是将路由/ping和对应的处理函数注册到http服务中,我们进入http.HandleFunc()函数看看该函数做了什么事情。

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  DefaultServeMux.HandleFunc(pattern, handler)
}

将路由和处理函数注册到了DefaultServeMux中,所以我们先看看DefaultServeMux的结构是什么。

type ServeMux struct {
  mu    sync.RWMutex
  m     map[string]muxEntry
  es    []muxEntry // slice of entries sorted from longest to shortest.
  hosts bool       // whether any patterns contain hostnames
}


type muxEntry struct {
  h       Handler
  pattern string
}


// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }


// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux


var defaultServeMux ServeMux

第17行代码就是刚刚用来注册http路由的服务,通过第19行代码知道了他是一个ServeMux类型。知道了DefaultServeMux的类型我们接着看具体的实现代码。

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  if handler == nil {
    panic("http: nil handler")
  }
  mux.Handle(pattern, HandlerFunc(handler))
}


// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
  mux.mu.Lock()
  defer mux.mu.Unlock()


  if pattern == "" {
    panic("http: invalid pattern")
  }
  if handler == nil {
    panic("http: nil handler")
  }
  if _, exist := mux.m[pattern]; exist {
    panic("http: multiple registrations for " + pattern)
  }


  if mux.m == nil {
    mux.m = make(map[string]muxEntry)
  }
  e := muxEntry{h: handler, pattern: pattern}
  mux.m[pattern] = e
  if pattern[len(pattern)-1] == '/' {
    mux.es = appendSorted(mux.es, e)
  }


  if pattern[0] != '/' {
    mux.hosts = true
  }
}

主要的代码就是第29行,这里将路由和处理函数保存在了ServeMux的m中,通过前面的代码我们知道m是一个map,到这里路由注册的过程就分析完了。接下来我们来看看 http.ListenAndServe()做了什么事情。

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// If srv.Addr is blank, ":http" is used.
//
// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
  if srv.shuttingDown() {
    return ErrServerClosed
  }
  addr := srv.Addr
  if addr == "" {
    addr = ":http"
  }
  ln, err := net.Listen("tcp", addr)
  if err != nil {
    return err
  }
  return srv.Serve(ln)
}

第22行就是真正开始启动http服务,并接受请求的函数。第17行创建了主动套接字并监听套接字,接着我们进入Serve()函数。

func (srv *Server) Serve(l net.Listener) error {
  if fn := testHookServerServe; fn != nil {
    fn(srv, l) // call hook with unwrapped listener
  }


  origListener := l
  l = &onceCloseListener{Listener: l}
  defer l.Close()


  if err := srv.setupHTTP2_Serve(); err != nil {
    return err
  }


  if !srv.trackListener(&l, true) {
    return ErrServerClosed
  }
  defer srv.trackListener(&l, false)


  baseCtx := context.Background()
  if srv.BaseContext != nil {
    baseCtx = srv.BaseContext(origListener)
    if baseCtx == nil {
      panic("BaseContext returned a nil context")
    }
  }


  var tempDelay time.Duration // how long to sleep on accept failure


  ctx := context.WithValue(baseCtx, ServerContextKey, srv)
  for {
    rw, err := l.Accept()
    if err != nil {
      select {
      case <-srv.getDoneChan():
        return ErrServerClosed
      default:
      }
      if ne, ok := err.(net.Error); ok && ne.Temporary() {
        if tempDelay == 0 {
          tempDelay = 5 * time.Millisecond
        } else {
          tempDelay *= 2
        }
        if max := 1 * time.Second; tempDelay > max {
          tempDelay = max
        }
        srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
        time.Sleep(tempDelay)
        continue
      }
      return err
    }
    connCtx := ctx
    if cc := srv.ConnContext; cc != nil {
      connCtx = cc(connCtx, rw)
      if connCtx == nil {
        panic("ConnContext returned nil")
      }
    }
    tempDelay = 0
    c := srv.newConn(rw)
    c.setState(c.rwc, StateNew, runHooks) // before Serve can return
    go c.serve(connCtx)
  }
}

比较关键的几行代码是第31行和第61行,他们做的事情分别是接收到请求并解析请求数据,使用新的goroutines处理该请求。接着我们需要看看golang具体是如何处理接收到的请求

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
  c.remoteAddr = c.rwc.RemoteAddr().String()
  ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
  defer func() {
    if err := recover(); err != nil && err != ErrAbortHandler {
      const size = 64 << 10
      buf := make([]byte, size)
      buf = buf[:runtime.Stack(buf, false)]
      c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
    }
    if !c.hijacked() {
      c.close()
      c.setState(c.rwc, StateClosed, runHooks)
    }
  }()


  if tlsConn, ok := c.rwc.(*tls.Conn); ok {
    if d := c.server.ReadTimeout; d > 0 {
      c.rwc.SetReadDeadline(time.Now().Add(d))
    }
    if d := c.server.WriteTimeout; d > 0 {
      c.rwc.SetWriteDeadline(time.Now().Add(d))
    }
    if err := tlsConn.HandshakeContext(ctx); err != nil {
      // If the handshake failed due to the client not speaking
      // TLS, assume they're speaking plaintext HTTP and write a
      // 400 response on the TLS conn's underlying net.Conn.
      if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
        io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
        re.Conn.Close()
        return
      }
      c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
      return
    }
    c.tlsState = new(tls.ConnectionState)
    *c.tlsState = tlsConn.ConnectionState()
    if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
      if fn := c.server.TLSNextProto[proto]; fn != nil {
        h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
        // Mark freshly created HTTP/2 as active and prevent any server state hooks
        // from being run on these connections. This prevents closeIdleConns from
        // closing such connections. See issue https://golang.org/issue/39776.
        c.setState(c.rwc, StateActive, skipHooks)
        fn(c.server, tlsConn, h)
      }
      return
    }
  }


  // HTTP/1.x from here on.


  ctx, cancelCtx := context.WithCancel(ctx)
  c.cancelCtx = cancelCtx
  defer cancelCtx()


  c.r = &connReader{conn: c}
  c.bufr = newBufioReader(c.r)
  c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)


  for {
    w, err := c.readRequest(ctx)
    if c.r.remain != c.server.initialReadLimitSize() {
      // If we read any bytes off the wire, we're active.
      c.setState(c.rwc, StateActive, runHooks)
    }
    if err != nil {
      const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"


      switch {
      case err == errTooLarge:
        // Their HTTP client may or may not be
        // able to read this if we're
        // responding to them and hanging up
        // while they're still writing their
        // request. Undefined behavior.
        const publicErr = "431 Request Header Fields Too Large"
        fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
        c.closeWriteAndWait()
        return


      case isUnsupportedTEError(err):
        // Respond as per RFC 7230 Section 3.3.1 which says,
        //      A server that receives a request message with a
        //      transfer coding it does not understand SHOULD
        //      respond with 501 (Unimplemented).
        code := StatusNotImplemented


        // We purposefully aren't echoing back the transfer-encoding's value,
        // so as to mitigate the risk of cross side scripting by an attacker.
        fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
        return


      case isCommonNetReadError(err):
        return // don't reply


      default:
        if v, ok := err.(statusError); ok {
          fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
          return
        }
        publicErr := "400 Bad Request"
        fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
        return
      }
    }


    // Expect 100 Continue support
    req := w.req
    if req.expectsContinue() {
      if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
        // Wrap the Body reader with one that replies on the connection
        req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
        w.canWriteContinue.setTrue()
      }
    } else if req.Header.get("Expect") != "" {
      w.sendExpectationFailed()
      return
    }


    c.curReq.Store(w)


    if requestBodyRemains(req.Body) {
      registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
    } else {
      w.conn.r.startBackgroundRead()
    }


    // HTTP cannot have multiple simultaneous active requests.[*]
    // Until the server replies to this request, it can't read another,
    // so we might as well run the handler in this goroutine.
    // [*] Not strictly true: HTTP pipelining. We could let them all process
    // in parallel even if their responses need to be serialized.
    // But we're not going to implement HTTP pipelining because it
    // was never deployed in the wild and the answer is HTTP/2.
    serverHandler{c.server}.ServeHTTP(w, w.req)
    w.cancelCtx()
    if c.hijacked() {
      return
    }
    w.finishRequest()
    if !w.shouldReuseConnection() {
      if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
        c.closeWriteAndWait()
      }
      return
    }
    c.setState(c.rwc, StateIdle, runHooks)
    c.curReq.Store((*response)(nil))


    if !w.conn.server.doKeepAlives() {
      // We're in shutdown mode. We might've replied
      // to the user without "Connection: close" and
      // they might think they can send another
      // request, but such is life with HTTP/1.1.
      return
    }


    if d := c.server.idleTimeout(); d != 0 {
      c.rwc.SetReadDeadline(time.Now().Add(d))
      if _, err := c.bufr.Peek(4); err != nil {
        return
      }
    }
    c.rwc.SetReadDeadline(time.Time{})
  }
}

关键的代码是第137行将需要返回的response和reques

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  handler := sh.srv.Handler
  if handler == nil {
    handler = DefaultServeMux
  }
  if req.RequestURI == "*" && req.Method == "OPTIONS" {
    handler = globalOptionsHandler{}
  }


  if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
    var allowQuerySemicolonsInUse int32
    req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
      atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
    }))
    defer func() {
      if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
        sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
      }
    }()
  }


  handler.ServeHTTP(rw, req)
}

在这里我们终于又和注册路由时候使用的DefaultServeMux见面了,因为在启动服务的时候handler传入的是nil,所以这里默认的使用DefaultServeMux,然而此时的DefaultServeMux已经包含了注册的路由。接下来我们来看看DefaultServeMux的ServeHTTP()是如何实现的。

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
  if r.RequestURI == "*" {
    if r.ProtoAtLeast(1, 1) {
      w.Header().Set("Connection", "close")
    }
    w.WriteHeader(StatusBadRequest)
    return
  }
  h, _ := mux.Handler(r)
  h.ServeHTTP(w, r)
}

第11行就是通过通过请求中的路由再返回路由对应的处理函数

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {


  // CONNECT requests are not canonicalized.
  if r.Method == "CONNECT" {
    // If r.URL.Path is /tree and its handler is not registered,
    // the /tree -> /tree/ redirect applies to CONNECT requests
    // but the path canonicalization does not.
    if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
      return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
    }


    return mux.handler(r.Host, r.URL.Path)
  }


  // All other requests have any port stripped and path cleaned
  // before passing to mux.handler.
  host := stripHostPort(r.Host)
  path := cleanPath(r.URL.Path)


  // If the given path is /tree and its handler is not registered,
  // redirect for /tree/.
  if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
    return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
  }


  if path != r.URL.Path {
    _, pattern = mux.handler(host, path)
    u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
    return RedirectHandler(u.String(), StatusMovedPermanently), pattern
  }


  return mux.handler(host, r.URL.Path)
}

第32行然后接着往下走

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
  mux.mu.RLock()
  defer mux.mu.RUnlock()


  // Host-specific pattern takes precedence over generic ones
  if mux.hosts {
    h, pattern = mux.match(host + path)
  }
  if h == nil {
    h, pattern = mux.match(path)
  }
  if h == nil {
    h, pattern = NotFoundHandler(), ""
  }
  return
}

第7行

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
  // Check for exact match first.
  v, ok := mux.m[path]
  if ok {
    return v.h, v.pattern
  }


  // Check for longest valid match.  mux.es contains all patterns
  // that end in / sorted from longest to shortest.
  for _, e := range mux.es {
    if strings.HasPrefix(path, e.pattern) {
      return e.h, e.pattern
    }
  }
  return nil, ""
}

第3~5行,如请求的路由有对应的处理函数则返回对应的处理函数。得到了对应的处理函数,然后调用处理函数实现的ServeHTTP()的逻辑


// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)


// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
  f(w, r)
}

通过刚开始注册路由的时候我们传入的处理函数是HandlerFunc类型,而且对应的ServeHTTP()逻辑是运行处理函数,所以到这里逻辑就走到了我们的业务逻辑了,这就是使用golang原生http包实现的http服务具体的实现过程。

接着我们来看看gin的http服务有什么不同,gin中匹配路由和处理函数的的数据结构是Radix Tree,这是前缀树的优化方案

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
  assert1(path[0] == '/', "path must begin with '/'")
  assert1(method != "", "HTTP method can not be empty")
  assert1(len(handlers) > 0, "there must be at least one handler")


  debugPrintRoute(method, path, handlers)


  root := engine.trees.get(method)
  if root == nil {
    root = new(node)
    root.fullPath = "/"
    engine.trees = append(engine.trees, methodTree{method: method, root: root})
  }
  root.addRoute(path, handlers)


  // Update maxParams
  if paramsCount := countParams(path); paramsCount > engine.maxParams {
    engine.maxParams = paramsCount
  }


  if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
    engine.maxSections = sectionsCount
  }
}

第14行向该树添加节点,gin中每一个http请求方法都单独维护了一棵Radix Tree。接着我们看Run()函数做了什么事情

func (engine *Engine) Run(addr ...string) (err error) {
  defer func() { debugPrintError(err) }()


  if engine.isUnsafeTrustedProxies() {
    debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
      "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
  }


  address := resolveAddress(addr)
  debugPrint("Listening and serving HTTP on %s\n", address)
  err = http.ListenAndServe(address, engine)
  return
}

第11行将我们将建的gin实例作为handler传入ListenAndServe,之后的逻辑就是http包原生的逻辑,唯一不同的是最后调用的ServeHTTP是gin的实现而不是DefaultServeMux的实现接下来我们看看gin的ServeHTTP实现

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  c := engine.pool.Get().(*Context)
  c.writermem.reset(w)
  c.Request = req
  c.reset()


  engine.handleHTTPRequest(c)


  engine.pool.Put(c)
}

gin将请求包装成Context然后调用handleHTTPRequestRadix Tree找到路由对应的处理函数,并调用该函函数。

func (engine *Engine) handleHTTPRequest(c *Context) {
  httpMethod := c.Request.Method
  rPath := c.Request.URL.Path
  unescape := false
  if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
    rPath = c.Request.URL.RawPath
    unescape = engine.UnescapePathValues
  }


  if engine.RemoveExtraSlash {
    rPath = cleanPath(rPath)
  }


  // Find root of the tree for the given HTTP method
  t := engine.trees
  for i, tl := 0, len(t); i < tl; i++ {
    if t[i].method != httpMethod {
      continue
    }
    root := t[i].root
    // Find route in tree
    value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
    if value.params != nil {
      c.Params = *value.params
    }
    if value.handlers != nil {
      c.handlers = value.handlers
      c.fullPath = value.fullPath
      c.Next()
      c.writermem.WriteHeaderNow()
      return
    }
    if httpMethod != "CONNECT" && rPath != "/" {
      if value.tsr && engine.RedirectTrailingSlash {
        redirectTrailingSlash(c)
        return
      }
      if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
        return
      }
    }
    break
  }


  if engine.HandleMethodNotAllowed {
    for _, tree := range engine.trees {
      if tree.method == httpMethod {
        continue
      }
      if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
        c.handlers = engine.allNoMethod
        serveError(c, http.StatusMethodNotAllowed, default405Body)
        return
      }
    }
  }
  c.handlers = engine.allNoRoute
  serveError(c, http.StatusNotFound, default404Body)
}

第22~31行获取处理函数,并执行中间件和处理函数。

到这里我们就一起知道了http请求是如何从golang流转到gin的,只要我们自己定义的结构体实现了ServeHTTP函数并在启动服务使用我们自己实现的handler类型的结构体,那么最后就会流转的自定义的http handler。

通过分析我们知道原生的DefaultServeMux路由和处理函数对应关系使用的是map,而gin使用的是Radix Tree,所以gin比原生http快的原因就是这两种数据结构的的性能差别,map在最糟糕的条件下时间复杂度会变成O(n)也就是所有的key hash只有相同,最后变成链表。而且由于map的性质,所有的key不是很可能不是连续的,所有可能造成空间浪费。

关于gin的学习今天就到这里,有什么错误的地方希望指正。

相关推荐

高效删除文件名中的指定内容,祛除烦恼

如何快速批量删除文件名中的指定字符?在电脑整理文件时,我们常常需要进行批量重命名操作。如果文件名中含有不必要或重复的字符,这将影响文件的识别和查找。因此,删除这些文字或字符是非常必要且有效的。本文将分...

你的手机是不是经常提示存储空间不足?Python帮你清理重复文件

  最近我的手机经常提示存储空间不足,主要是微信和QQ群里接收的文件太多了,平时也没怎么整理。我把这些文件从手机里拷出来,打算整理一下,把该删的文件都删掉,把要备份的文件分门别类存到电脑或网盘上。我突...

从零开始打造云端AI管理调度平台(四)如何设计主页_1

最近闲来无事,想着把自己工作正在做的一个项目做一个简单的分享与实战教程,该项目不困难但是由于涉及要素过多所以比较复杂。所以这里分享出来也是为了帮助新手小白能在实战当中快速了解python知识。主要内容...

「万能Python」-17-标准库OS_python2.7标准库

Python的库是指预先编写好的代码集合,可以用来处理特定的任务或实现特定的功能。Python的标准库提供了许多常用的标准库,无需安装引入使用。Python3的标准库提供了许多常用的功能,包括...

十多行代码生成原帖和临写对照图_几十行代码可以申请软著吗

介绍这个小工具把原帖和临写分解成多列对照的形式。输入原帖图片,和输入临写图片,生成对照图片。使用说明运行compare.py文件,输入参数为:原帖图片文件名,临写图片文件名,列数。python3co...

Python合集之目录操作(四)_python之文件操作大全

1.删除目录删除目录可以通过os模块提供的rmdir()函数实现。通过rmdir()函数删除目录时,只有当要删除的目录为空时才起作用。os.rmdir(path)path为要删除的目录,可以使用绝对路...

excel办公自动化,自动合并excel表格

平时时收到不同人反馈的excel表格,需要将其合并在一个excel中,每个excel打开复制粘贴会很慢,如下脚本可一键合并所有的exceldefauto_merge():try:print('...

批量打开一个文件夹下面所有的excel表格

如果现在有一个文件夹,名称叫做“练习表格”,路径名是'C:\Users\123\Desktop\练习表格',现在的要求是打开这个文件下面所有的excel表格,后缀名字为“xlsx”,我...

500行代码,教你用python写个微信飞机大战

这几天在重温微信小游戏的飞机大战,玩着玩着就在思考人生了,这飞机大战怎么就可以做的那么好,操作简单,简单上手。帮助蹲厕族、YP族、饭圈女孩在无聊之余可以有一样东西让他们振作起来!让他们的左手/右手...

python 日志写入_python日志文件写入

1.第一步,新建日志文件路径,如下图:2.日志模块封装,代码如下:importlogging,oslogs_path=os.path.join(os.path.dirname(__file...

Python os.path模块使用指南:轻松处理文件路径

前言在Python编程中,文件和目录的操作是非常重要的一部分。为了方便用户进行文件和目录的操作,Python标准库提供了os模块。其中,os.path子模块提供了一些处理文件路径的函数和方法。本文主要...

Python中获取当前路径之pathilb和os的区别

20230114星期六:1,直接在py脚本中,执行,没有区别;2,打包成exe文件以后,在本机执行,有区别,这时,不能使用os.path.dirname(__file__),只能使用pathlib.P...

Python(办公自动化编程系列)学习笔记1

1、获得当前程序工作目录importos#os.getcwd()函数可以取得当前工作路径的字符串print(os.getcwd())运行结果2、获得程序文件夹相对路径#相对路径都用/表示#../...

文件路径名Pathnames的操作_路径和文件名是什么意思

1.现象问题使用路径名来获取文件名,目录名,绝对路径等等2.原因分析无3.问题解决使用os.path模块中的函数来操作路径名importos.path#查看标准化的绝对路径print...

关于《Python入门:os常用函数》中复制文件(夹)函数从简到繁

基本考虑练习os模块及file读写操作:将给定的源路径的文件(夹)复制到目标路径简单实现#文件到文件的复制path_src='E:\src.txt'path_tar='D...

取消回复欢迎 发表评论: