apr_file_open函数用法心得

本文详细介绍了如何使用apr_file_open函数来打开文件,包括其参数含义、使用场景及常见注意事项,帮助开发者更好地掌握这一实用工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

apr_status_t apr_file_open(apr_file_t ** newf,
  const char * fname,
  apr_int32_t flag,
  apr_fileperms_t perm,
  apr_pool_tpool 
 )  

Open the specified file.

Parameters:
newfThe opened file descriptor.
fnameThe full path to the file (using / on all systems)
flagOr'ed value of:
         APR_READ              open for reading
         APR_WRITE             open for writing
         APR_CREATE            create the file if not there
         APR_APPEND            file ptr is set to end prior to all writes
         APR_TRUNCATE          set length to zero if file exists
         APR_BINARY            not a text file (This flag is ignored on 
                               UNIX because it has no meaning)
         APR_BUFFERED          buffer the data.  Default is non-buffered
         APR_EXCL              return error if APR_CREATE and file exists
         APR_DELONCLOSE        delete the file after closing.
         APR_XTHREAD           Platform dependent tag to open the file
                               for use across multiple threads
         APR_SHARELOCK         Platform dependent support for higher
                               level locked read/write access to support
                               writes across process/machines
         APR_FILE_NOCLEANUP    Do not register a cleanup with the pool 
                               passed in on the pool argument (see below).
                               The apr_os_file_t handle in apr_file_t will not
                               be closed when the pool is destroyed.
         APR_SENDFILE_ENABLED  Open with appropriate platform semantics
                               for sendfile operations.  Advisory only,
                               apr_socket_sendfile does not check this flag.
 
permAccess permissions for file.
poolThe pool to use.
Remarks:
If perm is APR_OS_DEFAULT and the file is being created, appropriate default permissions will be used.
By default, the returned file descriptor will not be inherited by child processes created by apr_proc_create(). This can be changed using apr_file_inherit_set().
如果你是服务端,客户端请求发送文件,你写在指定路径下,此时在打开文件时需要注意,apr_file_open函数,第三个参数应该写为 APR_WRITE|APR_CREATE| APR_TRUNCATE,其中APR_TRUNCATE表示,如果文件已经存在,则先将文件清空,这样就不会发生以下这种情况:
先发个大的文件,再发送个小文件,此时新生成的文件不会全部覆盖以前的文件,这就是BUG,故将参数设置成像上述所说的一样!

当频繁调用该函数,及需要在Pool上开辟空间的函数,应该使用局部的 pool  !!!
import curses import requests from bs4 import BeautifulSoup from urllib.parse import urljoin import os # 文件路径 FAVORITES_FILE = 'favorites.txt' HISTORY_FILE = 'history.txt' # 加载数据 def load_list(filename): try: with open(filename, 'r', encoding='utf-8') as f: return [line.strip() for line in f if line.strip()] except FileNotFoundError: return [] # 保存数据 def save_list(filename, items): with open(filename, 'w', encoding='utf-8') as f: for item in items: f.write(item + '\n') # 获取网页纯文本 def fetch_page(url): try: res = requests.get(url, timeout=5) res.raise_for_status() soup = BeautifulSoup(res.text, 'html.parser') for tag in soup(['script', 'style']): tag.decompose() return soup.get_text() except Exception as e: return f"加载失败: {e}" # 提取超链接和表单(扩展版) def extract_links_and_forms(html, base_url): soup = BeautifulSoup(html, 'html.parser') # 提取超链接 links = [] for a in soup.find_all('a', href=True): href = a['href'] if not href.startswith('http'): href = urljoin(base_url, href) links.append((href, a.get_text(strip=True))) # 提取表单 forms = [] for form in soup.find_all('form'): action = form.get('action', '') if not action.startswith('http'): action = urljoin(base_url, action) method = form.get('method', 'get').lower() inputs = [] # 提取所有输入字段 for input_tag in form.find_all(['input', 'textarea', 'select']): name = input_tag.get('name') if not name: continue input_type = input_tag.get('type', 'text') value = input_tag.get('value', '') required = input_tag.has_attr('required') placeholder = input_tag.get('placeholder', '') if input_tag.name == 'textarea': input_type = 'textarea' value = input_tag.get_text() elif input_tag.name == 'select': input_type = 'select' options = [(opt.get('value') or opt.text, opt.text) for opt in input_tag.find_all('option')] inputs.append({ 'name': name, 'type': input_type, 'value': value, 'required': required, 'placeholder': placeholder, 'options': options }) continue # 处理 checkbox 和 radio if input_type == 'checkbox' or input_type == 'radio': inputs.append({ 'name': name, 'type': input_type, 'value': value, 'checked': input_tag.has_attr('checked'), 'required': required }) continue inputs.append({ 'name': name, 'type': input_type, 'value': value, 'required': required, 'placeholder': placeholder }) forms.append((action, method, inputs)) return links, forms # 输入框 def input_box(stdscr, prompt): curses.echo() stdscr.clear() stdscr.addstr(0, 0, prompt) stdscr.refresh() input_str = stdscr.getstr(1, 0).decode('utf-8') curses.noecho() return input_str # 提交表单(扩展版) def submit_form(stdscr, form): action, method, inputs = form data = {} for field in inputs: name = field['name'] input_type = field['type'] required = field.get('required', False) placeholder = field.get('placeholder', '') value = field.get('value', '') prompt = f"{name}" if placeholder: prompt += f"(提示:{placeholder})" if required: prompt += " [必填]" if input_type == 'select': options = field['options'] stdscr.clear() stdscr.addstr(0, 0, f"请选择 {name}:") for i, (val, text) in enumerate(options): stdscr.addstr(i + 1, 0, f"{i + 1}. {text}") stdscr.refresh() idx = int(stdscr.getstr(len(options) + 2, 0).decode('utf-8')) - 1 data[name] = options[idx][0] elif input_type == 'checkbox': checked = field.get('checked', False) res = input_box(stdscr, f"{name} [复选框] 是否选中?(y/n):") data[name] = 'on' if res.lower() == 'y' else '' elif input_type == 'radio': res = input_box(stdscr, f"{name} [单选] 是否选中?(y/n):") data[name] = value if res.lower() == 'y' else '' else: default = value if value else '' user_input = input_box(stdscr, f"{prompt}:") data[name] = user_input if user_input else default try: if method == 'post': res = requests.post(action, data=data) else: res = requests.get(action, params=data) return res.text except Exception as e: return f"表单提交失败:{e}" # 收藏夹菜单 def favorites_menu(stdscr, favorites, current_url): while True: options = ["新添", "删除", "退出"] action = show_list(stdscr, options, "收藏夹", options) if action == 0: # 新添 if current_url not in favorites: favorites.append(current_url) save_list(FAVORITES_FILE, favorites) elif action == 1: # 删除 if favorites: idx = show_list(stdscr, favorites, "选择要删除的收藏") if idx >= 0: favorites.pop(idx) save_list(FAVORITES_FILE, favorites) elif action == 2 or action == -1: break # 历史记录菜单 def history_menu(stdscr, history): while True: options = ["清空", "退出"] action = show_list(stdscr, options, "历史记录", options) if action == 0: # 清空 history.clear() save_list(HISTORY_FILE, history) elif action == 1 or action == -1: break elif action >= 0: return history[action] return None # 显示列表(收藏夹/历史记录) def show_list(stdscr, items, title, actions=None): selected = 0 while True: stdscr.clear() stdscr.addstr(0, 0, title) if actions: stdscr.addstr(0, len(title) + 2, f"| {' | '.join(actions)}") stdscr.addstr(1, 0, "-" * 50) for i, item in enumerate(items): if i == selected: stdscr.attron(curses.A_REVERSE) stdscr.addstr(i + 2, 0, f"{i + 1}. {item}") if i == selected: stdscr.attroff(curses.A_REVERSE) stdscr.addstr(len(items) + 3, 0, "方向键选择,Enter确认,q退出") stdscr.refresh() key = stdscr.getch() if key == curses.KEY_UP and selected > 0: selected -= 1 elif key == curses.KEY_DOWN and selected < len(items) - 1: selected += 1 elif key == ord('\n'): return selected elif key == ord('q'): return -1 def main(stdscr): curses.curs_set(0) # 隐藏光标 favorites = load_list(FAVORITES_FILE) history = load_list(HISTORY_FILE) # 初始页面加载 current_url = input_box(stdscr, "请输入网址:") page_text = fetch_page(current_url) if current_url not in history: history.append(current_url) if len(history) > 20: history.pop(0) save_list(HISTORY_FILE, history) # 初始数据 menu_options = ["收藏夹", "新的网页", "历史记录"] selected_menu = 0 links, forms = extract_links_and_forms(page_text, current_url) selected_link = 0 selected_form = 0 mode = "menu" # 当前模式:menu, link, form while True: stdscr.clear() h, w = stdscr.getmaxyx() # 显示顶部菜单 for i, opt in enumerate(menu_options): x = 2 + i * 15 if mode == "menu" and i == selected_menu: stdscr.attron(curses.A_REVERSE) stdscr.addstr(0, x, opt) if mode == "menu" and i == selected_menu: stdscr.attroff(curses.A_REVERSE) stdscr.addstr(1, 0, "-" * w) # 显示网页内容 lines = page_text.split('\n') for i, line in enumerate(lines[:h - 10]): stdscr.addstr(i + 2, 0, line[:w - 1]) # 显示超链接 stdscr.addstr(h - 8, 0, "超链接:") for i, (url, text) in enumerate(links): label = f"{i + 1}. {text[:30]}..." if mode == "link" and i == selected_link: stdscr.attron(curses.A_REVERSE) stdscr.addstr(h - 7 + i, 0, label[:w - 1]) if mode == "link" and i == selected_link: stdscr.attroff(curses.A_REVERSE) # 显示表单 stdscr.addstr(h - 7 + len(links) + 1, 0, "表单:") for i, (action, method, inputs) in enumerate(forms): label = f"表单 {i + 1}: {method.upper()} {action[:30]}..." if mode == "form" and i == selected_form: stdscr.attron(curses.A_REVERSE) stdscr.addstr(h - 6 + len(links) + i + 1, 0, label[:w - 1]) if mode == "form" and i == selected_form: stdscr.attroff(curses.A_REVERSE) # 底部提示 stdscr.addstr(h - 1, 0, "方向键选择,Enter确认,q返回,Tab切换区域") stdscr.refresh() # 用户输入处理 key = stdscr.getch() if key == ord('q'): break elif key == ord('\t'): # 切换模式:菜单 -> 链接 -> 表单 -> 菜单 if mode == "menu": mode = "link" elif mode == "link": mode = "form" else: mode = "menu" elif key == curses.KEY_UP: if mode == "menu" and selected_menu > 0: selected_menu -= 1 elif mode == "link" and selected_link > 0: selected_link -= 1 elif mode == "form" and selected_form > 0: selected_form -= 1 elif key == curses.KEY_DOWN: if mode == "menu" and selected_menu < len(menu_options) - 1: selected_menu += 1 elif mode == "link" and selected_link < len(links) - 1: selected_link += 1 elif mode == "form" and selected_form < len(forms) - 1: selected_form += 1 elif key == ord('\n'): if mode == "menu": if selected_menu == 0: favorites_menu(stdscr, favorites, current_url) elif selected_menu == 1: current_url = input_box(stdscr, "请输入网址:") page_text = fetch_page(current_url) if current_url not in history: history.append(current_url) if len(history) > 20: history.pop(0) save_list(HISTORY_FILE, history) links, forms = extract_links_and_forms(page_text, current_url) elif selected_menu == 2: selected_url = history_menu(stdscr, history) if selected_url: current_url = selected_url page_text = fetch_page(current_url) links, forms = extract_links_and_forms(page_text, current_url) elif mode == "link": if links: current_url = links[selected_link][0] page_text = fetch_page(current_url) links, forms = extract_links_and_forms(page_text, current_url) elif mode == "form": if forms: result = submit_form(stdscr, forms[selected_form]) page_text = result links, forms = extract_links_and_forms(page_text, current_url) return # 启动程序 if __name__ == "__main__": curses.wrapper(main) 这是我的纯文本浏览器的源代码。它报错了:Python 3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license()" for more information. = RESTART: C:\Users\number one\Desktop\纯文本浏览器.py Traceback (most recent call last): File "C:\Users\number one\Desktop\纯文本浏览器.py", line 363, in <module> curses.wrapper(main) File "C:\Users\number one\AppData\Local\Programs\Python\Python311\Lib\curses\__init__.py", line 73, in wrapper stdscr = initscr() File "C:\Users\number one\AppData\Local\Programs\Python\Python311\Lib\curses\__init__.py", line 30, in initscr fd=_sys.__stdout__.fileno()) AttributeError: 'NoneType' object has no attribute 'fileno'
最新发布
07-12
<think>我们遇到了一个错误:`AttributeError: 'NoneType' object has no attribute 'fileno'`。这个错误发生在`curses.wrapper(main)`调用中,具体是在`initscr()`函数试图获取标准输出的文件描述符(fileno)时失败,因为`_sys.__stdout__`被设置为`None`。 在Python中,当标准输出被重定向或关闭时,`sys.__stdout__`可能会被设置为`None`。这种情况在某些IDE(如IDLE或某些Python shell)中运行程序时比较常见,因为它们可能没有提供标准输出流。 由于curses需要访问终端(tty)来创建窗口,因此它要求标准输出是一个有效的文件描述符。在Windows上,curses模块实际上是使用一个名为`_curses`的扩展模块,它需要与控制台交互。 解决方案: 1. 确保在终端(如命令提示符或PowerShell)中运行此程序,而不是在IDE(如IDLE)中运行。因为IDE可能没有提供完整的终端环境。 2. 如果必须在IDE中运行,尝试配置IDE以使用终端模拟器运行程序。 但是,错误信息显示是在RESTART(重新启动)时发生的,这通常是在IDLE中运行程序时出现的。因此,建议在命令提示符下运行这个程序。 如果问题仍然存在,我们可以尝试捕获异常并给出提示,或者检查`sys.__stdout__`是否存在,如果不存在则尝试使用`sys.stdout`(但同样,如果它也是None,则无效)。 然而,在curses的wrapper函数中,它直接使用了`_sys.__stdout__`,我们无法直接修改。因此,最好的方法是确保程序在真正的终端中运行。 另外,在Windows上,Python的curses模块可能不是原生支持的(在标准库中,Windows的curses是通过一个名为`_curses`的模块实现的,它需要有一个可用的控制台)。确保你的Python安装包含了curses模块(在Windows上,通常安装时已经包含)。 如果以上方法都不行,我们可以尝试使用其他库(如blessed)或使用Windows控制台API,但这超出了当前代码的范围。 因此,建议用户: - 打开命令提示符(cmd)或PowerShell - 使用命令 `python "C:\Users\number one\Desktop\纯文本浏览器.py"` 来运行程序 如果用户坚持在IDE中运行,可能需要IDE支持终端模拟。 但是,我们也可以尝试在代码中检查环境,如果`sys.__stdout__`为None,则打印错误信息并退出。 修改代码,在`curses.wrapper(main)`调用之前添加检查: ```python import sys if __name__ == "__main__": if sys.__stdout__ is None or sys.__stdout__.fileno() < 0: print("错误:必须在终端中运行此程序。") sys.exit(1) else: curses.wrapper(main) ``` 但是,这种方法可能无法完全解决问题,因为即使`sys.__stdout__`存在,也可能无法在IDE中正常工作。所以最可靠的方法还是在终端中运行。 由于错误信息已经明确,我们暂时先建议用户使用终端运行。 如果用户无法在终端中运行,并且希望避免错误,我们可以尝试使用其他方式初始化curses,但风险较高。 考虑到时间,我们首先建议用户使用终端运行。如果问题依旧,再考虑其他解决方案。 因此,我们不对代码做修改,而是提供运行方式的建议。 但是,根据错误信息,我们的程序已经终止,所以我们需要处理这个异常。 我们可以将启动部分修改为: ```python if __name__ == "__main__": try: curses.wrapper(main) except AttributeError as e: print("无法初始化curses。请确保在终端(如命令提示符或PowerShell)中运行此程序。") print("错误详情:", e) ``` 这样,当在IDLE中运行时,程序会捕获异常并打印提示信息。 修改后的启动代码: ```python if __name__ == "__main__": try: curses.wrapper(main) except Exception as e: print(f"程序启动失败: {e}") print("请确保在终端(如命令提示符或PowerShell)中运行此程序。") ``` 这样用户就能看到友好的错误提示。 我们修改代码的最后部分: 原始代码: if __name__ == "__main__": curses.wrapper(main) 修改为: if __name__ == "__main__": try: curses.wrapper(main) except Exception as e: print(f"程序启动失败: {e}") print("请确保在终端(如命令提示符或PowerShell)中运行此程序。") input("按回车键退出...") # 等待用户按回车,避免窗口立即关闭 这样,当在IDLE中运行时,用户会看到错误提示,并知道需要在终端中运行。 注意:在真正的终端中,如果程序正常退出,我们不需要等待用户按回车,但在异常情况下,我们添加了等待,以便用户看到信息。 现在,我们修改代码并重新运行。 但是,请注意,在终端中运行程序时,如果出现其他异常,也会被捕获并打印,所以这个修改是通用的。 我们按照这个方案修改代码。 另外,如果用户是在Windows的命令提示符中运行,但是仍然出现这个错误,那可能是因为其他原因导致`sys.__stdout__`不可用。但这种情况比较少见。 因此,我们修改代码的最后部分。</think>您的纯文本浏览器程序在Windows上运行时遇到了一个常见问题,主要与`curses`库在Windows环境中的兼容性有关。错误信息表明`sys.__stdout__`为`None`,导致无法获取文件描述符(fileno)。 ### 问题原因与解决方案 1. **Windows下的`curses`限制**: - `curses`库在Windows上的支持有限,它依赖于`_curses`模块(Windows版) - 许多IDE(如IDLE)会重定向标准输出,导致`sys.__stdout__`为`None` 2. **解决方案**: ```python # 修改启动部分(文件末尾) if __name__ == "__main__": try: # 尝试在终端环境运行 curses.wrapper(main) except Exception as e: # 如果curses初始化失败,使用备用模式 print(f"curses初始化失败: {e}") print("切换到备用控制台模式...") import sys sys.stdout = open('CONOUT$', 'w') # 强制重定向到Windows控制台 main(curses.initscr()) # 手动初始化curses curses.endwin() # 结束时清理 ``` 3. **替代方案(推荐)**: - 使用`windows-curses`库(专为Windows优化的版本): ```bash pip install windows-curses ``` - 然后修改导入: ```python try: import curses except ImportError: # 回退到Windows专用版本 import windows_curses as curses ``` 4. **运行环境建议**: - 不要在IDLE/PyCharm等IDE中运行,使用命令提示符或PowerShell - 运行命令:`python "C:\Users\number one\Desktop\纯文本浏览器.py"` ### 完整修复代码(仅修改启动部分) ```python if __name__ == "__main__": try: # 标准方式运行 curses.wrapper(main) except (AttributeError, curses.error) as e: print(f"错误: {e}") print("正在尝试备用启动方式...") try: # 手动初始化curses stdscr = curses.initscr() curses.noecho() curses.cbreak() stdscr.keypad(True) # 运行主程序 main(stdscr) # 清理 curses.nocbreak() stdscr.keypad(False) curses.echo() curses.endwin() except Exception as e: print(f"最终启动失败: {e}") input("按任意键退出...") ``` ### 其他注意事项 1. **路径问题**: ```python # 修改为绝对路径更安全 import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) FAVORITES_FILE = os.path.join(BASE_DIR, 'favorites.txt') HISTORY_FILE = os.path.join(BASE_DIR, 'history.txt') ``` 2. **编码问题**: - 添加编码声明到文件开头: ```python # -*- coding: utf-8 -*- ``` 3. **异常处理**: - 在`fetch_page()`中添加更详细的错误处理: ```python except requests.RequestException as e: return f"网络错误: {type(e).__name__} - {str(e)}" ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值