前言:
此刻你们对“python全栈试题”可能比较着重,你们都想要剖析一些“python全栈试题”的相关知识。那么小编也在网上收集了一些对于“python全栈试题””的相关知识,希望你们能喜欢,大家一起来了解一下吧!前后端分离架构
在Flet架构中,前端可以是基于浏览器的Web,或者windows桌面端exe程序,或者移动端的apk等等,由于前后端代码都写到一起的,要去理解哪些是前端代码,哪些是后端代码就很费解了。
我们先分析一个简单的页面如下,页面显示了服务端的ini配置文件的值。左边是ini文件列表,下一级菜单是sections,右边是每个section下面的配置名称和值,可以修改保存。
暂时不涉及复杂的交互,按照目前的前后端分离的架构,前端进行页面渲染,然后向后端去请求数据并动态响应展示。保存也通过接口传递给后端,修改ini文件。
如上图所示,分离架构里面,服务端主要提供RestFul接口服务,只需要提供数据查询和更新即可,不用关注用户动作和呈现,前端去定义数据展示形态,实现用户查询动作和更新动作的捕获,并将请求发送到后端,根据后端范围的数据动态渲染。
Flet的前后端代码合一
Flet比较神奇,比方说前面例子中的Web服务,页面的展示、动作定义和数据获取都在Python中实现的,感觉好像是Python的代码Run起来之后,Server端也把前端的事情也干了。
我贴一下代码来看看,首先是主入口代码,这里基本上就是定义主页面,启动一个flet服务。
import loggingfrom GTUtility.config.manager_ini import Inisimport flet as ftfrom manager_flet.parse.parse_ini import IniControlsdef main(page: ft.Page): # page.title = f"base mode测试" page.vertical_alignment = ft.MainAxisAlignment.START page.theme_mode = ft.ThemeMode.LIGHT logging.warning("页面启动开始") config_data = Inis(conf=Params.conf_dir) config_data.load() columns = ft.Column(expand=True) controls = IniControls(expand=8) controls.load(config_data=config_data) columns.controls.append(ft.Container(expand=1, content=ft.Tabs(expand=10, tabs=[ ft.Tab(text="INI配置", content=controls), ft.Tab(text="基础数据配置", content=ft.Text("第二个菜单预留")) ]))) page.controls.append(columns) page.update() logging.warning("页面启动完成")if __name__ == '__main__': # upload_dir=open_ai_dir, ft.app(name="index", port=38090, target=main, view=ft.WEB_BROWSER, web_renderer="html")
Inis类是我自己封装的加载和管理ini的数据处理对象; 而iniControls是定义的展示视图,是一个自定义Controls类,用来定义页面展示元素和事件响应。Inis类可以理解为纯后端数据处理的类,由于前后端开发时间比较长,用Flet的时候还是习惯将数据处理也页面处理分开[捂脸]
另外比较有意思的是main入库,需要有一个page,在flet中,不管是Web还是APP,page可以看做前端的页面主容器,页面的最终展示可以看成是通过page.update()来渲染的。官网与介绍,每个flet都有一个page,下面可以包含多个view,通过view的切换来完成视图的切换,呈现不同的前端页面。
Flet定义前端展示效果和事件处理
不管是BS还是CS,用户层面上最重要的两个动作就是展示和事件响应。参见前面范例的代码,controls是自定义的ini展示组件,将这个组件append到系统的columns中,最终将columns添加到主页面page里面,在page里面展示,我们这个范例里面只有一个view。有前端基础的很容易理解这个处理。
columns = ft.Column(expand=True)controls = IniControls(expand=8) controls.load(config_data=config_data) columns.controls.append(ft.Container(expand=1, content=ft.Tabs(expand=10, tabs=[ ft.Tab(text="INI配置", content=controls), ft.Tab(text="基础数据配置", content=ft.Text("第二个菜单预留")) ]))) page.controls.append(columns) page.update()
再来深入看下自定义的控件实现,build函数是重写了父类的函数,看实现就知道主要是完成前端显示元素构建和布局设置。
class IniControls(ft.UserControl): def get_on_change(self, key: str, cfg, save_button): """ 生成事件处理函数 :param key: :return: """ section, option = key.split(".") # 定义根据事件更新的处理函数 def update(e): cfg.set(section=section, option=option, value=e.data) # 有数据更新,保存按钮生效 save_button.disabled = False save_button.update() def _save_cfg(self, e, file_name): try: self.config_data.cfg_ini[file_name].save() except Exception as esave: logging.error(f"保存异常:{esave}") else: logging.warning(f"{file_name}更新保存") e.control.disabled = True e.control.update() def _build_section(self, columns_tree, columns_content, file_name, section, cfg: ConfigParser): def click_on_section(e): columns_content.controls.clear() save_button = ft.TextButton(text="保存", on_click=lambda e: self._save_cfg(e, file_name), disabled=True) # 增加保存按钮 columns_content.controls.append(ft.Container(content=save_button)) # 展开 for option in cfg.options(section=section): self._build_cfg_param(columns_content, section, option, cfg, save_button) columns_content.update() section_control = get_list_title_for_expand( label=section, on_click=click_on_section, init_expand_status=False, text_css={"width": 180}, level=2 ) section_control.visible = False columns_tree.controls.append(section_control) return section_control def build(self): rows = ft.Row(vertical_alignment=ft.CrossAxisAlignment.START, expand=True) columns_tree = ft.Column(scroll=ft.ScrollMode.AUTO) columns_content = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True) # 按照文件遍历构造 for file_name, cfg in self.config_data.cfg_ini.items(): self._build_cfg(columns_tree, columns_content, file_name, cfg.cfg) columns_tree.scroll = ft.ScrollMode.ALWAYS columns_content.scroll = ft.ScrollMode.ALWAYS rows.controls.append(ft.Container(content=columns_tree)) # 间隔 rows.controls.append(ft.VerticalDivider(width=5, color=ft.colors.BLUE, thickness=3)) rows.controls.append(ft.Container(content=columns_content)) return ft.Container(content=rows)
其他的内容我们暂时不关注,先重点看看保存按钮,为每个保存按钮添加self._save_cfg点击处理函数,有点像一个回调函数,这就是一个前端页面动作响应。
save_button = ft.TextButton(text="保存", on_click=lambda e: self._save_cfg(e, file_name), disabled=True) # 增加保存按钮 columns_content.controls.append(ft.Container(content=save_button)) # 展开 for option in cfg.options(section=section): self._build_cfg_param(columns_content, section, option, cfg, save_button)
而保存动作是保存到后端,看下self._save_config函数。我们save到后端,同时对控件状态进行了更新,e.control.disabled的目的是保存之后设置按钮状态并更新页面。
def _save_cfg(self, e, file_name): try: self.config_data.cfg_ini[file_name].save() except Exception as esave: logging.error(f"保存异常:{esave}") else: logging.warning(f"{file_name}更新保存") e.control.disabled = True e.control.update()
按照flet的思路,我们根据web的一个案例进行了分析,这个写法确实没有前后端的概念了[捂脸]这是因为Flet是SDUI架构,官网也进行了阐述,我们下一小节来描述。
Flet的SDUI架构
参见官网
SDUI架构全称是Server-driven UI,简单理解就是服务端驱动UI,如下图,在我们的Web应用中,User program和Flet server都运行在Server端,Flet client可以是Web、exe或者apk应用。
我们的所有Python程序构成User program模块,完成业务流程, 与Flet client的交互通过Flet框架中的Flet server,这部分是由go实现。
每个模块详细说明如下:
User program:用户面程序,支持后端数据处理、Flet前端页面控件定义、事件定义以及页面刷新等等所有业务相关处理Flet server:传递前端(web或者apk等)事件以及后端处理结果,直接驱动用户页面展示,通过websockets与连接通信Flet client:浏览器或者apk等等,完成用户界面展示和动作捕获,并与Flet server交互
可以看出来SDUI架构,动作响应和页面刷新都需要由Server端驱动,Server端需要干很多以前前端干的是事情,websockets连接对资源消耗也更大,所以Flet引入了Flet Server并用go来实现作为底层通道,解决Python性能较差的问题,但Flet仍然存在两个弊端
前后端除了数据交互,还有前端行为和响应的交互,会增大交互时延,可能影响用户操作体验每个客户端都会开辟Websockets通道,增大长连接的资源占用对于本机场景,或者使用Flet做客户端,对接其他HTTP Server场景,也需要部署Flet Server?
Flet 纯客户端场景
在官方博客<Flet mobile update> 中,官方介绍了Flet新的桌面架构,去掉了Go的Flet Server(Fletd),这部分封装到Python里面,与Flet Client通信也去掉websocket,windows上采用tcp, linux和mac等系统采用unix pipes。
因为是单客户端场景,并发少,缩短交互路径能够减少时延,提升响应速度。
如上图,该场景下我称之为纯客户端场景,用Python+Flutter去驱动Flet Client,如果要跟服务端通信,可以像前端一样,通过requests等库与第三方服务端通信,基于HTTP。
移动端APP也是类似的,只是按照官方所述,需要注意Python程序需要引入比较纯粹的python,或者必须支持在ARM64架构上编译。
标签: #python全栈试题