龙空技术网

Flet-Python全栈框架剖析

南站往南 185

前言:

此刻你们对“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全栈试题