写在前面
上课第20天,打卡:
In the end, we are our choices. Build yourself a great story.
1 2017-09-10 - s17day20 2 3 内容回顾: 4 1. Http请求相关 5 6 2. Django请求生命周期 7 alert({{name}}); 8 3. Model操作 9 单表: 10 [obj,obj,obj] = models.xxx.objects.all() 11 12 [{},{},{}] = models.xxx.objects.values(‘id‘,‘name‘....) 13 14 [(),()] = models.xxx.objects.values_list(‘id‘,‘name‘....) 15 16 一对多: 17 dep部门表: 标题 (user_set) 18 19 user员工表:用户 邮箱 部门Id(FK) 20 21 22 qs = [obj,obj,obj] = models.user.objects.all() 23 for row in qs: 24 row.id,row.name,row.email,row.xx.title 25 26 [{},{},{}] = models.xxx.objects.values(‘id‘,‘name‘,‘xx__title‘) 27 [(),(),()] = models.xxx.objects.values_list(‘id‘,‘name‘,‘xx__title‘) 28 29 多对多: 30 业务线: id title M2M 31 32 33 管理员: id name (bs_set) 34 35 36 业务线和管理员关系表 37 38 obj = models.bs.objects.get(id=1) 39 40 [管理员,管理员,管理员] = obj.m2m.all() 41 42 4. 分页 43 44 5. csrf 45 46 47 6. cookie 48 保存在浏览器客户端的键值对 49 50 7. session 51 以来cookie,保存在服务器端的键值对 52 53 54 今日内容: 55 1. FBV & CBV 56 a. 57 FBV -> 函数 58 CBV -> 类 59 - dispatch 60 - get获取/post提交 61 62 b. 应用:登录验证 63 64 继承: 65 单继承: 66 # class BaseView(View): 67 # def dispatch(self, request, *args, **kwargs): 68 # if request.session.get(‘username‘): 69 # response = super(BaseView,self).dispatch(request, *args, **kwargs) 70 # return response 71 # else: 72 # return redirect(‘/login.html‘) 73 # 74 # class IndexView(BaseView): 75 # 76 # def get(self,request,*args,**kwargs): 77 # return HttpResponse(request.session[‘username‘]) 78 79 80 多继承: 81 82 # 多继承方式: 83 # class BaseView(object): 84 # def dispatch(self, request, *args, **kwargs): 85 # if request.session.get(‘username‘): 86 # response = super(BaseView,self).dispatch(request, *args, **kwargs) 87 # return response 88 # else: 89 # return redirect(‘/login.html‘) 90 # 91 # class IndexView(BaseView,View): 92 # 93 # def get(self,request,*args,**kwargs): 94 # return HttpResponse(request.session[‘username‘]) 95 96 装饰器: 97 98 def auth(func): 99 def inner(request,*args,**kwargs): 100 if request.session.get(‘username‘): 101 obj = func(request,*args,**kwargs) 102 return obj 103 else: 104 return redirect(‘/login.html‘) 105 return inner 106 107 108 # @method_decorator(auth,name=‘get‘) 109 class IndexView(View): 110 111 @method_decorator(auth) 112 def dispatch(self, request, *args, **kwargs): 113 if request.session.get(‘username‘): 114 response = super(IndexView,self).dispatch(request, *args, **kwargs) 115 return response 116 else: 117 return redirect(‘/login.html‘) 118 119 @method_decorator(auth) 120 def get(self,request,*args,**kwargs): 121 return HttpResponse(request.session[‘username‘]) 122 123 @method_decorator(csrf_exempt) # 无效 124 def post(self,request,*args,**kwargs): 125 return HttpResponse(request.session[‘username‘]) 126 127 128 特殊:CSRF 129 class IndexView(View): 130 131 @method_decorator(csrf_exempt) 132 def dispatch(self, request, *args, **kwargs): 133 return super(LoginView,self).dispatch(request, *args, **kwargs) 134 135 136 def get(self,request,*args,**kwargs): 137 return HttpResponse(request.session[‘username‘]) 138 139 140 def post(self,request,*args,**kwargs): 141 return HttpResponse(request.session[‘username‘]) 142 2. 序列化 143 方式一: 144 user_list = models.UserInfo.objects.all() 145 data = serializers.serialize("json", user_list) 146 [ 147 {"model": "app01.userinfo", "pk": 1, "fields": {"username": "\u5174\u666e", "password": "123123"}}, 148 {"model": "app01.userinfo", "pk": 2, "fields": {"username": "\u94f6\u79cb\u826f", "password": "666"}} 149 ] 150 151 方式二: 152 153 user_list = models.UserInfo.objects.values(‘id‘,‘username‘) 154 user_list = list(user_list) 155 data = json.dumps(user_list) 156 [ 157 {"username": "\u5174\u666e", "id": 1}, 158 {"username": "\u94f6\u79cb\u826f", "id": 2} 159 ] 160 161 问题:对json.dumps做定制: 162 163 import json 164 from datetime import date 165 from datetime import datetime 166 167 class JsonCustomEncoder(json.JSONEncoder): 168 def default(self, field): 169 if isinstance(field, datetime): 170 return field.strftime(‘%Y-%m-%d %H:%M:%S‘) 171 elif isinstance(field, date): 172 return field.strftime(‘%Y-%m-%d‘) 173 else: 174 return json.JSONEncoder.default(self, field) 175 176 177 user_list = [ 178 {‘id‘:1,‘name‘:‘alex‘,‘ctime‘: datetime.now()}, 179 {‘id‘:2,‘name‘:‘eric‘,‘ctime‘: datetime.now()} 180 ] 181 182 data = json.dumps(user_list,cls=JsonCustomEncoder) 183 print(data) 184 185 186 总结: 187 - 模板渲染 188 - Ajax 189 - json序列化 190 - 前端:js添加到页面 191 192 193 3. Form表单验证(用户请求验证+生成HTML标签) 194 示例:用户管理 195 a. 添加用户页面 196 - 显示HTML标签 197 - 提交:数据验证 198 - 成功之后保存 199 - 错误显示错误信息 200 201 总结: 202 1. 创建Form类(本质就是正则表达式的集合) 203 204 from django.forms import Form 205 from django.forms import fields 206 from django.forms import widgets 207 208 class UserForm(Form): 209 username = fields.CharField( 210 required=True, 211 error_messages={‘required‘:‘用户名不能为空‘}, 212 widget=widgets.TextInput(attrs={‘class‘:‘form-control‘}) 213 ) 214 password = fields.CharField( 215 required=True, 216 error_messages={‘required‘: ‘邮箱不能为空‘,‘invalid‘:‘邮箱格式错误‘}, 217 widget = widgets.TextInput(attrs={‘class‘: ‘form-control‘}) 218 ) 219 # fields.EmailField() 220 # fields.GenericIPAddressField(protocol=‘ipv4‘) 221 222 ut_id = fields.ChoiceField( 223 choices=[], 224 widget=widgets.Select(attrs={‘class‘:‘form-control‘}) 225 ) 226 227 role_id = fields.MultipleChoiceField( 228 choices=[], 229 widget=widgets.SelectMultiple(attrs={‘class‘:‘form-control‘}) 230 ) 231 232 def __init__(self,*args,**kwargs): 233 super(UserForm,self).__init__(*args,**kwargs) 234 # self.fields已经有所有拷贝的字段 235 self.fields[‘ut_id‘].choices = models.UserType.objects.values_list(‘id‘,‘title‘) 236 self.fields[‘role_id‘].choices = models.Role.objects.values_list(‘id‘,‘caption‘) 237 238 2. 只是生成HTML标签: 添加页面 239 form = MyForm() 240 241 {{form.xx}} 242 243 3. 带默认值的HTML标签: 编辑页面 244 form = MyForm(initial={‘xx‘: xxx}) 245 246 {{form.xx}} 247 248 4. 提交数据 249 form = MyForm(data=request.POST) 250 251 if form.is_valid(): 252 print(form.cleaned_data) 253 else: 254 print(form.errors) 255 256 问题:下拉框数据无法实时更新 257 class UserForm(Form): 258 username = fields.CharField( 259 required=True, 260 error_messages={‘required‘:‘用户名不能为空‘} 261 ) 262 password = fields.CharField( 263 required=True, 264 error_messages={‘required‘: ‘邮箱不能为空‘,‘invalid‘:‘邮箱格式错误‘} 265 ) 266 267 ut_id = fields.ChoiceField(choices=[]) 268 269 def __init__(self,*args,**kwargs): 270 super(UserForm,self).__init__(*args,**kwargs) 271 272 self.fields[‘ut_id‘].choices = models.UserType.objects.values_list(‘id‘,‘title‘) 273 274 275 276 示例:只用表单验证的功能(Ajax提交),注册&登录 277 278 279 定律: 280 【个数少,内容少】 281 页面摸态对话框:添加+删除+编辑 =》 ajax(无刷新) + Djaogo Form组件 282 - 验证 283 - 生成HTML(可用可不用,因为提交页面不刷新) 284 285 286 287 【适用于:数据个数多;博客】 288 新URL方式:添加+删除+编辑 =》 Form标签提交(页面刷新) + + Djaogo Form组件 289 290 - 验证 291 - 生成HTML(不用,无保留上次输入的内容) 292 293 294 个人: 295 296 - 删除利用模态对话框,确认 297 - 添加+修改: 新URL方式 298 299 300 301 302 作业:主机管理 303 304 用户表(id, user,pwd,email,mm) 305 业务线(id, name) # 用户表_set 306 主机表(id host ip port FK(业务线)) 307 用户业务线关系表(id uid bid) ****** 308 309 310 1. 登录+注册(Ajax+form组价) 311 2. FBV&CBV 312 业务线管理(列表,增加,修改) # 删除对话框确认删除 313 主机管理(列表,增加,修改) # 删除对话框确认删除 314 用户管理(列表,增加,修改) # 删除对话框确认删除 315 3. 分页 316 4. BootStrap【可选】 317 318 如何做: 319 周一 + 周二: 课上讲的内容理解 320 321 周三 : 写作业 322 323 周六: 本周不太理解+以前不理解;下午预习 324 4. 缓存 325 326 5. 中间件 327 328 6. 信号 329 330 7. Admin 331 332 333 334
武Sir - 笔记
################ # 2017-09-10 - 课上笔记 ################ 上节回顾 1.http请求周期 请求头和请求体使用 ‘\r\n\r\n‘ 分隔 2.Models操作 单表 [obj,obj,obj...] = models.xxx.objects.all() [{},{},{}...] = models.xxx.objects.values(xx,xx,xx...) [(),(),()...] = models.xxx.objects.values_list(xx,xx,xx...) 一对多 dep 部门表:标题 user 员工表:用户,邮箱,部门id(外键) qs = [obj,obj,obj...] = models.user.objects.all() for row in qs: row.id,row.name,row.email,row.xx.title user 查 dep [{},{},{}...] = models.user.objects.values(‘id‘,‘name‘,‘email‘,‘xx__title‘) [(),(),()...] = models.user.objects.values_list(‘id‘,‘name‘,‘email‘,‘xx__title‘) dep 反查 user : user_set (在dep表里隐含的字段,多对多同样) 多对多 业务线 id name M2M 用户表 id name django给创建第三张表(因为manytomany) 3.自定义分页模块 4.csrf 5.cookie 保存在客户端浏览器上的键值对 6.session 保存在服务器端的键值对,依赖cookie day20今日内容 1. FBV 和 CBV FBV:function basic view /index/ func(request) if "GET" == request.method: ... CBV:class basic view /index/ class(object) - 判断: get请求 就执行类里的get方法 post请求 就执行类里的post方法 form表单只能提交 ‘get‘ 和 ‘post‘ 方法 ajax 则可以提交:‘get‘, ‘post‘, ‘put‘, ‘patch‘, ‘delete‘, ‘head‘, ‘options‘, ‘trace‘ ajax 遵循resful规范 /index/ obj = 类() 找到类之后,执行完 __init__ 就执行 dispatch()方法,自己不写的话就执行父类的 dispatch() ‘‘‘ class B: def f1(self): self.f2() class A(B): def f2(self): print("---> A") obj = A() obj.f1() ---> B.f1(obj) --> obj.f2() ‘‘‘ def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn‘t exist, # defer to the error handler. Also defer to the error handler if the # request method isn‘t on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) POST obj.post 是通过反射 getattr() GET obj.get 是通过反射 getattr() ‘‘‘执行super,调用父类的dispatch方法‘‘‘ def dispatch(self, request, *args, **kwargs): print(‘before‘) # 执行父类的 dispatch方法 response = super(LoginView,self).dispatch(request,*args, **kwargs) print(‘after‘) return response ‘‘‘或者重写父类的dispatch‘‘‘ def dispatch(self, request, *args, **kwargs): method = request.method.lower() # print(‘+++++++++++++‘, self.http_method_names) if hasattr(LoginView,method): func = getattr(LoginView,method) return func(self, request, *args, **kwargs) else: return HttpResponse(‘err‘) def get(self,request,*args,**kwargs): print(‘------->> get‘) return render(request,‘login.html‘) def post(self,request,*args,**kwargs): print(‘------->> post‘) return HttpResponse(‘ok‘) 注意:django的CBV里装饰器不能直接用,需要使用: from django.utils.decorators import method_decorator FBV -> 函数 CBV -> 类 - dispatch - get/post 应用: 登录验证的几种实现方法: - 继承 - 单继承实现 ‘‘‘ from django.shortcuts import render,redirect,HttpResponse from django.views import View from django.views.decorators.csrf import csrf_exempt,csrf_protect from django.core import serializers from django.utils.decorators import method_decorator # Create your views here. class Login(View): def dispatch(self, request, *args, **kwargs): return super(Login,self).dispatch(request, *args, **kwargs) def get(self, request,*args, **kwargs): return render(request,‘login.html‘) def post(self, request,*args, **kwargs): user = request.POST.get(‘user‘) pwd = request.POST.get(‘pwd‘) if ‘alex‘ == user and ‘123‘ ==pwd: request.session[‘user‘] = user return render(request,‘private.html‘,{‘user‘:user}) return render(request,‘login.html‘,{‘msg‘:‘用户名或密码错误!‘}) class Base(View): def dispatch(self, request, *args, **kwargs): if request.session.get(‘user‘): response = super(Base,self).dispatch(request, *args, **kwargs) return response else: return redirect(‘/login/‘) class Index(Base): def get(self, request,*args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ - 多继承实现 ‘‘‘ class Base(object): def dispatch(self, request, *args, **kwargs): if request.session.get(‘user‘): response = super(Base,self).dispatch(request, *args, **kwargs) return response else: return redirect(‘/login/‘) class Index(Base,View): def get(self, request,*args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ - 装饰器 auth 是装饰器函数 - 加在类上 @method_decorator(auth,name=‘post‘) ‘‘‘ # 装饰器:验证用户是否已经登录 def auth(func): def wrapper(request, *args, **kwargs): if request.session.get(‘user‘): obj = func(request, *args, **kwargs) return obj else: return redirect(‘/login/‘) return wrapper @method_decorator(auth,‘get‘) class Index(View): def dispatch(self, request, *args, **kwargs): return super(Index, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ - 加在dispatch上 @method_decorator(auth) ‘‘‘ # 装饰器:验证用户是否已经登录 def auth(func): def wrapper(request, *args, **kwargs): if request.session.get(‘user‘): obj = func(request, *args, **kwargs) return obj else: return redirect(‘/login/‘) return wrapper class Index(View): @method_decorator(auth) def dispatch(self, request, *args, **kwargs): return super(Index, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ - 加在具体方法上 @method_decorator(auth) ‘‘‘ # 装饰器:验证用户是否已经登录 def auth(func): def wrapper(request, *args, **kwargs): if request.session.get(‘user‘): obj = func(request, *args, **kwargs) return obj else: return redirect(‘/login/‘) return wrapper class Index(View): @method_decorator(auth) def get(self, request, *args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ from django.views.decorators.csrf import csrf_exempt,csrf_protect - csrf_exepmt 加在POST上例外,只能加在 dispatch上有效,官网bug。 2. 序列化 (Json是不能处理 queryset的) - 模板渲染 - ajax - from django.core import serializers - serializers 的局限性: queryset里必须是obj才可以 如果是models.xxx.objects.values(xx,xx,xx) 这样得到的queryset是不能用serializers进行序列化的!!! 只能采用在后台自己拼接好字典等形式,自己搞成json.dumps然后传给ajax - json 序列化 (有局限性,需自定义dumps) 因为json能处理的数据类型有限,比如datetime类型就不能处理,需要自定义dumps函数 方式一: user_list = models.UserInfo.objects.all() data = serializers.serialize("json", user_list) [ {"model": "app01.userinfo", "pk": 1, "fields": {"username": "\u5174\u666e", "password": "123123"}}, {"model": "app01.userinfo", "pk": 2, "fields": {"username": "\u94f6\u79cb\u826f", "password": "666"}} ] 方式二: user_list = models.UserInfo.objects.values(‘id‘,‘username‘) user_list = list(user_list) data = json.dumps(user_list) [ {"username": "\u5174\u666e", "id": 1}, {"username": "\u94f6\u79cb\u826f", "id": 2} ] 问题:json支持的数据类型有限,针对一些数据类型,例如datatime类型则不能进行序列化: class JSONEncoder(object): """Extensible JSON <http://json.org> encoder for Python data structures. Supports the following objects and types by default: +-------------------+---------------+ | Python | JSON | +===================+===============+ | dict | object | +-------------------+---------------+ | list, tuple | array | +-------------------+---------------+ | str | string | +-------------------+---------------+ | int, float | number | +-------------------+---------------+ | True | true | +-------------------+---------------+ | False | false | +-------------------+---------------+ | None | null | +-------------------+---------------+ To extend this to recognize other objects, subclass and implement a ``.default()`` method with another method that returns a serializable object for ``o`` if possible, otherwise it should call the superclass implementation (to raise ``TypeError``). """ ... 解决办法:对json.dumps做定制: import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return field.strftime(‘%Y-%m-%d %H:%M:%S‘) elif isinstance(field, date): return field.strftime(‘%Y-%m-%d‘) else: return json.JSONEncoder.default(self, field) user_list = [ {‘id‘:1,‘name‘:‘alex‘,‘ctime‘: datetime.now()}, {‘id‘:2,‘name‘:‘eric‘,‘ctime‘: datetime.now()} ] data = json.dumps(user_list,cls=JsonCustomEncoder) print(data) ajax扩展: 从后端获得的字符串: JSON.parse() 可以被 dataType: ‘JSON‘ 这一行替换: # 后端代码: ... import json return HttpResponse(json.dumps({‘flag‘:True,‘msg‘:‘ok‘})) ... 示例1:使用JSON.parse()的情况,返回的是 object类型 <form id="my_form" action="/login/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="password" name="pwd"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/jquery-3.2.1.min.js" %}"></script> <script src="{% static "js/jquery.cookie.js" %}"></script> <script src="{% static "js/bootstrap.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/login/‘, type:‘POST‘, data:{ ‘user‘:$(‘#my_form input[name="user"]‘).val(), ‘pwd‘:$(‘#my_form input[name="pwd"]‘).val() }, headers:{‘X-CSRFToken‘: $.cookie(‘csrftoken‘)}, success:function (data) { var info = JSON.parse(data); console.log(info); console.log(typeof info); } }) } </script> 示例2:使用dataType: ‘JSON‘的情况,返回的是string类型 <form id="my_form" action="/login/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="password" name="pwd"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/jquery-3.2.1.min.js" %}"></script> <script src="{% static "js/jquery.cookie.js" %}"></script> <script src="{% static "js/bootstrap.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/login/‘, type:‘POST‘, datatype:‘JSON‘, data:{ ‘user‘:$(‘#my_form input[name="user"]‘).val(), ‘pwd‘:$(‘#my_form input[name="pwd"]‘).val() }, headers:{‘X-CSRFToken‘: $.cookie(‘csrftoken‘)}, success:function (info) { console.log(info); console.log(typeof info); } }) } </script> 3. Django的Form表单验证(用户请求的验证 + 生成HTML标签) 示例:用户管理 添加用户页面: - 显示HTML标签 - 提交:数据验证 - 成功之后保存 - 错误的话则提示错误信息 from django.forms import widgets 样式 总结: 1. 创建类 MyForm(本质就是正则表达式的集合),继承Form类 2. 只是生成HTML标签:form = MyForm() 添加页面 3. 带默认值的HTML标签:form = MyForm(initial={‘xx‘:‘xx‘,...}) 编辑页面 4. 提交数据:form = Form(data=request.POST) if form.is_valid(): print(form.cleaned_data) else: print(form.errors) 5. 存在的问题:下拉框有更新时无法实时更新到前端页面 解决办法:重写下 __init__(self,*args,**kwargs) ‘‘‘ class UserForm(Form): user = fields.CharField(required=True,error_messages={‘required‘:‘用户名不能为空‘}) pwd = fields.CharField(required=True,error_messages={‘required‘:‘密码不能为空‘}) email = fields.EmailField(required=True,error_messages={‘required‘:‘邮箱不能为空‘,‘invalid‘:‘邮箱格式不正确‘}) # user_type = models.UserType.objects.values_list(‘id‘,‘title‘) # user_type = list(user_type) ut_id = fields.ChoiceField(choices=[]) role_id = fields.MultipleChoiceField(choices=[]) # ip = fields.GenericIPAddressField(required=True) # 解决下拉框数据更新后,前端页面不更新的问题 def __init__(self,*args,**kwargs): super(UserForm,self).__init__(*args,**kwargs) self.fields[‘ut_id‘].choices = models.UserType.objects.values_list(‘id‘,‘title‘) self.fields[‘role_id‘].choices = models.Role.objects.values_list(‘id‘,‘caption‘) ‘‘‘ 一对多 多对多 三元运算:v = xxx if xxx else [] 找name等于user的input标签 $(‘#f1 input[name="user"]‘).val(‘xxxxx‘) $(‘#f1 .error‘).remove() input标签后提示错误信息: $.each(arg.msg, function(k,v){ var tag = document.createFlement(‘span‘); tag.innerHTML = v[0]; tar.className = "error" # <span class=‘error‘>v[0]</span> $(‘#f1 input[name="‘ + k + ‘user"]‘).after(tag) }) 定律: 如果用模态对话框进行添加或者修改等操作,则前端提交数据需要用ajax,后端可以用django的form组件 因为ajax不刷新,所以可以利用form组件的验证功能,生成HTML的功能可用可不用 如果用新的页面进行添加或者修改等操作,则用ajax和form表单提交都可以;后端可以用django的form组件 因为form有刷新,生成HTML的功能要使用,不可以自己手写HTML标签,避免之前的数据因为刷新导致丢失; 4. 缓存 5. 中间件 6. 信号(scrapy爬虫框架里有用) 7. Django的 Admin JS跳转页面的方法:location.href = ‘/index.html‘ day20作业: 还是主机管理 1. 登录 + 注册(ajax+form组件) FBV / CBV 都可以 2. 业务线管理(列表、增加、修改 页面跳转) 模态确认删除 单表 3. 主机管理(列表、增加、修改 页面跳转) 模态确认删除 一对多 4. 用户管理(列表、增加、修改 页面跳转) 模态确认删除 多对多 5. 自定义分页 6. Bootstrap 把课上讲的内容搞明白,还有几个之前忘记的点,今天老师写的; 然后开始做作业; 周六上午,整理出遇到的问题; ‘‘‘‘‘‘
# shell in a box 插件
三元运算符 列表推导式 生成器推导式
时间: 2024-11-05 12:37:26