T

the5fire

关注python、vim、linux、web开发和互联网--life is short, we need python.

通过github actions部署aws lambda记录 - s3部署、ECR部署 以及固定出口IP

背景 一个每天只需要运行一次的策略(每天只需要去交易所拿一次行情数据,计算完成后输出结果),放到EC2里跑有点浪费资源,不如部署到aws lambda中。 本篇内容主要记录在使用lambda时遇到的依赖资源过大的问题。 下面主要分两部分来记录, 第一部分:lambda使用 关于lambda应该不用详细介绍,直接看这里即可:lambda文档 lambda的使用比较简单,创建时有三种方式,前两种(从头开始创建和使用蓝图)可以直接在aws面板上编辑代码,或者上传代码压缩包(zip)来部署,第三种是基于容器来部署。 根据项目不同情况,有三种部署方式: 1. 没有外部依赖的代码,比如: import json def lambda_handler(event, context): return { 'statusCode': 200, 'body': json.dumps({'content': 'hi the5fire'}) } 可以直接在面板编辑并更新项目。 2. 有外部依赖的情况,比如: import json import requests def lambda_handler(event, context): r = requests.get('https://api.github.com/user', auth=('user', 'pass')) return { 'statusCode': 200, 'body': json.dumps({'content': f'hi the5fire, code:{r.status_code}'}) } 这种情况需要处理外部依赖,有两个方案: 方案一:在部署项目时需要把依赖打包一起部署。打包命令如下 : pip install -r requirements.txt --target . zip -r lambda_function.zip . -x '*.git*' 之后把zip包在aws lambda管理面板上传即可。 方案二:可以通过layer的方式处理 即在lambda管理面包上找到【层】的菜单,进去创建一个新的层,把依赖单独打包上传到该层,最后关联到你的lambda函数下面即可。 这两个方案都有一个限制就是你的整个项目+依赖包的大小不能超过250M,如果超了,那就看第三种方式。 3. 使用容器部署的方式,也就是ECR 简单来说就是把你的代码放进容器里,镜像打包注册到aws的ecr,然后lambda直接运行容器. 在项目中增加一个Dockerfile: FROM public.ecr.aws/lambda/python:3.12 # Copy requirements.txt COPY . ${LAMBDA_TASK_ROOT} # Install the specified packages RUN pip install -r requirements.txt # Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) CMD [ "lambda_function.lambda_handler" ] 项目结构如下: . |-- .github/workflows/deploy.yml |-- Dockerfile |-- README.md |-- lambda_function.py |-- requirements.txt |-- strategy.py 部署的话需要使用docker和aws cli工具,参考这里:https://docs.aws.amazon.com/zh_cn/lambda/latest/dg/python-image.html,我自己的使用方式也会放到下面github action workflow配置中。 第二部分:github action配置 使用github action部署lambda之前,需要先去aws的IAM管理后台创建访问KEY,然后配置到github 的action/secrets中。 对于使用zip部署的方式也分两种情况,压缩包小于10M可以直接在上传,大于10M、小于250M的需要通过s3来上传。 所以这部分的action配置也分两种。 直接上传zip文件的方式: # .github/workflows/deploy.yml name: Deploy Lambda Function on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python 3.12 uses: actions/setup-python@v2 with: python-version: 3.12 - name: Install & zip run: | pip install --upgrade pip pip install -r ./requirements.txt zip -r lambda_function.zip . -x '*.git*' - name: Deploy to AWS Lambda env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }} run: | # Assuming you have an IAM role ARN for your Lambda function aws lambda update-function-code \ --function-name helloworld \ --zip-file fileb://lambda_function.zip 通过 s3 上传zip包并部署: 这里需要先到aws s3上创建桶 name: Deploy Lambda Function on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python 3.12 uses: actions/setup-python@v2 with: python-version: 3.12 - name: Install & zip run: | pip install --upgrade pip pip install -r ./requirements.txt --target . zip -r lambda_function.zip . -x '*.git*' - name: Deploy to AWS Lambda env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }} run: | # Assuming you have an IAM role ARN for your Lambda function aws s3 cp ./lambda_function.zip s3://${{ secrets.BUCKET_NAME }}/ aws lambda update-function-code --function-name ${{ secrets.FUNCTION_NAME }} \ --s3-bucket ${{ secrets.BUCKET_NAME }} --s3-key lambda_function.zip 使用ecr方式部署: 这一步需要配置aws account id,你在ECR创建完存储库之后就可以从URI上看到。 name: Deploy Lambda Function on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }} steps: - name: Checkout uses: actions/checkout@v4 - name: get login run: | aws ecr get-login-password --region ${{ secrets.AWS_REGION}}| docker login --username AWS --password-stdin ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com - name: update image run: | docker build -t docker-image:test . docker tag docker-image:test ${{secrets.AWS_ACCOUNT_ID }}.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/${{ secrets.FUNCTION_NAME }}:latest docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/${{ secrets.FUNCTION_NAME }}:latest - name: update function run: | aws lambda update-function-code \ --function-name ${{ secrets.FUNCTION_NAME }} \ --image-uri ${{secrets.AWS_ACCOUNT_ID}}.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/${{ secrets.FUNCTION_NAME }}:latest 需要说明的是,我这里都是只使用github action来做update function code,并没有使用它来创建function的需求,因此没写这创建的逻辑。对于使用ecr来部署的lambda function来说,需要先提交代码到github上,运行action后生成镜像,然后再创建lambda。 最后,关于出口IP固定的配置,参考文档:使用 Lambda 函数、Amazon VPC 和无服务器架构生成静态出站 IP 地址 示例代码: https://github.com/the5fire/action-lambda-demo/commits/main/ 阅读原文 Django视频教程

2024/6/26
articleCard.readMore

No more authentication methods to try,Permission denied (publickey)

简言之就是ssh client更新了,不支持rsa的私钥,导致无法登陆。 ssh登陆服务器,出现类似下面的提示: debug3: authmethod_is_enabled publickey debug1: Next authentication method: publickey debug1: Offering public key: /home/user/.ssh/id_rsa RSA ... agent debug1: send_pubkey_test: no mutual signature algorithm <-- ssh-rsa is not enabled debug1: No more authentication methods to try. user@hostname: Permission denied (publickey). 本来以为是key弄错了,或者服务器被hei了。加上 -v 参数才发现问题。 解决方案也很简单,就是在 ~/.ssh/config 中增加: Host * ServerAliveInterval 30 PubkeyAcceptedKeyTypes +ssh-rsa 如果是出现:no matching host key type found. Their offer: ssh-rsa,ssh-dss 的错误,还需要配置: HostKeyAlgorithms=ssh-rsa,ssh-dss 具体原因: https://www.openssh.com/txt/release-8.2 参考:https://confluence.atlassian.com/bitbucketserverkb/ssh-rsa-key-rejected-with-message-no-mutual-signature-algorithm-1026057701.html 阅读原文 Django视频教程

2023/11/21
articleCard.readMore

创作者滞后的快乐

一、 前几天@Manjusaka 给我发来一个截图,如下: 自己创作/分享的内容能够帮到别人确实值得开心,尤其是别人给你直接的反馈。 这种快乐我称之为滞后的快乐,这种快乐可以持续很久,知道你的内容被从互联网抹除,或者你被从星球上抹除。 从我的写博客、分享内容的经验来说,分享本身就是让人兴奋的一件事。脑袋中空想的东西,如果不寄托到电子呈现出来的文字上,你很难把它具象化。偶尔会看过去几年十几年写的内容时会好奇自己那时的脑袋中是在想什么,处于什么样的境地,能把想法化为这样的文字。 片段的分享比较轻松,能比较快的击中你的兴奋点,反馈途径足够短。当然快乐也有限。这类经历多了,也就免疫了。所以会发现那些文字写的足够多的人,开始系统的输出。 二、 系统的输出刚开始并不是特别有趣,因为反馈路径足够漫、足够长。 比如我写的《Django企业开发实战》花了一年左右的时间。中间的过程并不是那么舒服,想象下要把一团团棉花不断的压缩成一片一片的样子,然后整理起来。 在此之前我也是花了近两年的时间录制了 《Django实战开发》的视频,时长达 2499 分钟。中间的过程也不是由快乐裹挟,而是要始终拧着一股劲才行,保持、保证足够的专注。说实话,并不容易。 不过好处就是你有了更具象的东西,视频相对于文字,更加丰富,也更由录制时的内味儿。纸质书相对于电子文字更加具象,可触摸,可感受。 三、 从开始写博客几年后通过网络结识了不少人,也收到过很多反馈。至今通过 the5fire 这个 ID 在网上还能找到各种各样的碎片信息。相对于碎片的内容,足够系统的内容显然更能产生成就感和快乐。 比如近两年的快乐就来源于《Django企业开发实战》的读者,以及对应的视频。有些是来自网友的反馈,有些是来自生活中的朋友,同事。正向的反馈会让你觉得这个事做的还不错。具体的反馈内容这里就别贴上来了。当然也有来着网络某一角陌生人的 Diss,这个确实是让人不太舒服的一点,好在属于少数,把内容保留好,激励自己也不错。毕竟这样才能看到上升空间。 四、 前段时间有同事说也要写本书,聊了一下。现在愿意写书的人,都不是冲着钱来的,这点确实遗憾,始终无法形成足够正向的飞轮。大家的目的都是整理、分享、做点实质的输出、有所贡献。 当然,技术书也不是说都不畅销(zhengqian),比如今天要送出的由图灵赞助的《Python 3反爬虫原理与绕过实战》,据说很畅销。爬虫类的内容果然适应性足够广。 本篇文章的正题是分享下我对分享反馈的想法,当反馈的碎片快乐积累的足够多,不如压缩一下,系统整理。有兴趣写书的同学可以给我留言,我推荐编辑给你。 另外捎带着发一些福利,从去年离开知乎后一直在学习新行业的业务知识,疏于书写。发现公众号读者还在不断增加。感谢各种读者的保留(当然也可能忘了还关注着这么个公众号)。 五、赠书 三本 参与方式: 关注公众号:Python程序员杂谈, 在本篇文章(公众号对应的文章)下留言即可,我会随机抽出三位读者,截止日期:2020-03-21 08:00,中奖读者会收到我的回复,需要在2020-03-23 08:00 前发送邮寄地址给我。 阅读原文 Django视频教程

2020/3/17
articleCard.readMore

Django源码解析第一季 剧终

更新了近两年的时间,对于早期就支持的读者确实表示抱歉,也很感谢各位的理解。 今天终于上传了最后一节,非常感谢各位读者的支持。 源码阅读如果没有切实的场景确实不那么容易,如果你是检查每节都看过来并且自己翻了源代码的,我对你表示佩服。 事实上the5fire 个人认为,这套源码的课程更适合遇到对应的问题,或者有对应的场景,刚好要看对应的源码,可以顺带着看看 the5fire 的讲解。 源码里的很多地方的应用场景其实已经超过了我的使用场景,因此只能是推测、尝试他的用法和这么写的原因。 需要说明的是,我的解读也不一定正确,受限于个人的使用场景、个人的精力和时间投入。如果读者发现其中哪些内容有误或跟你理解的有误差,欢迎反馈。 最后再次感谢。 接下来会搞一些新的事情。 阅读原文 Django视频教程

2019/10/26
articleCard.readMore

【Django源码阅读】Django 自定义异常处理页面源码解读

Django 自定义异常处理页面源码解读 这个解读来源于一个读者的反馈,于是花了几分钟看了下这部分源码,打算用十分钟的时间写一下,预计阅读需要 5 分钟。 自定义异常页面 Django 提供了常见的错误的页面,比如 说用户访问了一个不存在的路径,引发的 404 系统发生了一个异常,出现了 500 一个好的网站应该可以给用户友好的信息提示,比如:“服务器提了一个问题”之类的,然后给用户一个引导。对于商业网站需要注意的是错误页面的流量也是流量,应该有明确的引导。 在 Django 中定义这类处理很简单,只需要在 urls.py 中配置: # 参考:https://github.com/the5fire/typeidea/blob/deploy-to-cloud/typeidea/typeidea/urls.py#L24 handler404 = Handler404.as_view() handler500 = Handler50x.as_view() 当然你需要定义这里面的 Handler50x: class Handler404(CommonViewMixin, TemplateView): template_name = '404.html' def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) return self.render_to_response(context, status=404) class Handler50x(CommonViewMixin, TemplateView): template_name = '50x.html' def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) return self.render_to_response(context, status=500) 这样就可以简单的控制出错时展示给用户的页面了。需要注意的是,这个配置只会在非 Debug 模式下有效。 Django Error Handler 源码解析 要看这部分源码的第一步是判断 Django 可能会在哪处理这个异常。有很多方法,这里是说一种,从请求的入口开始撸。 注意我看到版本是 Django 2.0.1 1 WSGI Handler 的部分 # 代码:https://github.com/the5fire/django-inside/blob/84f272e1206554b43c86c0f7a50f37d1f3efbc28/django/core/handlers/wsgi.py#L135 class WSGIHandler(base.BaseHandler): request_class = WSGIRequest def __init__(self, *args, **kwargs): super(WSGIHandler, self).__init__(*args, **kwargs) self.load_middleware() def __call__(self, environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send(sender=self.__class__, environ=environ) request = self.request_class(environ) response = self.get_response(request) # the5fire: 注意这儿 # ... the5fire:省略其他 2 BaseHandler 中的 get_response # ref: https://github.com/the5fire/django-inside/blob/84f272e1206554b43c86c0f7a50f37d1f3efbc28/django/core/handlers/base.py#L94 def get_response(self, request): """Return an HttpResponse object for the given HttpRequest.""" # Setup default url resolver for this thread set_urlconf(settings.ROOT_URLCONF) response = self._middleware_chain(request) # the5fire: 这里进去 response._closable_objects.append(request) # If the exception handler returns a TemplateResponse that has not # been rendered, force it to be rendered. if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)): response = response.render() if response.status_code == 404: logger.warning( 'Not Found: %s', request.path, extra={'status_code': 404, 'request': request}, ) return response 3 被包装的 _middleware_chain # https://github.com/the5fire/django-inside/blob/84f272e1206554b43c86c0f7a50f37d1f3efbc28/django/core/handlers/base.py#L76 def load_middleware(self): """ Populate middleware lists from settings.MIDDLEWARE. Must be called after the environment is fixed (see __call__ in subclasses). """ self._request_middleware = [] self._view_middleware = [] self._template_response_middleware = [] self._response_middleware = [] self._exception_middleware = [] handler = convert_exception_to_response(self._get_response) for middleware_path in reversed(settings.MIDDLEWARE): middleware = import_string(middleware_path) # ... the5fire:忽略中间这些代码 handler = convert_exception_to_response(mw_instance) # We only assign to this when initialization is complete as it is used # as a flag for initialization being complete. self._middleware_chain = handler 4 具体处理异常的部分 def convert_exception_to_response(get_response): """ Wrap the given get_response callable in exception-to-response conversion. All exceptions will be converted. All known 4xx exceptions (Http404, PermissionDenied, MultiPartParserError, SuspiciousOperation) will be converted to the appropriate response, and all other exceptions will be converted to 500 responses. This decorator is automatically applied to all middleware to ensure that no middleware leaks an exception and that the next middleware in the stack can rely on getting a response instead of an exception. """ @wraps(get_response) def inner(request): try: response = get_response(request) except Exception as exc: response = response_for_exception(request, exc) # the5fire: 这里进去 return response return inner def response_for_exception(request, exc): if isinstance(exc, Http404): if settings.DEBUG: response = debug.technical_404_response(request, exc) else: response = get_exception_response(request, get_resolver(get_urlconf()), 404, exc) # ... the5fire: 省略掉一大坨类似的代码 else: signals.got_request_exception.send(sender=None, request=request) # the5fire: 下面这一行,具体的处理逻辑。 response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info()) # Force a TemplateResponse to be rendered. if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)): response = response.render() return response 5 异常处理逻辑 # https://github.com/the5fire/django-inside/blob/84f272e1206554b43c86c0f7a50f37d1f3efbc28/django/core/handlers/exception.py#L107 def handle_uncaught_exception(request, resolver, exc_info): """ Processing for any otherwise uncaught exceptions (those that will generate HTTP 500 responses). """ if settings.DEBUG_PROPAGATE_EXCEPTIONS: raise logger.error( 'Internal Server Error: %s', request.path, exc_info=exc_info, extra={'status_code': 500, 'request': request}, ) if settings.DEBUG: return debug.technical_500_response(request, *exc_info) # Return an HttpResponse that displays a friendly error message. # the5fire: 这里会解析到对应的handler ,比如我们定义的那个 callback, param_dict = resolver.resolve_error_handler(500) return callback(request, **param_dict) 6 最终解析到 urls/resolvers.py 中 # 完整代码: https://github.com/the5fire/django-inside/blob/84f272e1206554b43c86c0f7a50f37d1f3efbc28/django/urls/resolvers.py#L555 def resolve_error_handler(self, view_type): callback = getattr(self.urlconf_module, 'handler%s' % view_type, None) # the5fire: 这里就是去获取 urls.py 中对应的配置 if not callback: # No handler specified in file; use lazy import, since # django.conf.urls imports this file. from django.conf import urls callback = getattr(urls, 'handler%s' % view_type) return get_callable(callback), {} 最后 实际上花了比预计更多的时间来把完整的代码贴出来,以及明确对应的版本。在 Django 1.11 中的处理逻辑有些不同。 实际阅读时间也会比预计的久,但如果能理解这个过程,你对于Django也会有更深的进步。 阅读原文 Django视频教程

2019/8/10
articleCard.readMore

你是怎么学习 Python 的 | 知乎高赞回答

这个回答是我在知乎认真回答的问题中,点赞和收藏最高的,所以题目高赞是对我来说的(捂脸 ------- 以下是正文 ------- 我在换第二份 Python 工作时也被 HR 问过类似的问题:你花了多少时间学习 Python,然后开始能正式工作的。 回答是:一两天。 一、 需要介绍下背景: 在我开始写 Python 之前,其实已经在用其他的语言开发项目了,Java、.Net、VB 6.0。 所以在这种情况下,需要学习的就是如何使用 Python 语言表达出来你想要实现的东西,这个跟学习英语或者其他语言差不多,你有想说的东西,只是需要尝试用另外一种方式表达出来。 所谓的一两天其实就是熟悉了 Python 的语法,当时是看了这本书——《简明 Python 教程》: 介绍 · 简明 Python 教程 https://bop.mol.uno/ 这是刚搜索出来的最新的版本。 这本书很小,花一两天时间看完,以及写写书上代码就能熟悉了。 之后其实就是在工作中跟着别人写好的代码来写代码。 当你对一个语言不是很熟悉的时候,借鉴、模仿现成的代码可以比较快的实现某个实际的功能。 二、 不过,实际上,单纯的读完 《简明 Python 教程》是远远不够的,这个在第二次换工作时能明显感受到。工作中能快速积累的是使用 Python 的经验,解决问题的经验,但对 Python 的基础库以及能力范围(也就是能做的事)掌握会不足。 还是需要系统学习,所以之后又系统看了《Python 基础教程》 并且把里面对应的练习都实现了一遍: 《Python 基础教程》中的十个项目练习和代码 python项目练习一:即时标记 python项目练习二:画幅好画 python项目练习三:万能的XML python项目练习四:新闻聚合 python项目练习五:虚拟茶话会 python项目练习六:使用CGI进行远程编辑 python项目练习七:自定义公告板 python项目练习八:使用XML-RPC进行远程文件共享 python项目练习九:文件共享2-GUI版本 python项目练习十:DIY街机游戏 对于初学者来说,每次攻克一个练习,得到实际可运行的代码,可以操作的程序,还是可以达到正向反馈的。 三、 之后为了面试还看了 《Python 高级编程》: 理论上当你掌握了 《Python 基础教程》里面的内容,完成了所有练习后,开始干活是没问题了。但是高级编程确实能给面试提供帮助(逃 另外,最近两年出版的《流畅的 Python》也属于进阶以及应对面试准备的「弹药」,当然本质还是为了提升能力嘛。 最后总结一下: 书的话就是三本: 《简明 Python 教程》 《Python 基础教程》 《Python 高级编程》 和 《流畅的 Python》 基本上就是一个阶段搞明白一本就行,看太多的基础类的书作用不大,低水平重复没有意义。 另外编程是一个实践性很强的事,不写代码是没有感觉的,不写完整的程序是没有成就感的。需要的就是在这个循环中「看书 - 实践 - 解决问题 - 总结」不断的重复。 知乎回答链接:https://www.zhihu.com/question/55493026/answer/687917097 阅读原文 Django视频教程

2019/5/25
articleCard.readMore

中奖名单|《代码里的世界观》赠书活动

中奖名单|《代码里的世界观》赠书活动 经过一周的沉淀,这次的数据看起来比较正常,从文章阅读量上就能看出来。恭喜这次中奖的读者: 前两名: 随机两名: {'侯国威', '青鸟'} 随机代码: import random readers = [ '刘玉龙', 'testercode', 'RuntimeError', 'PaperSheep', '独角兽男孩', '侯国威', 'MrLi', '镇上村树', '朝菌', 'DouWo', '(X_X)', 'JAMI', '青鸟', 'orangleliu', ] final_readers = set() while len(final_readers) != 2: final_readers.add(random.choice(readers)) print(final_readers) 关注最早的读者: orangleliu 请所有中奖读者 {'星辰', 'Guang', '侯国威', '青鸟', 'orangleliu'} 尽快把邮寄地址和电话发给我,通过公众号回复的功能(注意不是留言)。 没中奖的读者可以保持关注,福利还会有。当然也可以直接去购买这本书: https://item.jd.com/12501348.html 阅读原文 Django视频教程

2019/5/22
articleCard.readMore

不做翻译需求的程序员 + 赠书

最近在做大量的代码 Review 的工作,尝试整理出一些大家在写代码时要避免的一些问题,同时也在读《代码整洁之道》和《代码里的世界观》。也在知识星球(同公众号名:Python程序员杂谈)中发了一些关于代码设计的内容。 1. 不做需求翻译器 绝大部分程序员在入门编程时学习的第一门课程/书籍差不多都会是《 XXX 程序设计》,但是很多人在实际写代码时却完全不做任何设计的工作,只是单纯的把产品的需求翻译为可执行的代码。对于软件开发来说,这是入门阶段,毕竟程序员的第一要务还是要把产品的需求转变为可运行的系统/软件。 这种做法就像我们刚开始学习写作文时那种流水账式的写法,能表达信息(实现功能),但是内容冗余,不易维护。 所以我们在接需求时,需要做的是充分理解产品需求,什么叫充分理解产品需求呢?实际工作中很简单,你能给其他人讲明白要做的产品需求是啥即可。之后需要做的是根据产品需求思考应该怎么实现,对其中的逻辑做一些抽象。 单纯讲可能不太容易理解,举个例子,有这么一个需求,写一个函数判断用户上传图片文件的是否合法。需求描述是,如果后缀是.txt则返回不合法,如果后缀是.gif返回不合法,如果后缀是.mp4返回不合法。(等一系列不合法的后缀)。 那么怎么实现呢,如果不加思索的写代码就会出现这么个状况: def is_valid_filetype(suffix): if suffix == 'txt': return False if suffix == 'gif': return False if suffix == 'mp4': return False return True 这样写代码的问题是什么?暂且不说需求问题。代码的问题在于,如果我需要新增一个不支持的类型,那就要新增一个if判断。显然更好的做法是: invalid_suffix_set = {'txt', 'gif', 'mp4'} def is_valid_filetype(suffix): return not suffix in invalid_suffix_set 这么一改看起来简单多了,消除了if分支语句(这个是代码设计中很重要的部分),并且改成了基于配置的方式,新增不合法后缀只需要去修改配置invalid_suffix_set即可,不涉及代码逻辑改动。 但是我们再回过头来看下需求,是不是有什么不妥。写一个函数判断用户上传图片文件的是否合法。相信读者已经看出来了,这个需求其实有问题:需要枚举出所有不合法的后缀,鬼知道有多少中文件后缀。虽然我们做了不合法后缀的配置,但是还需要经常去做修改来支持新发现的文件后缀。 所以应该去反问下产品,哪些文件是合法的?显然这是一个可以有限枚举的内容。最终可能得到的代码是这样: valide_suffix_set = {'png', 'webp', 'jpg', 'jpeg'} def is_valid_filetype(suffix): reutrn suffix in valide_suffix_set 2. S.O.L.I.D 原则 SOLID 这个词很多人可能没有听说过,这是程序设计时经常用到的一些原则,如果你看过设计模式,应该知道,所有的设计模式都会基于这些原则。SOLID 什么意思呢?为了避免不了解的读者此时关闭文章去搜索(笑),我列到这里: 在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期[1] 引入的记忆术首字母缩略字[2][3],指代了面向对象编程和面向对象设计的五个基本原则。 - S 单一功能原则 认为对象应该仅具有一种单一功能的概念。 - O 开闭原则 认为“软件体应该是对于扩展开放的,但是对于修改封闭的”的概念。 - L 里氏替换原则 认为“程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的”的概念。 - I 接口隔离原则 认为“多个特定客户端接口要好于一个宽泛用途的接口”[5] 的概念。 - D 依赖反转原则 认为一个方法应该遵从“依赖于抽象而不是一个实例”[5] 的概念。 依赖注入是该原则的一种实现方式。 摘自:https://zh.wikipedia.org/wiki/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1) 看到这些描述后,想必很多读者会叹一口气 —— 原来说的是这个。 我在 Review 代码时突然想到,如果每个程序员在设计程序,编写代码时,先念一串这样的「咒语」—— 「用 SOLID ,写好代码」,会不会能写出更好维护的代码。有些戏谑的成分,但是在写代码、做设计时需要一些原则来支撑。 关于这些原则,这里不做详细解读,对于知道的人可以经常拿出来 Review 下你写的代码,对于不知道的,需要去找几个例子,指导你写出更好的代码。 3. 最后 本次要赠的书就是《代码里的世界观》,看书名以为是偏软技能类的,但从内容上来说,软件设计的内容占大头。这本书可能理解为一个架构师的思考,因为各章节篇幅比较短,读起来相对容易。 感谢图灵出版社,本次赠书:5 本。 参与方式:老规矩——留言说下「你写代码、设计程序时的一些原则是什么」? 抽奖方式(去公众号留言): 点赞排行前两名 前两名之外的留言中随机抽出两名 没中奖的留言中抽出关注最早的一名 开奖时间:下周二(05.21 晚上 10:30),可以关注公众号: Python 程序员杂谈 的文章 阅读原文 Django视频教程

2019/5/14
articleCard.readMore

Python音频播客推荐:捕蛇者说

关注我公众号比较久的人应该都看到过我经常会推一些好的播客内容,比较遗憾的是很多中文类的技术播客更新一段时间之后就停更了,或者间歇性停更或者永久「弃坑」。但不管怎么说,很多被记载、传播的音频还是有其价值的,我们需要更多的记录着真实开发者想法、经验、牢骚的声音。 希望「捕蛇者说」能保持更新。 简单介绍下播客的几个主播: laike9m(https://laike9m.com/)目前就职于 Google laixintao(https://www.kawabangga.com/) 目前就职于 蚂蚁金服 Adam Wen (https://twitter.com/wenxiaobin) 我在知乎的同事 Manjusaka (PyCon China 2018 组织者) 目前就职于 饿了么 其他的我就不多介绍了,相对于网上一水的技术类的营销文章,来自一线开发者的讨论还是很值得一听的,反正我听完后是有挺多收获的。 下面我直接把最新一期的内容贴上来,链接:https://pythonhunter.org/episodes/2 --- copy 分割线 ---- 本期主持 laike9m laixintao Adam Wen Manjusaka 本期提要 00:00:35 嘉宾介绍 00:03:15 开发中踩过的坑 00:04:20 Requests UA 带来的 Github 误封问题 00:08:06 单元测试遇到的坑 00:11:53 非法 Cookies 引发的坑 00:19:38 一个不合法的 HTTP Header 00:25:01 glibc 引发的内存泄漏 00:30:20 werkzeug 的 bug 复现 PR 00:32:42 关于一些不好的库,文档,feature 的吐槽 00:33:13 Python 的 LEGB 问题 00:43:06 一些不好的库与文档, 生产环境不推荐的一些做法 00:52:20 函数参数的种类 00:57:11 Celery, asyncio, os 的一些槽点 01:12:14 一些疑难问题排查的技巧与工具 01:12:30 Py-Spy, 一个 Python 进程取样分析工具 01:17:13 构造最小可复现样例 01:18:30 Debug 技巧 01:23:40 用 PDB 来 Debug 01:26:25: pyrasite, attached 到 Python 进程的 REPL 01:29:25 休息,提升 Debug 效率的方法 01:30:50 能复现的 Bug 情况都是幸福的 01:38:05 库与文章的推荐 播客中提到的内容 Github REST API v3 Mock Python Cookie 标准库实现 Tornado 4.0 Cookies Parse 实现 RFC 7230 Section 5.4 Host Fix memory leak in Rule function builder Short description of the scoping rules? lxml Kafka Python Golang Functional options for friendly APIs Beautiful Regular expression Denial of Service - ReDoS tox Netty Request Demo linux环境内存分配原理 Buildout Google Python Style Guide 理解Python的UnboundLocalError(Python的作用域) PEP 3102 -- Keyword-Only Arguments PEP 0570 -- Positional-Only Arguments Digg's v4 launch: an optimism born of necessity. let me google that for you celery BPO-36054 BPO-29406 asyncio uvloop Py-Spy: A sampling profiler for Python programs. Sentry PDB pyrasite FreezeGun: Let your Python tests travel through time Hidden features of Python pingtop Awesome Python Click What the f*ck Python Gevent PySnooper Curious Course on Coroutines and Concurrency 公众号点击{阅读原文}进行收听和查看相关链接: https://pythonhunter.org/episodes/2 阅读原文 Django视频教程

2019/5/5
articleCard.readMore

知乎回答:程序员应该怎么提高自己

工作中几个能力是要有的: 算法能力,尤其是面试前需要刷一段时间的算法; 代码能力,对自己用到的语言/库/框架能够熟练到拿来即用; 设计能力,是指程序设计能力,除了能实现某个需求外,还需要考虑程序的健壮性和可维护性; 业务能力,能够知道需求方确切的想要什么,并且能够很好的沟通出需求方的需求。 怎么提高呢?个人看法如下: 算法的话,刷题会比较快,很多人都会以刷 LeetCode 为乐,挺好的事; 代码能力,只能是多写,做不同类型的项目,但是都需要做的足够完整,完成后需要总结下项目中用到的技术有哪些,以及有没有类似的技术; 设计能力,需要深入一个项目,足够完整,不断的迭代。只有一个项目不断迭代(不断啃自己之前写过的「烂」代码)的情况下,才能知道之前的设计有什么问题,以后如何避免; 业务能力,需要对自己所涉及的业务领域有足够多的了解,绝大部分情况下,技术只是支撑业务实现,只有当你掌握了足够的信息时,才能快速的 Get 到对方的点是什么,以及对方可能缺了什么。 回答链接:https://www.zhihu.com/question/321425347/answer/661430245 阅读原文 Django视频教程

2019/4/24
articleCard.readMore