为什么range不是迭代器?range到底是什么类型?

迭代器是 23 种设计模式中最常用的一种(之一),在 Python 中随处可见它的身影,我们经常用到它,但是却不一定意识到它的存在。在关于迭代器的系列文章中(链接见文末),我至少提到了 23 种生成迭代器的方法。有些方法是专门用于生成迭代器的,还有一些方法则是为了解决别的问题而“暗中”使用到迭代器。在系统学习迭代器之前,我一直以为 range() 方法也是用于生成迭代器的,现在却突然发现,它生成的只是可迭代对象,而并不是迭代器! (PS:Python2 中 range() 生成的是列表,本文基于Python3,生成的是可迭代对象)于是,我有了这样的疑问:为什么 range() 不生成迭代器呢?在查找答案的过程中,我发现自己对 range 类型的认识存在一些误区。因此,本文将和大家全面地认识一下 range ,期待与你共同学习进步。1、range() 是什么?它的语法:range(start, stop [,step]) ;start 指的是计数起始值,默认是 0;stop 指的是计数结束值,但不包括 stop ;step 是步长,默认为 1,不可以为 0 。range() 方法生成一段左闭右开的整数范围。>>> a = range(5) # 即 range(0,5)>>> arange(0, 5)>>> len(a)5>>> for x in a:>>> print(x,end=" “)0 1 2 3 4对于 range() 函数,有几个注意点:(1)它表示的是左闭右开区间;(2)它接收的参数必须是整数,可以是负数,但不能是浮点数等其它类型;(3)它是不可变的序列类型,可以进行判断元素、查找元素、切片等操作,但不能修改元素;(4)它是可迭代对象,却不是迭代器。# (1)左闭右开>>> for i in range(3, 6):>>> print(i,end=” “)3 4 5# (2)参数类型>>> for i in range(-8, -2, 2):>>> print(i,end=” “)-8 -6 -4>>> range(2.2)—————————-TypeError Traceback (most recent call last)…TypeError: ‘float’ object cannot be interpreted as an integer# (3)序列操作>>> b = range(1,10)>>> b[0]1>>> b[:-3]range(1, 7)>>> b[0] = 2TypeError Traceback (most recent call last)…TypeError: ‘range’ object does not support item assignment# (4)不是迭代器>>> hasattr(range(3),’iter’)True>>> hasattr(range(3),’next’)False>>> hasattr(iter(range(3)),’next’)True2、 为什么range()不生产迭代器?可以获得迭代器的内置方法很多,例如 zip() 、enumerate()、map()、filter() 和 reversed() 等等,但是像 range() 这样仅仅得到的是可迭代对象的方法就绝无仅有了(若有反例,欢迎告知)。这就是我存在知识误区的地方。在 for-循环 遍历时,可迭代对象与迭代器的性能是一样的,即它们都是惰性求值的,在空间复杂度与时间复杂度上并无差异。我曾概括过两者的差别是“一同两不同”:相同的是都可惰性迭代,不同的是可迭代对象不支持自遍历(即next()方法),而迭代器本身不支持切片(即__getitem__() 方法)。虽然有这些差别,但很难得出结论说它们哪个更优。现在微妙之处就在于,为什么给 5 种内置方法都设计了迭代器,偏偏给 range() 方法设计的就是可迭代对象呢?把它们都统一起来,不是更好么?事实上,Pyhton 为了规范性就干过不少这种事,例如,Python2 中有 range() 和 xrange() 两种方法,而 Python3 就干掉了其中一种,还用了“李代桃僵”法。为什么不更规范点,令 range() 生成的是迭代器呢?关于这个问题,我没找到官方解释,以下纯属个人观点 。zip() 等方法都需要接收确定的可迭代对象的参数,是对它们的一种再加工的过程,因此也希望马上产出确定的结果来,所以 Python 开发者就设计了这个结果是迭代器。这样还有一个好处,即当作为参数的可迭代对象发生变化的时候,作为结果的迭代器因为是消耗型的,不会被错误地使用。而 range() 方法就不同了,它接收的参数不是可迭代对象,本身是一种初次加工的过程,所以设计它为可迭代对象,既可以直接使用,也可以用于其它再加工用途。例如,zip() 等方法就完全可以接收 range 类型的参数。>>> for i in zip(range(1,6,2), range(2,7,2)):>>> print(i, end=”")(1, 2)(3, 4)(5, 6)也就是说,range() 方法作为一种初级生产者,它生产的原料本身就有很大用途,早早把它变为迭代器的话,无疑是一种画蛇添足的行为。对于这种解读,你是否觉得有道理呢?欢迎就这个话题与我探讨。3、range 类型是什么?以上是我对“为什么range()不产生迭代器”的一种解答。顺着这个思路,我研究了一下它产生的 range 对象,一研究就发现,这个 range 对象也并不简单。首先奇怪的一点就是,它竟然是不可变序列!我从未注意过这一点。虽然说,我从未想过修改 range() 的值,但这一不可修改的特性还是令我惊讶。翻看文档,官方是这样明确划分的——有三种基本的序列类型:列表、元组和范围(range)对象。(There are three basic sequence types: lists, tuples, and range objects.) 这我倒一直没注意,原来 range 类型居然跟列表和元组是一样地位的基础序列!我一直记挂着字符串是不可变的序列类型,不曾想,这里还有一位不可变的序列类型呢。那 range 序列跟其它序列类型有什么差异呢? 普通序列都支持的操作有 12 种,在《你真的知道Python的字符串是什么吗?》这篇文章里提到过。range 序列只支持其中的 10 种,不支持进行加法拼接与乘法重复。>>> range(2) + range(3)—————————————–TypeError Traceback (most recent call last)…TypeError: unsupported operand type(s) for +: ‘range’ and ‘range’>>> range(2)*2—————————————–TypeError Traceback (most recent call last)…TypeError: unsupported operand type(s) for *: ‘range’ and ‘int’那么问题来了:同样是不可变序列,为什么字符串和元组就支持上述两种操作,而偏偏 range 序列不支持呢?虽然不能直接修改不可变序列,但我们可以将它们拷贝到新的序列上进行操作啊,为何 range 对象连这都不支持呢?且看官方文档的解释:…due to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation will usually violate that pattern.原因是 range 对象仅仅表示一个遵循着严格模式的序列,而重复与拼接通常会破坏这种模式…问题的关键就在于 range 序列的 pattern,仔细想想,其实它表示的就是一个等差数列啊(喵,高中数学知识没忘…),拼接两个等差数列,或者重复拼接一个等差数列,想想确实不妥,这就是为啥 range 类型不支持这两个操作的原因了。由此推论,其它修改动作也会破坏等差数列结构,所以统统不给修改就是了。4、小结回顾全文,我得到了两个偏冷门的结论:range 是可迭代对象而不是迭代器;range 对象是不可变的等差序列。 若单纯看结论的话,你也许没有感触,或许还会说这没啥了不得啊。但如果我追问,为什么 range 不是迭代器呢,为什么 range 是不可变序列呢?对这俩问题,你是否还能答出个自圆其说的设计思想呢?(PS:我决定了,若有机会面试别人,我必要问这两个问题的嘿~)由于 range 对象这细微而有意思的特性,我觉得这篇文章写得值了。本文是作为迭代器系列文章的一篇来写的,所以对于迭代器的基础知识介绍不多,欢迎查看之前的文章。另外,还有一种特殊的迭代器也值得单独成文,那就是生成器了,敬请期待后续推文哦~猜你想读:Python进阶:迭代器与迭代器切片Python进阶:设计模式之迭代器模式你真的知道Python的字符串是什么吗?官方文档:http://t.cn/EGMzJt8—————–本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。 ...

January 5, 2019 · 2 min · jiezi

Django搭建个人博客:结束和开始

教程看到这里,你已经学会如下内容:搭建开发环境博文管理用户管理发表评论若干小功能搭建简单的小博客,以上的功能够用了。相信你的志向不止于此。毕竟程序员面试个个造火箭啊。接下来学什么虽说Django已经入门了,但别激动,还有漫漫长路需要走哒。重要的学习点列举如下。响应式布局响应式布局,简单来说就是页面布局随着终端设备的变化而自动适应。教程为了起步平缓,没有展开这方面的内容。也就是说教程中的博客在手机上浏览,界面会变得非常的糟糕。好在Bootstrap就是一个强大的响应式布局框架。在它的官网上有非常详细的介绍、复制就能用的代码,请耐心查阅:Bootstrap官方文档类视图教程中的视图,至始至终都是用def,即函数写的。实际上视图还可以用class,即类来写。Django内置了很多常用的基础类,你可以继承这些类,从而让代码更加的精简,并且有效的复用。虽然用类视图重写代码后,功能上不会有任何变化,但是良好的重构是扩展和维护的基础。详情看官网:Django类视图官方文档各种轮子轮子就是别人封装好的库。很多常用功能别人写好了,就不要你重复劳动了。虽然不用自己写,但最起码要学会使用。优秀的轮子推荐如下:django-allauth:强大的第三方登录app。它可以帮助你轻松使用微博、微信、GitHub等社交账号登录自己的网站。django-braces:包含各种有用的基础类视图。写类视图用它可以节省很多时间。django-taggit:给文章添加标签的app。django-haystack:实现复杂的定制化搜索。django-rest-framework:有的读者迫不及待想尝试前后端分离开发模式,这个框架几乎是你唯一的选择。优秀的app很多,这里没办法列举。感兴趣的同学请点这里:djangopackages,这个网址集合了所有优秀的库。慢慢研究吧。部署开发好项目的demo后,你应该尽快部署到云服务器上,让世界各地的用户随时浏览你的网页。不要误会,部署只是一个起点,作用是让你获得巨大的成就感;部署后你还是需要持续的优化、添加网站的功能,以及修补你满天飞的Bug。关于如何部署上线,很遗憾我懂的也不多,帮不上你的忙。多多搜索相关博客吧,会有很多厉害的人手把手教你的。小提示:部署上线时一定记得在setting.py中设置DEBUG = False,否则你的网站会向用户无意义的报错,并且导致安全问题。在settings.py中有个SECRET_KEY、以及各种账号密码,都需要保密。请妥善保管这些密码,不要泄露到如GitHub之类的公开网络上。我的做法是将敏感信息以txt保存在服务器本地,然后用代码去读取它们。本教程结束了吗基础部分内容确实结束了。进阶的内容,还会继续补充撰写,包括:类视图多级评论网站流量跟踪文章栏目和标签图片处理视频模块第三方登录可视化图表测试与维护以及其他内容全写出来可能比基础章节还多…就是这么任性。有读者注意到我的博客比教程中要完善很多,后面的章节我会尽量把用到的技巧都讲解到。以后更新的频率会慢些,工作、生活的挤压,自由的时间越来越少,望理解。编程只是我的业余爱好,写教程付出了我很多的精力和时间。如果你有收获,不妨在我的GitHub博客教程代码给一个小星星哟~感谢支持。新的开始看完这些,你就可以踏上新的征程了。写这篇博文正好在2019年元旦(发布还要晚几天),而你应该在未来的某个普通的日子看到。陌生人,祝你学业进步、事业有成!欢迎常到杜赛的个人网站做客!

January 4, 2019 · 1 min · jiezi

Django搭建个人博客:在博文中发表评论

在没有互联网的年代,我们用日记来记录每天的心得体会。小的时候我有一个带锁的日记本,生怕被别人看见里面写了啥,钥匙藏得那叫一个绝。现在时代变了,网络版的日记本:博客,却巴不得越多人看越好。别人看完你写的深度好文,难免也想高谈阔论一番,这就是“评论”功能。本章将要编写的评论模块,几乎没有新的知识点,而是将前面章节内容的综合应用。强烈建议读者自行尝试编写这部分内容,测试自己的知识掌握程度。准备工作评论是一个相对独立的功能,因此新建一个评论的app:(env) E:\django_project\my_blog > ppython manage.py startapp comment有的人觉得奇怪,没有博文就没有评论,为什么说评论是“独立”的功能?那是因为不仅博文可以评论,照片、视频甚至网站本身都可以“被评论”。将其封装成单独的模块方便以后的扩展。确认app创建成功后,记得在settings.py中注册:my_blog/settings.py…INSTALLED_APPS = [ … ‘comment’,]…TIME_ZONE = ‘Asia/Shanghai’…因为我们想显示发表评论的时间,修改时区设置TIME_ZONE为上海的时区。然后在my_blog/urls.py中注册根路由:my_blog/urls.py…urlpatterns = [ … # 评论 path(‘comment/’, include(‘comment.urls’, namespace=‘comment’)),]…编写核心功能评论的模型首先编写评论的模型:comment/models.pyfrom django.db import modelsfrom django.contrib.auth.models import Userfrom article.models import ArticlePost# 博文的评论class Comment(models.Model): article = models.ForeignKey( ArticlePost, on_delete=models.CASCADE, related_name=‘comments’ ) user = models.ForeignKey( User, on_delete=models.CASCADE, related_name=‘comments’ ) body = models.TextField() created = models.DateTimeField(auto_now_add=True) class Meta: ordering = (‘created’,) def str(self): return self.body[:20]模型中共有2个外键:article是被评论的文章user是评论的发布者别忘了每次新增、修改Model后,必须数据迁移。提示:你必须先在setting.py中注册app,这个app中的数据迁移才能生效评论的表单用户提交评论时会用到表单,因此新建表单类:comment/forms.pyfrom django import formsfrom .models import Commentclass CommentForm(forms.ModelForm): class Meta: model = Comment fields = [‘body’]因为模型中的2个外键将通过视图逻辑自动填写,所以这里只需要提交body就足够了。评论的url在comment app中新建路由文件:comment/urls.pyfrom django.urls import pathfrom . import viewsapp_name = ‘comment’urlpatterns = [ # 发表评论 path(‘post-comment/<int:article_id>/’, views.post_comment, name=‘post_comment’),]评论必须关联在某篇具体的博文里,因此传入博文的id,方便后续调用。post_comment()视图还没写,先取个名字占位置。评论的视图评论的视图函数如下:comment/views.pyfrom django.shortcuts import render, get_object_or_404, redirectfrom django.contrib.auth.decorators import login_requiredfrom django.http import HttpResponsefrom article.models import ArticlePostfrom .forms import CommentForm# 文章评论@login_required(login_url=’/userprofile/login/’)def post_comment(request, article_id): article = get_object_or_404(ArticlePost, id=article_id) # 处理 POST 请求 if request.method == ‘POST’: comment_form = CommentForm(request.POST) if comment_form.is_valid(): new_comment = comment_form.save(commit=False) new_comment.article = article new_comment.user = request.user new_comment.save() return redirect(article) else: return HttpResponse(“表单内容有误,请重新填写。”) # 处理错误请求 else: return HttpResponse(“发表评论仅接受POST请求。")代码中有2个新面孔。get_object_or_404():它和Model.objects.get()的功能基本是相同的。区别是在生产环境下,如果用户请求一个不存在的对象时,Model.objects.get()会返回Error 500(服务器内部错误),而get_object_or_404()会返回Error 404。相比之下,返回404错误更加的准确。redirect():返回到一个适当的url中:即用户发送评论后,重新定向到文章详情页面。当其参数是一个Model对象时,会自动调用这个Model对象的get_absolute_url()方法。因此接下来马上修改article的模型。实际上之前的章节已经用过redirect()了。功能是相同的,实现上略有区别。文章的模型按照上面说的,在文章模型中添加get_absolute_url()方法:article/models.py…# 记得导入from django.urls import reverseclass ArticlePost(models.Model): … # 获取文章地址 def get_absolute_url(self): return reverse(‘article:article_detail’, args=[self.id])通过reverse()方法返回文章详情页面的url,实现了路由重定向。文章详情视图评论模块需要在文章详情页面展示,所以必须把评论模块的上下文也传递到模板中。因此修改article/views.py中的article_detail():article/views.py…from comment.models import Commentdef article_detail(request, id): # 已有代码 article = ArticlePost.objects.get(id=id) # 取出文章评论 comments = Comment.objects.filter(article=id) … # 添加comments上下文 context = { ‘article’: article, ’toc’: md.toc, ‘comments’: comments } …filter()可以取出多个满足条件的对象,而get()只能取出1个,注意区分使用文章详情模板到最后一步了,坚持。所有后台的功能已经写完了,就差把所有这些展现到页面中了。修改文章详情页面:templates/article/detail.html…<div class=“col-9”> … <!– 已有代码,文章正文 –> <div class=“col-12”> … </div> <!– 发表评论 –> <hr> {% if user.is_authenticated %} <div> <form action=”{% url ‘comment:post_comment’ article.id %}" method=“POST” > {% csrf_token %} <div class=“form-group”> <label for=“body”> <strong> 我也要发言: </strong> </label> <textarea type=“text” class=“form-control” id=“body” name=“body” rows=“2”></textarea> </div> <!– 提交按钮 –> <button type=“submit” class=“btn btn-primary “>发送</button> </form> </div> <br> {% else %} <br> <h5 class=“row justify-content-center”> 请<a href=”{% url ‘userprofile:login’ %}">登录</a>后回复 </h5> <br> {% endif %} <!– 显示评论 –> <h4>共有{{ comments.count }}条评论</h4> <div> {% for comment in comments %} <hr> <p> <strong style=“color: pink”> {{ comment.user }} </strong> 于 <span style=“color: green”> {{ comment.created|date:“Y-m-d H:i:s” }} </span> 时说: </p> <pre style=“font-family: inherit; font-size: 1em;">{{ comment.body }}</pre> {% endfor %} </div></div><!– 目录 –><div class=“col-3 mt-4”> …</div>…表单组件中的action指定数据提交到哪个url中显示评论中的comments.count是模板对象中内置的方法,对包含的元素进行计数|date:“Y-m-d H:i:s”:管道符你已经很熟悉了,用于给对象“粘贴”某些属性或功能。这里用于格式化日期的显示方式。请尝试修改其中的某些字符试试效果。<pre>定义预格式化的文本,在我们的项目中最关键的作用是保留空格和换行符。该标签会改变文字的字体、大小等,因此用style属性重新定义相关内容。尝试将<pre>替换为div,输入多行文本试试效果。之前说代码最好不要复制粘贴,否则有些“小坑”你是留意不到的。比如在<pre>标签中的文本千万不能缩进。测试又到了激动人心的测试环节了。登录自己的账户,进入某个文章详情页面,发现已经可以进行留言了:如果退出登录,显示提示语:点击登录就回到登录页面。评论模块的发布、展示功能就搞定了。扫尾工作数据的删、改功能我们已经做过很多遍,这里不打算再赘述了。评论同样也可以支持Markdown语法,或者插入Emoji表情符号。读者可以自己去实现感兴趣的功能。有些网站干脆就没有删除、更新评论的功能。因为对小站来说,这些功能用到的次数太少太少了,不如把精力用在更有价值的地方去。比如我的博客就没有。还有的网站提供软删除,删除后仅仅是不显示而已,实际上数据还存在。具体应该如何做,都以你的喜好而定。总结本章实现了发表评论、展示评论的功能。像开头说的一样,本章的内容是前面所学章节的综合。如果你没有看本章代码,而是完全靠自己完成了评论功能,那么恭喜你获得了“Django入门程序员”的称号!不要小看“入门”两字,万事开头难嘛。有疑问请在杜赛的个人网站留言,我会尽快回复。或Email私信我:dusaiphoto@foxmail.com项目完整代码:Django_blog_tutorial转载请注明出处。 ...

January 2, 2019 · 2 min · jiezi

Django搭建个人博客:渲染Markdown文章目录

对会读书的人来说,读一本书要做的第一件事,就是仔细阅读这本书的目录。阅读目录可以对整体内容有所了解,并清楚地知道感兴趣的部分在哪里,提高阅读质量。博文也是同样的,好的目录对博主和读者都很有帮助。更进一步的是,还可以在目录中设置锚点,点击标题就立即前往该处,非常的方便。文中的目录之前我们已经为博文支持了Markdown语法,现在继续增强其功能。有折腾代码高亮的痛苦经历之后,设置Markdown的目录扩展就显得特别轻松了。修改文章详情视图:article/views.py…# 文章详情def article_detail(request, id): … article.body = markdown.markdown(article.body, extensions=[ ‘markdown.extensions.extra’, ‘markdown.extensions.codehilite’, # 目录扩展 ‘markdown.extensions.TOC’, ] ) …仅仅是将将markdown.extensions.TOC扩展添加了进去。TOC: Table of Contents,即目录的意思代码增加这一行就足够了。为了方便测试,往之前的文章中添加几个一级标题、二级标题等。还记得Markdown语法如何写标题吗?一级标题:# title1,二级标题:## title2然后你可以在文中的任何地方插入[TOC]字符串,目录就自动生成好了:点击标题,页面就立即前往相应的标题处(即“锚点”的概念)。任意位置的目录上面的方法只能将目录插入到文章当中。如果我想把目录插入到页面的任何一个位置呢?也简单,这次需要修改Markdown的渲染方法:article/views.py…def article_detail(request, id): … # 修改 Markdown 语法渲染 md = markdown.Markdown( extensions=[ ‘markdown.extensions.extra’, ‘markdown.extensions.codehilite’, ‘markdown.extensions.toc’, ] ) article.body = md.convert(article.body) # 新增了md.toc对象 context = { ‘article’: article, ’toc’: md.toc } return render(request, ‘article/detail.html’, context)为了能将toc单独提取出来,我们先将Markdown类赋值给一个临时变量md,然后用convert()方法将正文渲染为html页面。通过md.toc将目录传递给模板。注意markdown.markdown()和markdown.Markdown()的区别更详细的解释见:官方文档为了将新的目录渲染到页面中,需要修改文章详情模板:templates/article/detail.html…<div class=“container”> <div class=“row”> <!– 将原有内容嵌套进新的div中 –> <div class=“col-9”> <h1 class=“mt-4 mb-4”>{{ article.title }}</h1> <div class=“alert alert-success”> … </div> </div> <!– 新增的目录 –> <div class=“col-3 mt-4”> <h4><strong>目录</strong></h4> <hr> <div> {{ toc|safe }} </div> </div> </div></div>…重新布局,将原有内容装进col-9的容器中,将右侧col-3的空间留给目录toc需要|safe标签才能正确渲染重新打开页面:总结完成了文章的目录功能,至此文章详情页面也比较完善了。有疑问请在杜赛的个人网站留言,我会尽快回复。或Email私信我:dusaiphoto@foxmail.com项目完整代码:Django_blog_tutorial转载请注明出处。 ...

January 1, 2019 · 1 min · jiezi

Django搭建个人博客:简单搜索博客文章

不管是最新文章列表也好、最热文章列表也罢,都是把所有的文章数据全部展示给了用户。但是如果用户只关心某些特定类型的文章,抽取全部数据就显得既不方便、又不效率了。因此,给用户提供一个搜索功能,提供给用户感兴趣的几篇文章,就大有用处了。准备工作逻辑尽管细节不同,但是搜索和列表有很多类似的地方:它们都是先检索出一些文章对象,并将其展示给用户。上一章已经说过,代码重复是万恶之源,好的实践必须把功能类似的模块尽量复用起来。基于这个原则,我们打算继续在原有的article_list()上添砖加瓦,让其功能更加的强大。随着项目越来越庞大,又需要将功能复杂的模块拆分成更简单的多个模块。目前我们还不用担心这个问题。更酷的是,我们希望搜索出来的文章也能够按照时间、热度等各种方式进行排序。因此需要构造一个新的参数search,能够和之前的order参数进行联合查询。GET还是POST?用户搜索内容时提交的文本,可以用GET请求提交,也可以用POST请求提交。根据实际的需要进行选择。因为order是用GET提交的,并且翻页是GET请求,因此选择GET方式提交搜索文本,可以方便地和之前的模块结合起来。之前我们已经用过表单组件<form method=“POST”>,通过POST请求提交数据。表单组件同样也可以提交GET请求,只要去掉method=“POST"属性就可以了。Q对象Model.objects.all()能够返回表中的所有对象。对应的,Model.objects.filter(**kwargs)可以返回与给定参数匹配的部分对象。还有Model.objects.exclude(**kwargs)返回与给定参数不匹配的对象如果想对多个参数进行查询怎么办?比如同时查询文章标题和正文内容。这时候就需要Q对象。视图那么按照前面说好的,修改article_list():article/views.py…# 引入 Q 对象from django.db.models import Qdef article_list(request): search = request.GET.get(‘search’) order = request.GET.get(‘order’) # 用户搜索逻辑 if search: if order == ’total_views’: # 用 Q对象 进行联合搜索 article_list = ArticlePost.objects.filter( Q(title__icontains=search) | Q(body__icontains=search) ).order_by(’-total_views’) else: article_list = ArticlePost.objects.filter( Q(title__icontains=search) | Q(body__icontains=search) ) else: # 将 search 参数重置为空 search = ’’ if order == ’total_views’: article_list = ArticlePost.objects.all().order_by(’-total_views’) else: article_list = ArticlePost.objects.all() paginator = Paginator(article_list, 3) page = request.GET.get(‘page’) articles = paginator.get_page(page) # 增加 search 到 context context = { ‘articles’: articles, ‘order’: order, ‘search’: search } return render(request, ‘article/list.html’, context)…重点知识如下:新增参数search,存放需要搜索的文本。若search不为空,则检索特定文章对象。留意filter中Q对象的用法。Q(title__icontains=search)意思是在模型的title字段查询,icontains是不区分大小写的包含,中间用两个下划线隔开。search是需要查询的文本。多个Q对象用管道符|隔开,就达到了联合查询的目的。icontains不区分大小写,对应的contains区分大小写为什么需要search = ‘‘语句?如果用户没有搜索操作,则search = request.GET.get(‘search’)会使得search = None,而这个值传递到模板中会错误地转换成"None"字符串!等同于用户在搜索“None”关键字,这明显是错误的。完成本章内容后,可以删除此语句看看效果除此之外还有一点小的代码优化工作:将需要重复用到order = request.GET.get(‘order’)提取到顶部,让模块稍稍清爽一点。模板还是修改文章列表的模板文件。需要修改的内容稍多,仔细一些不要看错:templates/article/list.html…<div class=“container”> <!– 修改,面包屑的href增加search参数 –> <nav aria-label=“breadcrumb”> <ol class=“breadcrumb”> <li class=“breadcrumb-item”> <a href=”{% url ‘article:article_list’ %}?search={{ search }}"> 最新 </a> </li> <li class=“breadcrumb-item”> <a href="{% url ‘article:article_list’ %}?order=total_views&search={{ search }}"> 最热 </a> </li> </ol> </nav> <!– 新增,搜索栏 –> <div class=“row”> <div class=“col-auto mr-auto”> <form class=“form-inline” > <label class=“sr-only”>content</label> <input type=“text” class=“form-control mb-2 mr-sm-2” name=“search” placeholder=“搜索文章…” required > </form> </div> </div> <!– 新增,搜索提示语 –> {% if search %} {% if articles %} <h4><span style=“color: red”>"{{ search }}"</span>的搜索结果如下:</h4> <hr> {% else %} <h4>暂无<span style=“color: red”>"{{ search }}"</span>有关的文章。</h4> <hr> {% endif %} {% endif %} … <!– 修改,页码href增加search参数 –><a href="?page=1&order={{ order }}&search={{ search }}" class=“btn btn-success”>…<a href="?page={{ articles.previous_page_number }}&order={{ order }}&search={{ search }}" class=“btn btn-secondary”>…<a href="?page={{ articles.next_page_number }}&order={{ order }}&search={{ search }}" class=“btn btn-secondary”>…<a href="?page={{ articles.paginator.num_pages }}&order={{ order }}&search={{ search }}“class=“btn btn-success”>…面包屑组件、页码组件都改动了href:增加了search参数新增搜索栏,以GET请求提交search参数;required属性阻止用户提交空白文本新增搜索提示语。好的UI必须让用户了解当前的状态Emmm…想想也不用改动其他东西了。开始测试吧!测试还是打开文章列表页面:出现了搜索栏!并且翻页、最热等功能一切正常。在搜索栏中输入“PYTHON”,结果如下:成功将标题或正文中含有"python"关键字的文章检索出来了,并且是忽略大小写的。点击最热可以让检索结果按浏览量排序,翻页功能也正常工作。很好,达成了目标!学到这里的读者应该感到自豪:你用了同一个url,集成了很多种功能,展示了不同的内容!这对新手来说其实并不容易做到。这种方法有一个小缺点:有的时候url中会包含像search=’’(空值)这样无意义的字符串,强迫症简直不能忍。所幸这无伤大雅,通常用户并不会关心你的url是什么样子的,只要网页美观好用就行。总结本章完成了一个简单的搜索功能,这对于个人博客来说应该够用了。更加复杂、深度定制的搜索可以借助第三方模块,如Haystack。另外笔者这样实现搜索不一定是最优的。相信你已经掌握多种途径来实现搜索功能了(POST请求?搜索专用视图?另写url?),尽情尝试一番吧。有疑问请在杜赛的个人网站留言,我会尽快回复。或Email私信我:dusaiphoto@foxmail.com项目完整代码:Django_blog_tutorial转载请注明出处。 ...

December 31, 2018 · 2 min · jiezi

Python进阶:全面解读高级特性之切片!

导读:切片系列文章连续写了三篇,本文是对它们做的汇总。为什么要把序列文章合并呢?在此说明一下,本文绝不是简单地将它们做了合并,主要是修正了一些严重的错误(如自定义序列切片的部分),还对行文结构与章节衔接做了大量改动,如此一来,本文结构的完整性与内容的质量都得到了很好的保证。众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串、列表、元组…)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢?切片(slice)就是一种截取索引片段的技术,借助切片技术,我们可以十分灵活地处理序列类型的对象。通常来说,切片的作用就是截取序列对象,然而,对于非序列对象,我们是否有办法做到切片操作呢?在使用切片的过程中,有什么要点值得重视,又有什么底层原理值得关注呢?本文将主要跟大家一起来探讨这些内容,希望我能与你共同学习进步。1、切片的基础用法列表是 Python 中极为基础且重要的一种数据结构,也是最能发挥切片的用处的一种数据结构,所以在前两节,我将以列表为例介绍切片的一些常见用法。首先是切片的书写形式:[i : i+n : m] ;其中,i 是切片的起始索引值,为列表首位时可省略;i+n 是切片的结束位置,为列表末位时可省略;m 可以不提供,默认值是1,不允许为0 ,当m为负数时,列表翻转。注意:这些值都可以大于列表长度,不会报越界。切片的基本含义是:从序列的第i位索引起,向右取到后n位元素为止,按m间隔过滤 。li = [1, 4, 5, 6, 7, 9, 11, 14, 16]# 以下写法都可以表示整个列表,其中 X >= len(li)li[0:X] == li[0:] == li[:X] == li[:] == li[::] == li[-X:X] == li[-X:]li[1:5] == [4,5,6,7] # 从1起,取5-1位元素li[1:5:2] == [4,6] # 从1起,取5-1位元素,按2间隔过滤li[-1:] == [16] # 取倒数第一个元素li[-4:-2] == [9, 11] # 从倒数第四起,取-2-(-4)=2位元素li[:-2] == li[-len(li):-2] == [1,4,5,6,7,9,11] # 从头开始,取-2-(-len(li))=7位元素# 步长为负数时,列表先翻转,再截取li[::-1] == [16,14,11,9,7,6,5,4,1] # 翻转整个列表li[::-2] == [16,11,7,5,1] # 翻转整个列表,再按2间隔过滤li[:-5:-1] == [16,14,11,9] # 翻转整个列表,取-5-(-len(li))=4位元素li[:-5:-3] == [16,9] # 翻转整个列表,取-5-(-len(li))=4位元素,再按3间隔过滤# 切片的步长不可以为0li[::0] # 报错(ValueError: slice step cannot be zero)上述的某些例子对于初学者(甚至很多老手)来说,可能还不好理解,但是它们都离不开切片的基本语法,所以为方便起见,我将它们也归入基础用法中。对于这些样例,我个人总结出两条经验:(1)牢牢记住公式[i : i+n : m] ,当出现缺省值时,通过想象把公式补全;(2)索引为负且步长为正时,按倒数计算索引位置;索引为负且步长为负时,先翻转列表,再按倒数计算索引位置。2、切片的高级用法一般而言,切片操作的返回结果是一个新的独立的序列(PS:也有例外,参见《Python是否支持复制字符串呢?》)。以列表为例,列表切片后得到的还是一个列表,占用新的内存地址。当取出切片的结果时,它是一个独立对象,因此,可以将其用于赋值操作,也可以用于其它传递值的场景。但是,切片只是浅拷贝 ,它拷贝的是原列表中元素的引用,所以,当存在变长对象的元素时,新列表将受制于原列表。li = [1, 2, 3, 4]ls = li[::]li == ls # Trueid(li) == id(ls) # Falseli.append(li[2:4]) # [1, 2, 3, 4, [3, 4]]ls.extend(ls[2:4]) # [1, 2, 3, 4, 3, 4]# 下例等价于判断li长度是否大于8if(li[8:]): print(“not empty”)else: print(“empty”)# 切片列表受制于原列表lo = [1,[1,1],2,3]lp = lo[:2] # [1, [1, 1]]lo[1].append(1) # [1, [1, 1, 1], 2, 3]lp # [1, [1, 1, 1]]由于可见,将切片结果取出,它可以作为独立对象使用,但是也要注意,是否取出了变长对象的元素。切片既可以作为独立对象被“取出”原序列,也可以留在原序列,作为一种占位符使用。不久前,我介绍了几种拼接字符串的方法(链接见文末),其中三种格式化类的拼接方法(即 %、format()、template)就是使用了占位符的思想。对于列表来说,使用切片作为占位符,同样能够实现拼接列表的效果。特别需要注意的是,给切片赋值的必须是可迭代对象。li = [1, 2, 3, 4]# 在头部拼接li[:0] = [0] # [0, 1, 2, 3, 4]# 在末尾拼接li[len(li):] = [5,7] # [0, 1, 2, 3, 4, 5, 7]# 在中部拼接li[6:6] = [6] # [0, 1, 2, 3, 4, 5, 6, 7]# 给切片赋值的必须是可迭代对象li[-1:-1] = 6 # (报错,TypeError: can only assign an iterable)li[:0] = (9,) # [9, 0, 1, 2, 3, 4, 5, 6, 7]li[:0] = range(3) # [0, 1, 2, 9, 0, 1, 2, 3, 4, 5, 6, 7]上述例子中,若将切片作为独立对象取出,那你会发现它们都是空列表,即 li[:0]==li[len(li):]==li[6:6]==[] ,我将这种占位符称为“纯占位符”,对纯占位符赋值,并不会破坏原有的元素,只会在特定的索引位置中拼接进新的元素。删除纯占位符时,也不会影响列表中的元素。与“纯占位符”相对应,“非纯占位符”的切片是非空列表,对它进行操作(赋值与删除),将会影响原始列表。如果说纯占位符可以实现列表的拼接,那么,非纯占位符可以实现列表的替换。li = [1, 2, 3, 4]# 不同位置的替换li[:3] = [7,8,9] # [7, 8, 9, 4]li[3:] = [5,6,7] # [7, 8, 9, 5, 6, 7]li[2:4] = [‘a’,‘b’] # [7, 8, ‘a’, ‘b’, 6, 7]# 非等长替换li[2:4] = [1,2,3,4] # [7, 8, 1, 2, 3, 4, 6, 7]li[2:6] = [‘a’] # [7, 8, ‘a’, 6, 7]# 删除元素del li[2:3] # [7, 8, 6, 7]切片占位符可以带步长,从而实现连续跨越性的替换或删除效果。需要注意的是,这种用法只支持等长替换。li = [1, 2, 3, 4, 5, 6]li[::2] = [‘a’,‘b’,‘c’] # [‘a’, 2, ‘b’, 4, ‘c’, 6]li[::2] = [0]*3 # [0, 2, 0, 4, 0, 6]li[::2] = [‘w’] # 报错,attempt to assign sequence of size 1 to extended slice of size 3del li[::2] # [2, 4, 6]3、自定义对象实现切片功能切片是 Python 中最迷人最强大最 Amazing 的语言特性(几乎没有之一),以上两小节虽然介绍了切片的基础用法与高级用法,但这些还不足以充分地展露切片的魅力,所以,在接下来的两章节中,我们将聚焦于它的更高级用法。前两节内容都是基于原生的序列类型(如字符串、列表、元组……),那么,我们是否可以定义自己的序列类型并让它支持切片语法呢?更进一步,我们是否可以自定义其它对象(如字典)并让它支持切片呢?3.1、魔术方法:getitem()想要使自定义对象支持切片语法并不难,只需要在定义类的时候给它实现魔术方法 getitem() 即可。所以,这里就先介绍一下这个方法。语法: object.getitem(self, key)官方文档释义:Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the getitem() method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.概括翻译一下:getitem() 方法用于返回参数 key 所对应的值,这个 key 可以是整型数值和切片对象,并且支持负数索引;如果 key 不是以上两种类型,就会抛 TypeError;如果索引越界,会抛 IndexError ;如果定义的是映射类型,当 key 参数不是其对象的键值时,则会抛 KeyError 。3.2、自定义序列实现切片功能接下来,我们定义一个简单的 MyList ,并给它加上切片功能。(PS:仅作演示,不保证其它功能的完备性)。import numbersclass MyList(): def init(self, anylist): self.data = anylist def len(self): return len(self.data) def getitem(self, index): print(“key is : " + str(index)) cls = type(self) if isinstance(index, slice): print(“data is : " + str(self.data[index])) return cls(self.data[index]) elif isinstance(index, numbers.Integral): return self.data[index] else: msg = “{cls.name} indices must be integers” raise TypeError(msg.format(cls=cls))l = MyList([“My”, “name”, “is”, “Python猫”])### 输出结果:key is : 3Python猫key is : slice(None, 2, None)data is : [‘My’, ’name’]<main.MyList object at 0x0000019CD83A7A90>key is : hiTraceback (most recent call last):…TypeError: MyList indices must be integers or slices从输出结果来看,自定义的 MyList 既支持按索引查找,也支持切片操作,这正是我们的目的。3.3、自定义字典实现切片功能切片是序列类型的特性,所以在上例中,我们不需要写切片的具体实现逻辑。但是,对于其它非序列类型的自定义对象,就得自己实现切片逻辑。以自定义字典为例(PS:仅作演示,不保证其它功能的完备性):class MyDict(): def init(self): self.data = {} def len(self): return len(self.data) def append(self, item): self.data[len(self)] = item def getitem(self, key): if isinstance(key, int): return self.data[key] if isinstance(key, slice): slicedkeys = list(self.data.keys())[key] return {k: self.data[k] for k in slicedkeys} else: raise TypeErrord = MyDict()d.append(“My”)d.append(“name”)d.append(“is”)d.append(“Python猫”)print(d[2])print(d[:2])print(d[-4:-2])print(d[‘hi’])### 输出结果:is{0: ‘My’, 1: ’name’}{0: ‘My’, 1: ’name’}Traceback (most recent call last):…TypeError上例的关键点在于将字典的键值取出,并对键值的列表做切片处理,其妙处在于,不用担心索引越界和负数索引,将字典切片转换成了字典键值的切片,最终实现目的。4、迭代器实现切片功能好了,介绍完一般的自定义对象如何实现切片功能,这里将迎来另一类非同一般的对象。迭代器是 Python 中独特的一种高级对象,它本身不具备切片功能,然而若能将它用于切片,这便仿佛是锦上添花,能达到如虎添翼的效果。所以,本节将隆重地介绍迭代器如何实现切片功能。4.1、迭代与迭代器首先,有几个基本概念要澄清:迭代、可迭代对象、迭代器。迭代 是一种遍历容器类型对象(例如字符串、列表、字典等等)的方式,例如,我们说迭代一个字符串“abc”,指的就是从左往右依次地、逐个地取出它的全部字符的过程。(PS:汉语中迭代一词有循环反复、层层递进的意思,但 Python 中此词要理解成单向水平线性 的,如果你不熟悉它,我建议直接将其理解为遍历。)那么,怎么写出迭代操作的指令呢?最通用的书写语法就是 for 循环。# for循环实现迭代过程for char in “abc”: print(char, end=” “)# 输出结果:a b cfor 循环可以实现迭代的过程,但是,并非所有对象都可以用于 for 循环,例如,上例中若将字符串“abc”换成任意整型数字,则会报错: ‘int’ object is not iterable .这句报错中的单词“iterable”指的是“可迭代的”,即 int 类型不是可迭代的。而字符串(string)类型是可迭代的,同样地,列表、元组、字典等类型,都是可迭代的。那怎么判断一个对象是否可迭代呢?为什么它们是可迭代的呢?怎么让一个对象可迭代呢?要使一个对象可迭代,就要实现可迭代协议,即需要实现__iter__() 魔术方法,换言之,只要实现了这个魔术方法的对象都是可迭代对象。那怎么判断一个对象是否实现了这个方法呢?除了上述的 for 循环外,我还知道四种方法:# 方法1:dir()查看__iter__dir(2) # 没有,略dir(“abc”) # 有,略# 方法2:isinstance()判断import collectionsisinstance(2, collections.Iterable) # Falseisinstance(“abc”, collections.Iterable) # True# 方法3:hasattr()判断hasattr(2,”iter”) # Falsehasattr(“abc”,"iter") # True# 方法4:用iter()查看是否报错iter(2) # 报错:‘int’ object is not iterableiter(“abc”) # <str_iterator at 0x1e2396d8f28>### PS:判断是否可迭代,还可以查看是否实现__getitem__,为方便描述,本文从略。这几种方法中最值得一提的是 iter() 方法,它是 Python 的内置方法,其作用是将可迭代对象变成迭代器 。这句话可以解析出两层意思:(1)可迭代对象跟迭代器是两种东西;(2)可迭代对象能变成迭代器。实际上,迭代器必然是可迭代对象,但可迭代对象不一定是迭代器。两者有多大的区别呢?如上图蓝圈所示,普通可迭代对象与迭代器的最关键区别可概括为:一同两不同 ,所谓“一同”,即两者都是可迭代的(iter),所谓“两不同”,即可迭代对象在转化为迭代器后,它会丢失一些属性(getitem),同时也增加一些属性(next)。首先看看增加的属性 next , 它是迭代器之所以是迭代器的关键,事实上,我们正是把同时实现了 iter 方法 和 next 方法的对象定义为迭代器的。有了多出来的这个属性,可迭代对象不需要借助外部的 for 循环语法,就能实现自我的迭代/遍历过程。我发明了两个概念来描述这两种遍历过程(PS:为了易理解,这里称遍历,实际也可称为迭代):它遍历 指的是通过外部语法而实现的遍历,自遍历 指的是通过自身方法实现的遍历。借助这两个概念,我们说,可迭代对象就是能被“它遍历”的对象,而迭代器是在此基础上,还能做到“自遍历”的对象。ob1 = “abc"ob2 = iter(“abc”)ob3 = iter(“abc”)# ob1它遍历for i in ob1: print(i, end = " “) # a b cfor i in ob1: print(i, end = " “) # a b c# ob1自遍历ob1.next() # 报错: ‘str’ object has no attribute ‘next’# ob2它遍历for i in ob2: print(i, end = " “) # a b c for i in ob2: print(i, end = " “) # 无输出# ob2自遍历ob2.next() # 报错:StopIteration# ob3自遍历ob3.next() # aob3.next() # bob3.next() # cob3.next() # 报错:StopIteration通过上述例子可看出,迭代器的优势在于支持自遍历,同时,它的特点是单向非循环的,一旦完成遍历,再次调用就会报错。对此,我想到一个比方:普通可迭代对象就像是子弹匣,它遍历就是取出子弹,在完成操作后又装回去,所以可以反复遍历(即多次调用for循环,返回相同结果);而迭代器就像是装载了子弹匣且不可拆卸的枪,进行它遍历或者自遍历都是发射子弹,这是消耗性的遍历,是无法复用的(即遍历会有尽头)。写了这么多,稍微小结一下:迭代是一种遍历元素的方式,按照实现方式划分,有外部迭代与内部迭代两种,支持外部迭代(它遍历)的对象就是可迭代对象,而同时还支持内部迭代(自遍历)的对象就是迭代器;按照消费方式划分,可分为复用型迭代与一次性迭代,普通可迭代对象是复用型的,而迭代器是一次性的。4.2、迭代器切片前面提到了“一同两不同”,最后的不同是,普通可迭代对象在转化成迭代器的过程中会丢失一些属性,其中关键的属性是 getitem 。在前一节中,我已经介绍了这个魔术方法,并用它实现了自定义对象的切片特性。那么问题来了:为什么迭代器不继承这个属性呢?首先,迭代器使用的是消耗型的遍历,这意味着它充满不确定性,即其长度与索引键值对是动态衰减的,所以很难 get 到它的 item ,也就不再需要 getitem 属性了。其次,若强行给迭代器加上这个属性,这并不合理,正所谓强扭的瓜不甜……由此,新的问题来了:既然会丢失这么重要的属性(还包括其它未标识的属性),为什么还要使用迭代器呢?这个问题的答案在于,迭代器拥有不可替代的强大的有用的功能,使得 Python 要如此设计它。限于篇幅,此处不再展开,后续我会专门填坑此话题。还没完,死缠烂打的问题来了:能否令迭代器拥有这个属性呢,即令迭代器继续支持切片呢?hi = “欢迎关注公众号:Python猫"it = iter(hi)# 普通切片hi[-7:] # Python猫# 反例:迭代器切片it[-7:] # 报错:‘str_iterator’ object is not subscriptable迭代器因为缺少__getitem__ ,因此不能使用普通的切片语法。想要实现切片,无非两种思路:一是自己造轮子,写实现的逻辑;二是找到封装好的轮子。Python 的 itertools 模块就是我们要找的轮子,用它提供的方法可轻松实现迭代器切片。import itertools# 例1:简易迭代器s = iter(“123456789”)for x in itertools.islice(s, 2, 6): print(x, end = " “) # 输出:3 4 5 6for x in itertools.islice(s, 2, 6): print(x, end = " “) # 输出:9# 例2:斐波那契数列迭代器class Fib(): def init(self): self.a, self.b = 1, 1 def iter(self): while True: yield self.a self.a, self.b = self.b, self.a + self.bf = iter(Fib())for x in itertools.islice(f, 2, 6): print(x, end = " “) # 输出:2 3 5 8for x in itertools.islice(f, 2, 6): print(x, end = " “) # 输出:34 55 89 144itertools 模块的 islice() 方法将迭代器与切片完美结合,终于回答了前面的问题。然而,迭代器切片跟普通切片相比,前者有很多局限性。首先,这个方法不是“纯函数”(纯函数需遵守“相同输入得到相同输出”的原则);其次,它只支持正向切片,且不支持负数索引,这都是由迭代器的损耗性所决定的。那么,我不禁要问:itertools 模块的切片方法用了什么实现逻辑呢?下方是官网提供的源码:def islice(iterable, *args): # islice(‘ABCDEFG’, 2) –> A B # islice(‘ABCDEFG’, 2, 4) –> C D # islice(‘ABCDEFG’, 2, None) –> C D E F G # islice(‘ABCDEFG’, 0, None, 2) –> A C E G s = slice(*args) # 索引区间是[0,sys.maxsize],默认步长是1 start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1 it = iter(range(start, stop, step)) try: nexti = next(it) except StopIteration: # Consume iterable up to the start position. for i, element in zip(range(start), iterable): pass return try: for i, element in enumerate(iterable): if i == nexti: yield element nexti = next(it) except StopIteration: # Consume to stop. for i, element in zip(range(i + 1, stop), iterable): passislice() 方法的索引方向是受限的,但它也提供了一种可能性:即允许你对一个无穷的(在系统支持范围内)迭代器进行切片的能力。这是迭代器切片最具想象力的用途场景。除此之外,迭代器切片还有一个很实在的应用场景:读取文件对象中给定行数范围的数据。我们知道,从文件中读取内容主要有两种方法(参见之前关于文件读写的文章):read() 适合读取内容较少的情况,或者是需要一次性处理全部内容的情况;而 readlines() 适用性更广,因为它是迭代地读取内容,既减少内存压力,又方便逐行对数据处理。虽然 readlines() 有迭代读取的优势,但它是从头到尾逐行读取,若文件有几千行,而我们只想要读取少数特定行(例如第1000-1009行),那它还是效率太低了。考虑到文件对象天然就是迭代器 ,我们可以使用迭代器切片先行截取,然后再处理,如此效率将大大地提升。# test.txt 文件内容’‘‘猫Python猫python is a cat.this is the end.‘‘‘from itertools import islicewith open(’test.txt’,‘r’,encoding=‘utf-8’) as f: print(hasattr(f, “next”)) # 判断是否迭代器 content = islice(f, 2, 4) for line in content: print(line.strip())### 输出结果:Truepython is a cat.this is the end.本节内容较多,简单回顾一下:迭代器是一种特殊的可迭代对象,可用于它遍历与自遍历,但遍历过程是损耗型的,不具备循环复用性,因此,迭代器本身不支持切片操作;通过借助 itertools 模块,我们能实现迭代器切片,将两者的优势相结合,其主要用途在于截取大型迭代器(如无限数列、超大文件等等)的片段,实现精准的处理,从而大大地提升性能与效率。5、小结最后总结一下,切片是 Python 的一种高级特性,常用于截取序列类型的元素,但并不局限于此,本文主要介绍了它的基础用法、高级用法(如占位符用法)、自定义对象切片、以及迭代器切片等使用内容。除此之外,切片还有更广阔多样的使用场景,例如 Numpy 的多维切片、内存视图切片、异步迭代器切片等等,都值得我们去探索一番,今限于篇幅而无法细说,欢迎关注公众号“Python猫 ”,以后我们慢慢学习之。切片系列(原单篇): Python进阶:切片的误区与高级用法Python进阶:自定义对象实现切片功能Python进阶:迭代器与迭代器切片相关链接: 官方文档getitem用法:http://t.cn/EbzoZyp切片赋值的源码分析:http://t.cn/EbzSaoZ官网itertools模块介绍:http://t.cn/EbNc0otPython是否支持复制字符串呢?来自Kenneth Reitz大神的建议:避免不必要的面向对象编程给Python学习者的文件读写指南(含基础与进阶,建议收藏)详解Python拼接字符串的七种方式—————–本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。 ...

December 31, 2018 · 5 min · jiezi

Django搭建个人博客:根据浏览量对最热文章排序

有了浏览量之后,文章受欢迎的程度就有了评价标准。随之而来的就有根据浏览量对文章进行排序的需求,即显示“最热文章”。现在你已经很熟悉MTV模式,不需要我啰嗦也能完成任务:文章的模型已经有了,不需要写Model了写一个视图函数article_list_by_views(),取出按浏览排序后的文章对象将文章对象传递到模板,并进行渲染很简单,但也隐藏着问题:最热文章列表和之前的普通文章列表相比,大部分功能其实都是相同的,仅仅是排序不同而已。万一哪天需要根据文章标题排序呢?万一还需要用户id排序、标签排序、收藏排序…不仅如此,就连路由urls.py都要跟着膨胀。代码会越来越臃肿且不可维护。重复的代码是万恶之源。因此这里挑战一下,不创建新的视图/路由,而是将排序功能融合到已有的视图/路由中。视图根据以上需求,重写article_list():article/views.py…# 重写文章列表def article_list(request): # 根据GET请求中查询条件 # 返回不同排序的对象数组 if request.GET.get(‘order’) == ’total_views’: article_list = ArticlePost.objects.all().order_by(’-total_views’) order = ’total_views’ else: article_list = ArticlePost.objects.all() order = ’normal’ paginator = Paginator(article_list, 3) page = request.GET.get(‘page’) articles = paginator.get_page(page) # 修改此行 context = { ‘articles’: articles, ‘order’: order } return render(request, ‘article/list.html’, context)重点知识如下:前面用过GET请求传递单个参数。它也是可以传递多个参数的,如?a=1&b=2,参数间用&隔开视图根据GET参数order的值,判断取出的文章如何排序order_by()方法指定对象如何进行排序。模型中有total_views这个整数字段,因此‘total_views’为正序,‘-total_views’为逆序为什么把新变量order也传递到模板中?因为文章需要翻页!order给模板一个标识,提醒模板下一页应该如何排序这样一来,排序所需要的参数都可以通过查询获得,连urls.py都不用改写了。模板接下来修改文章列表模板:优化入口,并且正确分页:templates/article/list.html…<div class=“container”> <nav aria-label=“breadcrumb”> <ol class=“breadcrumb”> <li class=“breadcrumb-item”> <a href="{% url ‘article:article_list’ %}"> 最新 </a> </li> <li class=“breadcrumb-item”> <a href="{% url ‘article:article_list’ %}?order=total_views"> 最热 </a> </li> </ol> </nav> <div class=“row mt-2”> {% for article in articles %} … {% endfor %} </div><!– 页码导航 –>…<a href="?page=1&order={{ order }}" class=“btn btn-success”>&laquo; 1</a>…<a href="?page={{ articles.previous_page_number }}&order={{ order }}" class=“btn btn-secondary”>…</a>… {% if articles.has_next %}<a href="?page={{ articles.next_page_number }}&order={{ order }}" class=“btn btn-secondary”>{{ articles.next_page_number }}</a>…<a href="?page={{ articles.paginator.num_pages }}&order={{ order }}" class=“btn btn-success”>{{ articles.paginator.num_pages }} &raquo;</a>…新增了Bootstrap中的面包屑导航样式breadcrumb页码导航中,所有的分页链接都新增了order参数测试启动服务器,点击“最热”:工作得很好!切换页码,留意地址栏中是如何变化的。还剩一个小瑕疵:用户点击“最热”按钮后,此按钮最好能够变为灰色,并且不可点击。这个精益求精的机会就留给读者去优化吧。header.html中有一个小改动:“写文章"的入口被挪到用户下拉菜单中了。总结本章已经摸到一个高级的编程领域门槛了:代码复用。将类似功能的代码合并到了一起,并且让后续的功能扩展变得很容易。只需要在视图中写几个elif语句就搞定了。在读者以后的编程中,也要尽量优化代码结构,达到事半功倍的效果。至此,博客虽小,功能却相当完整了。继续努力!有疑问请在杜赛的个人网站留言,我会尽快回复。或Email私信我:dusaiphoto@foxmail.com项目完整代码:Django_blog_tutorial转载请注明出处。 ...

December 30, 2018 · 1 min · jiezi

TOP100summit分享实录 | 数字化三支柱:企业数字化转型的众妙之门

喜茶凭借「喜茶GO」小程序跻身第七届全球软件案例研究峰会(简称:TOP100summit),为100个技术案例中唯一的非科技企业和唯一的零售餐饮企业。本文为喜茶CTO陈霈霖在TOP100summit上的演讲实录,分享者陈霈霖曾任职金山软件,目前为喜茶互联网事业部负责人、喜茶GO产品负责人,在喜茶负责数字化战略落地,从0到1打造喜茶的数字化体系。编者按:2018年11月30日-12月3日,第七届全球软件案例研究峰会在北京国家会议中心盛大开幕,现场解读2018年「壹佰案例榜单」。本文为喜茶CTO、喜茶互联网事业部总经理陈霈霖老师分享的《数字化三支柱:传统企业数字化转型的众妙之门》案例实录。今天到场的大部分人都来自于各大国内外科技公司,有公司负责人、技术负责人、产品负责人,这个会场还有很多的互联网产品经理,你们肯定都是抱着学习的心态过来的,看到我估计会感觉到挺崩溃,你一个卖奶茶跑来这干嘛。上午一家SaaS科技公司的嘉宾分享的一个观点特别棒,他说,很多商家对于互联网和数字化其实是搞不懂,商家就是韭菜,随波逐流。我觉得“韭菜”这个词用得好,比较能概括我今天所讲的内容,刚好我也可以自我介绍一下。我是喜茶CTO陈霈霖,我今天要讲一个韭菜的故事。这几年有一种趋势,很多传统行业都想往互联网和数字化进行转型。在座的各位说不定以后也有一些的职业机会,从一个互联网行业跳槽去到一个传统行业。不妨自我想象一下,假如有一天到了一家传统企业,比如一个餐厅、一个制造业,让你去担任这个公司的IT数字化负责人,你会打算重点做哪方面的工作?我这里给两个方向的选择,一是IT管理,二是市场营销。你是选一,还是选二?这看上去不就一句废话吗,既然是IT负责人,当然是搞IT管理。但我想说的是,如果你选择二,恭喜你,你有潜力成为一个更出色的IT数字化负责人。为什么?这是我将会涉及的主题。喜茶目前是新茶饮品牌领导者,进入公众视野往往是从1个关键词开始的,“排队”,大量的新闻报道都围绕着“排队”进行讨论甚至指责。让我们回到半年前,喜茶GO小程序发布,我们研发的喜茶GO小程序,集堂食、预约、外卖功能于一身,消费者再也不用排起长长的队伍去买一杯喜茶了,而喜茶跟互联网结合的故事,就从这里开始。数字化转型最大障碍是数字化认知你肯定听都过这些词语:新零售、大数据、智慧零售、产业互联网、云计算……说到底这些词语他们本质都在说一件事情,那就是——传统企业数字化转型。你看,就一个事情,居然这么一大堆相似的名词丢出来,广大的传统企业主们当然会非常迷惑,但企业主们又看到互联网公司正在从各个方向一步一步地侵入到各个行业和领域,这自然而然就会产生一种“数字化生存焦虑”。很多企业主会想,虽然我看不懂这个事情,但是感觉这个事情还是挺重要的,我总要找出一条属于自己企业的数字化发展之路。但大家都知道,IT投入是很花钱的,需要大量投入,企业主们又没看懂这个事情,他们无法衡量投入和产出之比。既然自己看不懂,那就招个人呗,然后他们又会进入了另一个迷惑,应该招什么人?CTO?CIO?CDO?还是C什么O?更关键的是,把他们招过来之后,究竟要开始哪方面的工作?对于这种情况,我的观点是——企业数字化转型,是一个“一把手工程”,不是招一个人就能解决的,要想更好地数字化落地,甚至所有的员工都拥有“数字化认知”。怎么理解什么是“数字化认知”?我举个场景。每次回到老家,面对家里老人家,经常会被问到的问题就是,霈霖,你做的是什么工作啊?你怎么回答?敲代码?画原型?你会发现你根本就无法解释清楚。总之,他们都会困惑你怎么每天盯着电脑就把钱给赚了。但有一件事,他们总会喜欢找你的,那就是:当他们的电脑或者手机使用不正常的时候,找到你求救。起初我是不耐烦的,后来我也释怀了,找到了回答这类问题的绝招。我会回答说:“我在雷军那公司工作啊”。因为雷军还是金山的董事长,老人家们会经常看珠海的报纸读过雷军的辉煌故事,听到这里也会顺势一个回答:“哦,挺好挺好,厉害厉害”。这也是数字化转型的现实情况,缺乏数字化认知,你最多能做到的,就只能是不明觉厉。喜茶GO诞生于思想迭代虽然喜茶GO从表面上是一个“小”程序,但它却承载着我们的数字化战略。喜茶GO的背后意味着什么?究竟为什么要数字化转型呢?这一切都围绕着一个核心的思维模型,那就是——数字化三支柱。在我讲述数字化三支柱之前,我们不妨先来看看喜茶GO诞生的故事。就像我刚开始问的那个问题,如果你从互联网企业到一家传统企业工作,你会选择做IT管理呢,还是做市场营销呢。像我刚开始,就是非常直白的闯进了IT管理领域的工作,每天都在研究市面上的各种POS、ERP、OA等的系统。后来又开始研究某跨国咖啡企业的会员系统。又过了一段时间,开始着手想有什么方法去解决排队问题,才想到去做一个小程序。但要注意的是,当初的这个“想解决排队问题的小程序”,充其量只是一个电子菜单。它的最初想法是这样的:消费者通过小程序先选饮品,然后继续排队,把手机小程序选好的饮品展示给收银员看。后面有经历过各种各样的思想碰撞,直到今年年初的时候我们才开始组建团队,基于数字化三支柱把喜茶GO小程序做出来。可见,所有今天的一切,都是思想的迭代,它不是一蹴而就的结果,可以说数字化三支柱模型是实践出真知。餐饮行业为什么要做数字化转型为了让在座科技行业的同学们理解一下数字化转型中数字化三支柱的意义,我先讲4个故事,让大家了解一下餐饮行业为什么要做数字化。第一个故事是「边际成本递增效应」。边际成本指的是每多生产或者多卖一件产品,所带来的总成本的增加。如果你开发的是一个互联网产品,你会知道,它理论上可以覆盖全中国甚至全世界,虽然前期有一定的投入,但后续的运营,不管你是有1亿、2亿..10亿用户,你的成本不会随之等比激增,“边际成本”会不断递减。但线下开直营门店,则完全不一样。你开一家店,那就只管理一家店。但随着店数的增加,你开始管不过来了,比如我现在在北京,我就完全不会知道深圳的门店在发生什么事情,你需要额外投入更多的人力和物力去进行管理监督。同一时间,你的销量越高,需要的店员就越多,成本就越高。与互联网产品相比,这是一种管理“边际成本递增”。像我们办公室加门店,至今已拥有几千名员工,要知道几十人的互联网公司已经可被称为中等规模的了,几千名员工要是换作一个互联网企业,那可是妥妥的大公司。第二个故事是「国际上的传统企业」。很多人会把传统企业理解成开线下店的企业。那以国外开线下店的一个所谓的传统企业沃尔玛为例,有两个事件:1.沃尔玛在80年代曾发射过多颗人造卫星,目的是通过卫星网络,让全球门店的商品数据进行流通。2.美国最著名的电商公司叫亚马逊,亚马逊创业的时候,去了哪家互联网公司找CTO?亚马逊的第一任CTO,是从沃尔玛挖来的;可见,那些我们看起来的国外“传统企业”,技术实力相当雄厚,本质其实就是一家科技公司,是IT技术驱动他们把店开遍全球。我们却简单的认为别人也是“传统企业”,这也侧面的反映了观念之差距,国内连锁经营对IT的重视还处于萌芽阶段。所以,餐饮行业有句话是“1家赚,10家毙”。第三个故事叫「不知道你是谁」。在座如果有做电商的同学,你们会知道“复购率”、“消费者年龄段”这些都是十分基础的用户信息。那回忆一下你们平日自己吃饭,你把服务员喊过来,点个餐,吃顿饱,你拍拍屁股就跑了,你没有留下任何个人信息,店家完全不知道你是谁,更不用说什么消费者年龄段、复购率,什么数据都没有。所以如果你遇到一些普通的餐饮店,他们跟你说自己公司的复购率是多少、消费者年龄段是多少,你笑笑就可以了,多数是瞎猜的。第四个故事叫「挨踢」。像我,之前每天接收最多的呼唤就是,“你好我的电脑坏啦”、“你好陈工,我的U盘拔不出来啦”、“能飞过来看看我的电脑怎么打不开了吗?”等等,大部分人对IT人员的理解还停留在修电脑阶段。因为我们这还有互联网产品经理嘛,也有些女生,她们会被问到,“哟你一个女生长得也挺好看的你怎么搞IT啊?”、“女生也会敲代码啊?”。我还试过一次,朋友拉群拉来一个同行业的市场负责人,想聊一次活动合作,对方直接来了一句“你怎么拉一个负责修电脑的让我认识”。这些都是传统企业对IT人员的刻板印象,觉得都是一群形象邋遢的技术男。我总结一下传统企业的现状。管理上,因不重视IT管理,直营连锁企业遍及存在管理成本递增效应;营销上,缺乏用户触达能力,不知道用户是谁;认知上,从业人员普遍缺乏数字化认知和IT偏见。所以,我经常跟人说:喜茶GO绝不仅仅是一个小程序,还是一个贯穿企业经营管理的数字化引擎,无奈更多时候这都是一厢情愿的呻吟,大部分人还很难理解,因为确实能看得到的,就是一个小程序,更可恨的是微信还把它叫“小”程序,搞得大家都以为它是个“小”东西。怎样把数字化转型的重要性讲清楚?这就需要“数字化三支柱”了。数字化三支柱,由三部分组成的,分别是——数字管理、数字营销、数字力量。什么是数字管理什么是数字管理?不妨看看传统企业IT负责人的日常:修电脑、修手机、修台式机,还有落地各种各样的ERP、OA、POS等等等各种各样英文缩写的系统。抛开IT维护部分,我们看看那些系统,抽象一下,你会发现,他们都有一个特点就是——它们都是辅助管理的工具。想想看,ERP辅助的是供应链部门、财务部门的管理工作,OA辅助行政部门的管理工作,EHR则辅助人力资源部门的管理工作等等,利用数字化进行管理。看起来很正常是不是,那我们落实到场景:一个数字管理系统的落地,一般会有两波力量,一个是IT部门,一个是业务部门。需求的博弈作为一个IT部门,它自然会收到各路英豪的需求,像营运部、人力资源部、物流部、市场部、财务部等等……IT部门接到需求后,对于这些繁花似锦的业务需求,一般也会有个标准答案:你说怎样就怎样咯。然后屁颠屁颠地开始到市场寻找各种各样的IT系统,经历无数个日夜的奋斗、努力、激情岁月之后,数字管理系统终于上线了。业务部门在满怀兴奋地使用系统后,一般也会有个标准答案:这不是我想要的!最后,这锅谁接?对,你们都想到,当然是IT部门,不然怎么“挨踢”。冷静,我们分析一下,这里面究竟为什么会这样?本质其实就是:业务的不懂IT,IT的不懂业务。好吧,用互联网人的话来说就是,这里缺少了懂业务的产品经理。所以我今天来到这个演讲的会场我感到异常的亲切。亲切在哪里?我的天啊,这里居然分成这么多模块,产品创新、组织发展、项目管理、人工智能、技术架构、开发运维、质量测试,太亲切了,要知道,这些所有的东西,在大部分传统企业内都被统称为两个英文字母——IT。面对各种各样的业务部门需求,还有同事会抱怨现有的数字管理系统不够好用,而且采购价格也挺贵的,要不自己开发一套?作为产品经理的我,我开始掌握一套正确回答方法:产品经理是不是你? …(开始解释什么是产品经理)…好,假设产品经理我来当;研发人员是不是你招?…(解释互联网产品研发流程)…好,假设我来管;MBA课程可以报销不?…这个根本就不是技术问题,是人力资源、组织管理、物流管理、财务会计…………..我得先去读个MBA。可是,问题来了,是不是真的是这些数字管理系统不够好用?各大科技软件企业,就真的那么差劲吗?管理的惯性如前所说,数字管理,就是通过各种各样的IT数字化系统来辅助业务部门管理它们的业务。可是,管理是啥?——管理很多时候,其实是一堆约定俗成、共识、习惯。几乎每一个人去当管理者,都会有彻底不同的管理方式。我们回望一下中华几千年历史,可以把以往的这些管理方式归纳为——P2P管理,从人到人的管理。这个很好理解,比如你喊一句,“小明你帮我倒杯水”,小明就去帮你倒杯水过来,这就是人到人的管理。但是,近年来,出现了一种完全不一样的管理方式。如果你是负责互联网产品研发的,你会对这个场景似曾相识:产品经理小红兴冲冲地跑到程序员小黑面前,提出一个XXX的功能修改,小黑仔细地听后,十分感动,然后回了一句,“你建个任务单”吧。大部分产品研发里都会用到「项目管理软件」,产品经理通过项目管理工具录入任务单,工程师们见任务单做事,这就是P2M2P管理,从人到机再到人的管理,通过中间的一个项目管理软件进行传达。推而广之,你会发现,OA、ERP、POS,其实都是一种P2M2P管理。我把这理解成一种「管理交互方式」的改变,它导致了以往人到人管理模式下的约定俗成、共识、习惯,全面崩溃。大部分业务方提出的需求,都是基于人到人管理交互方式,把自己「约定俗成」套入「特定流程」的数字管理系统,在这一堆「约定俗成」中,只要有那么一点点无法套入,数字管理系统就会背上“不好用”的骂名。举个例子帮助大家理解,比如古代的交通工具是马,人们想跑得更快,怎么办呢?——当然是更好的马了!刚开始是加强马的训练,后来演变成帮助马变得更好,开始出现马鞍、护腿、脚蹬……然后有一天,汽车被发明了,却被骑马人耻笑,这台汽车的后备箱怎么连一匹马都放不下?IT的唯一量化是钱刚刚讲的这些IT实施过程的各种委屈,我觉得其实都不是最糟糕的,最最最终最糟糕的是——IT所做的所有事情都是不可量化。你说做一个OA,帮助公司大力地提升了效率,好吧,提升了多少?算给我看啊。算不出吧?那总得有个标准啊。对于IT来说,唯一能够量化的东西只有一个点,叫——IT支出成本。成本是什么?就是钱啊。所以,大部分的IT部门到最后就只会沦落成挨别人踢的部门。对此,我觉得,以当前社会数字化认知程度,传统企业做IT数字管理更多是一种迷信,顶多算得上是一种宗教般的热情。什么是数字营销讲到这里,我想大家开始逐步了解到,产业互联网、互联网+、数字化转型,在未来究竟会有一场怎样的浪潮。「数字管理」这个话题很沉重,这也是为什么我刚一开始问大家的问题,IT负责人第一要重视的,是如何利用数字化做市场营销。什么是数字营销?对它的注意,始于一个餐饮同行问我的问题,他问我:“你会不会数字化营销?”我这直男当时听了还心里嘀咕了一下:营销难道不都是数字化的吗?后面仔细想一下,大部分的品牌营销确实是没有办法做到数字化,比如传单、电视、电梯等广告渠道,难以用量化的数字去衡量具体的广告效果。通过数字化渠道推广产品和服务的活动,并有数字化有效反馈的方式,都可理解为数字营销。传统企业所谓的数字化营销,约等于互联网运营的工作。一个公司的最高决策者,最关注的永远是业绩的增长,因为业绩增长,它能够带来最直接的量化呈现,一切数据看得见的,销售是多少,复购率是多少,能带动公司成长的事,绝对是最高决策者最关注的事情。我们为了做数字营销,需要一个实现量化的载体,这就是——「喜茶GO」小程序,表面是个小程序,却兼具了数字管理和数字营销的使命。以往购买一杯喜茶需要排长队,现在却可以手机直接下单,排队队伍急剧下降;以往消费者的提前预约需求,是通过拨电话到门店实现的,现在直接在手机提前预约就可以了;因为都是手机点单,自然我们就能了解到我们的真实用户画像;因为知道用户是谁,就自然知道门店的复购率;顺道一提,微信下拉菜单小程序入口,是我们的第一流量入口;线下门店一般存在的长短款问题,由于都是通过消费者线上下单的,彻底解决;餐饮行业总是财务对不准账,手机下单的方式彻底根治了这个问题;以往线下门店要做一个活动,需要大量物资和人员通知,现在通过手机就能把活动迅速完成;最重要的是,每一个登录过小程序的用户,我们都拥有了消息通知触达他们的能力。所有的线下门店,以往只能靠优质的地理位置来获取人流量,最多只有几公里影响范围,现在积累的用户触达能力则可以让营销场景无限拓宽。围绕着喜茶GO带来的技术变革,我们在深圳开设了无人收银门店「喜茶GO」。我们可以把喜茶GO小程序比作是一个流量池,它就像一条江流,可以把过去海量般的流量开始有策略地引进来。但江流毕竟还是会入海,这时候你还需要打造一个属于自己的鱼塘,这就是会员系统,通过会员系统的喜茶券和福利,进一步地培养消费者与品牌之间的感情。跟数字管理最大的对比,就是数字营销的所有工作都是可以被量化。基于获得的数据,我们还做了一个「数据驾驶舱」,所有的经营管理数据,都能通过数据驾驶舱清晰可见。我们在6个月时间积累超过400万的用户,复购率从最初第一个月至今,拉升了300%,日活超过17万,第一入口就是微信顶部下拉菜单,入选小程序流量前TOP100,70%微信支付用户都选择小程序下单(而不是门店排队),带动门店环比增长率为6%。如果有人有疑问,喜茶最近不排队了,是不是不火了?这可能只是个假象,因为大部分只是通过手机下单了。达成这一切做的唯一一件事情,就是在门店贴了一个二维码,没花钱,仅此而已,这也是我们所有增长的套路。什么是数字力量什么叫数字力量?我先从一个故事开始说起。有一天一个互联网公司市场部的同学找到我,说他突然想到了一个好点子,他发现喜茶具有巨大的社交场景,应该做一款小游戏,挺兴奋的。我听完之后我心里面有点忐忑,最关键的不是想法。所以我回了句,这个事最关键的是谁来研发这个游戏?答说,你们可以自己找一个外包团队来实现的。在数字时代,很多时候点子是不值钱的,更关键的是对细节的认知与执行力。这里得致敬当代所有的软件工程师和产品经理,一个公司没有数字力量,就算有再多的想法、点子,都是放屁。公司数字化,你必须得有强大的数字力量支持,也是在座各位在未来的价值所在。我们这把“数字力量”由三部分组成。第一,是我们自己的研发团队,喜茶GO小程序和背后的数字化引擎,都是通过他们的努力研发出来的。第二,除此以外我们还有合作伙伴,很多的科技公司做SaaS产品,功能往往是单点的,但实际在零售餐饮行业,做IT数字化,是一个「全局」的事,每个部门都有各种各样的业务数字化需求,自己的研发团队不可能覆盖所有场景,所以中间我们还有第三方合作伙伴,跟合作伙伴的系统做对接。第三,是我们还在组建一个数据中台,是数字力量至关重要的部分,它把我们所有系统数据都汇流在形成核心数据资产。数字化三支柱下的产品架构图前面讲了为什么我们会诞生数字化三支柱和什么是数字化三支柱,那基于数字化三支柱,究竟需要做什么样的数字化产品架构。像刚说的那样,跟大部分科技公司SaaS企业的产品多是提供单点功能,比如说有的SaaS企业会提供会员,提供POS,提供ERP,但却很难说,一个公司能囊括了所有的数字化系统。数字化不是单点的事情,而是全局涉及到公司每一个人,公司有多少部门,就有多少系统。而对于产业内部数字化,则不一样,数字化系统是一件全局的事情。数字管理里,就有ERP、OA、BOH、培训等系统,来协助我们的门店和职能部门的管理。在数字营销里,围绕现有的数字化能力进行的线上的营销运营。数字力量里,则有我们自研的点单系统、营销平台、会员系统、数据中台这些数字化基石。数字化三支柱背后的意义数字化三支柱在我们这,不止是一个理论基础,同时还是我们事业部的组织架构和工作方针。如何快速理解其背后的作用?数字化三支柱由三部分组成,一是数字营销,二是数字管理,三是数字力量。做数字营销是为了更好地卖产品,做数字管理是为了让公司更好地提供服务,建立数字力量,是为了应对数字化的变革和能更好地沉淀我们的数据,让公司提供跟消费者互动的能力。所以,继续推论,做数字营销的终点就是带动业务增长,为公司赚钱,做数字管理的终点是整体提升公司运转效率,为公司省钱,建立数字力量的终点是为公司聚合经营数据,创新新的核心资产。一个合格数字化企业应该同时拥有这三支柱,三件事必须同时地做。到最后,你能发现数字化最终都会跟公司财富直接挂钩,这背后其实都是钱,促使“数字化”演化成企业的核心战略和成长的引擎。以上内容来自陈霈霖老师的分享。声明:本文是由壹佰案例原创,转载请联系 meixu.feng@msup.com.cn

December 29, 2018 · 1 min · jiezi

Django搭建个人博客:统计文章浏览量

文章浏览量是所有社交类网站所必备的数据,足以显示其重要性了。博主可以通过浏览量来评估某篇文章的受欢迎程度,读者也能够通过浏览量来筛选质量更高的文章。然而,准确统计浏览量并不简单:某些类型的请求不应该统计为浏览量,比如作者自己的浏览或编辑文章之后的重定向请求;由于用户众多,浏览量的数据时刻都在快速更新,会给数据库带来很大的压力。因此很多大型网站都会使用如Redis这样的读写速度非常快的内存数据库辅助存储。因为我们的项目是博客网站,粗略统计就可以了,也没有那么大的用户压力,所以设计就简单得多了。模型浏览量作为每篇博文都有的数据,需要一个字段来存储。因此修改文章的模型:article/models.py…class ArticlePost(models.Model): … total_views = models.PositiveIntegerField(default=0) …PositiveIntegerField是用于存储正整数的字段default=0设定初始值从0开始修改完数据库别忘了要数据迁移,否则更改不会生效。由于新字段设置了初始值,迁移会很顺畅:(env) E:\django_project\my_blog>python manage.py makemigrationsMigrations for ‘article’: article\migrations\0003_articlepost_total_views.py - Add field total_views to articlepostMigrations for ‘userprofile’: userprofile\migrations\0002_auto_20181227_2041.py - Alter field avatar on profile - Alter field user on profile(env) E:\django_project\my_blog>python manage.py migrateOperations to perform: Apply all migrations: admin, article, auth, contenttypes, sessions, userprofileRunning migrations: Applying article.0003_articlepost_total_views… OK Applying userprofile.0002_auto_20181227_2041… OK列表模板为了方便观察效果,这次先写模板文件。什么地方需要显示浏览量呢?很容易想到的就是文章列表了。修改文章列表的模板:templates/article/list.html…<div class=“card-footer”> <!– 已有代码 –> <a href="{% url ‘article:article_detail’ article.id %}" class=“btn btn-primary”> 阅读本文 </a> <!– 显示浏览量 –> <span> <small class=“col align-self-end” style=“color: gray;"> 浏览: {{ article.total_views }} </small> </span></div>…笔者将浏览量显示在了“阅读本文”的边上。有的同学觉得显示在这里不好看,请修改代码,将其放到自己最满意的地方。(顺便熟悉一下Bootstrap!)详情模板除了文章列表外,通常详情页面中也需要显示浏览量。除此之外,在前面的学习中为了方便,没有做任何权限管理,以至于任何用户都可以对所有文章进行修改、删除:这样是肯定不行的,必须修复这个严重的错误。修改article/detail.html模板文件:templates/article/detail.html…<!– 文章详情 –><div class=“container”> <div class=“row”> … <div class=“col-12 alert alert-success”> <div> 作者:{{ article.author }} {% if user == article.author %} · <a href=”#" onclick=“confirm_delete()">删除文章</a> · <a href=”{% url “article:article_update” article.id %}"> 编辑文章 </a> {% endif %} </div> <div> 浏览:{{ article.total_views }} </div> </div> …</div>…修改内容有:确认当前登录用户是文章的作者,才显示“删除文章、“编辑文章”两个链接显示浏览量修改后的页面如下:上图中由于文章作者和登录用户不一致,修改文章的链接没有渲染出来了;如果登录用户是作者本人,它们又会正常显示。这样的方法可以阻止大部分的“好用户”非法修改数据。但是如果有“坏用户”直接输入url地址来使坏,该怎么办呢?所以光是靠前端页面来鉴权是不够的。视图现在浏览量能够正确显示了,但是由于没有进行任何处理,其数值会一直为0。我们希望每当用户访问详情页面时,浏览量就加1。修改article_detail()如下:article/views.py…def article_detail(request, id): article = ArticlePost.objects.get(id=id) # 浏览量 +1 article.total_views += 1 article.save(update_fields=[’total_views’]) …update_fields=[]指定了数据库只更新total_views字段,优化执行效率。测试一下,可以正常对浏览量计数了:视图中鉴权前面讲了,光是在模板中鉴权是不够的,必须在后端业务逻辑中再次验证用户身份。修改article_update()更新文章的视图:article/views.py…# 提醒用户登录@login_required(login_url=’/userprofile/login/’)def article_update(request, id): # 已有代码 article = ArticlePost.objects.get(id=id) # 过滤非作者的用户 if request.user != article.author: return HttpResponse(“抱歉,你无权修改这篇文章。”) …视图中进行了两次鉴权:login_required装饰器过滤未登录的用户if语句过滤已登录、但非作者本人的用户通过在业务逻辑中再次验证身份,完全阻止恶意用户从中使坏了。除了更新文章的视图外,删除文章也应该做类似的工作,请读者自行修改并测试。总结本章完成了简单的统计浏览量的功能,并且在前后端中对用户的身份进行了验证。下一章学习与浏览量紧密相关的功能:查询最热文章。有疑问请在杜赛的个人网站留言,我会尽快回复。或Email私信我:dusaiphoto@foxmail.com项目完整代码:Django_blog_tutorial转载请注明出处。 ...

December 29, 2018 · 1 min · jiezi

使用Docker开发Django项目

背景当多个Python项目且某些包无法兼容时,通常我们使用虚拟环境即可解决。但是在团队中多个环境其实相对比较固定了,较少变更,如果换电脑或者新人加入需要重新一个一个配置虚拟环境并安装相应的包,会耗费很多时间,而且由于重新安装的包依赖可能会有版本变更导致各种离奇问题。但事实上Docker不仅仅只能用于线上应用部署,我们的开发、调试环境也可以使用。下面以Django项目来举例,为了说明方便此处有以下前提条件和假设:基础Docker已经安装且可用Docker已经暴露了远程访问地址(使用Pycharm需要),具体方法请自行查阅文档或教材,假如为tcp://localhost:2375Docker基本命令不再详述Docker的Django环境镜像已经做好,为:myimageDjango代码目录为d:\demo演示环境为Windows 10(由于Docker集成原因,本文不适用于windows 10之前版本系统),linux和mac os可能稍有差别开始使用普通环境:python d:\demo\manage.py runserver 0.0.0.0:8000Docker启动:docker run -it –name demo -v d:\demo:/code -p 0.0.0.0:8000:8000 myimage python /code/manage.py runserver 0.0.0.0:8000在Pycharm中无缝使用添加Docker镜像:打开配置 pycharm > File > Settings > Project > Project Interpreter选择镜像修改原Run配置:打开原Run配置(和使用本地环境的配置一样,不再赘述)选择刚才添加的镜像,下面三个复选框保持下图一样上一步选择镜像后下面会出现Docker container settings:点开进行编辑,可以看到此时已经有了Volume bindings,还需要一个端口映射再次Run就已经是从容器中启动了(可以看到容器ID),使用Debug启动也是可以的底部还有个选项卡,此可以一键打开Django shell问题Q:使用Python Console打开django shell报错,错误示例:ModuleNotFoundError: No module named ‘cms’A:Pycharm > Settings > Build, Execution, Deployment > Console > Django Console 勾选如下两项,重新打开底部Python Console即可

December 28, 2018 · 1 min · jiezi

Django数据库连接丢失问题

问题在Django中使用mysql偶尔会出现数据库连接丢失的情况,错误通常有如下两种1. OperationalError: (2006, 'MySQL server has gone away') 1. OperationalError: (2013, 'Lost connection to MySQL server during query') 查询mysql全局变量SHOW GLOBAL VARIABLES;可以看到wait_timeout,此变量表示连接空闲时间。如果客户端使用一个连接查询多次数据库,如果连续查询则没有问题,如果查询几次后停顿超过wait_timeout后再次查询就会出现数据库连接丢失。复现下面用Django复现下次问题:将mysql的wait_timeout设置为10秒,然后进入django shell模拟查询(以下错误信息只保留了部分)In[1]:import timeIn[2]:from django.contrib.auth.models import UserIn[3]:list(User.objects.filter(id=1))Out[3]:[<User: admin>]In[4]:time.sleep(15) # 模拟比较慢的代码(其中没有查询数据库的代码),或者空闲什么都不操作一段时间,此时间要比wait_timeout大一些list(User.objects.filter(id=1))Traceback (most recent call last): File “<ipython-input-4-3574ae8220ee>”, line 1, in <module> list(User.objects.filter(id=1)) File “/usr/lib/python3.6/site-packages/pymysql/connections.py”, line 1037, in _read_bytes CR.CR_SERVER_LOST, “Lost connection to MySQL server during query”)django.db.utils.OperationalError: (2013, ‘Lost connection to MySQL server during query’)寻求那么以上问题就基本说明了是空闲时间过长导致的错误。 django为了减少不必要的数据库连接、关闭,复用了数据库连接,当开始一个请求后建立一个连接池存放连接,之后此次请求都复用一个连接。那猜测就是django保存连接的比wait_timeout长了,如果保存时间短一些就可以重新建立连接避免此错误了。没错,官方文档也已经说明了此问题,设置数据库 CONN_MAX_AGE参数,示例:DATABASES = { “default”: { ‘ENGINE’: ‘django.db.backends.mysql’, ‘NAME’: ‘’, ‘USER’: ‘’, ‘PASSWORD’: ‘’, ‘HOST’: ‘’, ‘CONN_MAX_AGE’: 9 # 比wait_timeout小一些 }}当我们测试后却发现,事情并非想想中那么简单。为何错误依旧出现?这一切的背后, 是人性的扭曲还是道德的沦丧?敬请收看下节《突破》。突破对django源码中CONN_MAX_AGE进行了一番搜索,顺藤摸瓜发现了django关闭失效连接的方法django.db.close_old_connections():# Register an event to reset transaction state and close connections past# their lifetime.def close_old_connections(**kwargs): for conn in connections.all(): conn.close_if_unusable_or_obsolete()signals.request_started.connect(close_old_connections)signals.request_finished.connect(close_old_connections)重点在最后两行,通过signal实现特定事件时执行此方法,两个特定事件顾名思义是请求开始和请求结束。而我们报错的是在一次请求中,所以此法通常无效,仅仅是实现每个请求关闭并重新建立连接。解决复现问题的django shell不要关闭,继续执行如下代码:In[5]:from django.db import close_old_connectionsIn[6]:close_old_connections()In[7]:list(User.objects.filter(id=1))Out[7]: [<User: admin>]调用django.db.close_old_connections后再次查询就没有错误了。那么我们要避免此错误就要执行每个数据库查询前调用django.db.close_old_connections方法。一般情况不会出现此类问题,因为一个请求中不间断进行数据库查询,无需每个请求调用此方法,杞人忧天。有时候一个请求中数据量较大,会查询数据库后进行一段时间其他(不涉及数据库)处理,比如先查询一些数据,然后将数据处理、生成excel、保存文件并生成url。已知此过长需要非常长时间,那么最终url保存数据库就最好先调用django.db.close_old_connections防止连接丢失题外话实际上②所述情况最好从根本上解决处理慢的问题,也可以换作异步处理,从根本上解决问题。 ...

December 28, 2018 · 1 min · jiezi

Python进阶:自定义对象实现切片功能

Python进阶:自定义对象实现切片功能切片是 Python 中最迷人最强大最 Amazing 的语言特性(几乎没有之一),在《Python进阶:切片的误区与高级用法》中,我介绍了切片的基础用法、高级用法以及一些使用误区。这些内容都是基于原生的序列类型(如字符串、列表、元组……),那么,我们是否可以定义自己的序列类型并让它支持切片语法呢?更进一步,我们是否可以自定义其它对象(如字典)并让它支持切片呢?1、魔术方法:getitem()想要使自定义对象支持切片语法并不难,只需要在定义类的时候给它实现魔术方法 getitem() 即可。所以,这里就先介绍一下这个方法。语法: object.getitem(self, key)官方文档释义:Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the getitem() method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.概括翻译一下:getitem() 方法用于返回参数 key 所对应的值,这个 key 可以是整型数值和切片对象,并且支持负数索引;如果 key 不是以上两种类型,就会抛 TypeError;如果索引越界,会抛 IndexError ;如果定义的是映射类型,当 key 参数不是其对象的键值时,则会抛 KeyError 。2、自定义序列实现切片功能接下来,我们定义一个简单的 MyList ,并给它加上切片功能。(PS:仅作演示,不保证其它功能的完备性)。class MyList(): def init(self): self.data = [] def append(self, item): self.data.append(item) def getitem(self, key): print(“key is : " + str(key)) return self.data[key]l = MyList()l.append(“My”)l.append(“name”)l.append(“is”)l.append(“Python猫”)print(l[3])print(l[:2])print(l[‘hi’])### 输出结果:key is : 3Python猫key is : slice(None, 2, None)[‘My’, ’name’]key is : hiTraceback (most recent call last):…TypeError: list indices must be integers or slices, not str从输出结果来看,自定义的 MyList 既支持按索引查找,也支持切片操作,这正是我们的目的。特别需要说明的是,此例中的 getitem() 方法会根据不同的参数类型而实现不同的功能(取索引位值或切片值),也会妥当地处理异常,所以并不需要我们再去写繁琐的处理逻辑。网上有不少学习资料完全是在误人子弟,它们会教你区分参数的不同类型,然后写一大段代码来实现索引查找和切片语法,简直是画蛇添足。下面的就是一个代表性的错误示例:###略去其它代码####def getitem(self, index): cls = type(self) if isinstance(index, slice): # 如果index是个切片类型,则构造新实例 return cls(self.components[index]) elif isinstance(index, numbers.Integral): # 如果index是个数,则直接返回 return self.components[index] else: msg = “{cls.name} indices must be integers” raise TypeError(msg.format(cls=cls))3、自定义字典实现切片功能切片是序列类型的特性,所以在上例中,我们不需要写切片的具体实现逻辑。但是,对于其它非序列类型的自定义对象,就得自己实现切片逻辑。以自定义字典为例(PS:仅作演示,不保证其它功能的完备性):class MyDict(): def init(self): self.data = {} def len(self): return len(self.data) def append(self, item): self.data[len(self)] = item def getitem(self, key): if isinstance(key, int): return self.data[key] if isinstance(key, slice): slicedkeys = list(self.data.keys())[key] return {k: self.data[k] for k in slicedkeys} else: raise TypeErrord = MyDict()d.append(“My”)d.append(“name”)d.append(“is”)d.append(“Python猫”)print(d[2])print(d[:2])print(d[-4:-2])print(d[‘hi’])### 输出结果:is{0: ‘My’, 1: ’name’}{0: ‘My’, 1: ’name’}Traceback (most recent call last):…TypeError上例的关键点在于将字典的键值取出,并对键值的列表做切片处理,其妙处在于,不用担心索引越界和负数索引,将字典切片转换成了字典键值的切片,最终实现目的。4、小结最后小结一下:本文介绍了__getitem() 魔术方法,并用于实现自定义对象(以列表类型和字典类型为例)的切片功能,希望对你有所帮助。参考阅读: Python进阶:切片的误区与高级用法官方文档getitem用法:http://t.cn/EbzoZypPython切片赋值源码分析:http://t.cn/EbzSaoZPS:本公众号(Python猫)已开通读者交流群,详情请通过菜单栏中的“交流群”来了解。—————–本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。 ...

December 27, 2018 · 2 min · jiezi

数据中心运营商资产管理软件

简介django-idcops 是一个开源的倾向于数据中心运营商而开发的,拥有数据中心、客户、机柜、设备、跳线、物品、测试、文档等一些列模块的资源管理平台,解决各类资源集中管理与数据可视化的问题。django-idcops 通过“数据中心”来分类管理每个数据中心下面的资源,每个数据中心均是单独的。软件许可协议django-idcops 遵循 Apache License 2.0。联系作者博客email: 294060408@qq.comqq群:185964462点击进入QQ群: 数据中心运维管理idcops快速开始一、安装:cd /homegit clone https://github.com/Wenvki/django-idcops.git mysitecd mysite/mkvirtualenv env # python虚拟环境pip install -U pip # 升级pippip install -r requirements.txt python manage.py migratepython manage.py createsuperuser # 创建一个超级管理员用户python manage.py runserver 0.0.0.0:8000 # 以django开发服务器运行软件二、初始化配置:1、访问 http://your_ip:8000/ 2、首次使用,系统还没有数据中心,需新建一个数据中心3、将用户关联至数据中心4、重新访问首页 http://your_ip:8000/ 三、配置settings.py ~/mysite/idcops_proj/idcops_proj/settings.py:STATIC_URL = ‘/static/‘STATIC_ROOT = os.path.join(BASE_DIR, ‘static’)MEDIA_URL = ‘/media/‘MEDIA_ROOT = os.path.join(BASE_DIR, ‘media’)AUTH_USER_MODEL = ‘idcops.User’# idcops optionsSOFT_DELELE = TrueCOLOR_TAGS = False说明与项目截图模块说明:[(‘syslog’, ’log entries’), # 日志记录,核心内容,用于报表统计,日志分析等(‘user’, ‘用户信息’), (‘idc’, ‘数据中心’), (‘option’, ‘机房选项’), # 机房选项,核心内容 ,系统元数据(‘client’, ‘客户信息’), (‘rack’, ‘机柜信息’), (‘unit’, ‘U位信息’), (‘pdu’, ‘PDU信息’), (‘device’, ‘设备信息’), (‘online’, ‘在线设备’), (‘offline’, ‘下线设备’), (‘jumpline’, ‘跳线信息’), (’testapply’, ‘测试信息’), (‘zonemap’, ‘区域视图’), (‘goods’, ‘物品分类’), (‘inventory’, ‘库存物品’), (‘document’, ‘文档资料’)]项目截图: ...

December 26, 2018 · 1 min · jiezi

Python进阶:切片的误区与高级用法

众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串、列表、元组…)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢?切片(slice)就是一种截取索引片段的技术,借助切片技术,我们可以十分灵活地处理序列类型的对象。通常来说,切片的作用就是截取序列对象,然而,它还有一些使用误区与高级用法,都值得我们注意。所以,本文将主要跟大家一起来探讨这些内容,希望你能学有所获。事先声明,切片并非列表的专属操作,但因为列表最具有代表性,所以,本文仅以列表为例作探讨。1、切片的基础用法列表是 Python 中极为基础且重要的一种数据结构,我曾写过一篇汇总文章(链接见文末)较全面地学习过它。文中详细地总结了切片的基础用法,现在回顾一下:切片的书写形式:[i : i+n : m] ;其中,i 是切片的起始索引值,为列表首位时可省略;i+n 是切片的结束位置,为列表末位时可省略;m 可以不提供,默认值是1,不允许为0 ,当m为负数时,列表翻转。注意:这些值都可以大于列表长度,不会报越界。切片的基本含义是:从序列的第i位索引起,向右取到后n位元素为止,按m间隔过滤 。li = [1, 4, 5, 6, 7, 9, 11, 14, 16]# 以下写法都可以表示整个列表,其中 X >= len(li)li[0:X] == li[0:] == li[:X] == li[:] == li[::] == li[-X:X] == li[-X:]li[1:5] == [4,5,6,7] # 从1起,取5-1位元素li[1:5:2] == [4,6] # 从1起,取5-1位元素,按2间隔过滤li[-1:] == [16] # 取倒数第一个元素li[-4:-2] == [9, 11] # 从倒数第四起,取-2-(-4)=2位元素li[:-2] == li[-len(li):-2] == [1,4,5,6,7,9,11] # 从头开始,取-2-(-len(li))=7位元素# 步长为负数时,列表先翻转,再截取li[::-1] == [16,14,11,9,7,6,5,4,1] # 翻转整个列表li[::-2] == [16,11,7,5,1] # 翻转整个列表,再按2间隔过滤li[:-5:-1] == [16,14,11,9] # 翻转整个列表,取-5-(-len(li))=4位元素li[:-5:-3] == [16,9] # 翻转整个列表,取-5-(-len(li))=4位元素,再按3间隔过滤# 切片的步长不可以为0li[::0] # 报错(ValueError: slice step cannot be zero)上述的某些例子对于初学者(甚至很多老手)来说,可能还不好理解。我个人总结出两条经验:(1)牢牢记住公式[i : i+n : m] ,当出现缺省值时,通过想象把公式补全;(2)索引为负且步长为正时,按倒数计算索引位置;索引为负且步长为负时,先翻转列表,再按倒数计算索引位置。2、切片是伪独立对象切片操作的返回结果是一个新的独立的序列(PS:也有例外,参见《Python是否支持复制字符串呢?》)。以列表为例,列表切片后得到的还是一个列表,占用新的内存地址。当取出切片的结果时,它是一个独立对象,因此,可以将其用于赋值操作,也可以用于其它传递值的场景。但是,切片只是浅拷贝,它拷贝的是原列表中元素的引用,所以,当存在变长对象的元素时,新列表将受制于原列表。li = [1, 2, 3, 4]ls = li[::]li == ls # Trueid(li) == id(ls) # Falseli.append(li[2:4]) # [1, 2, 3, 4, [3, 4]]ls.extend(ls[2:4]) # [1, 2, 3, 4, 3, 4]# 下例等价于判断li长度是否大于8if(li[8:]): print(“not empty”)else: print(“empty”)# 切片列表受制于原列表lo = [1,[1,1],2,3]lp = lo[:2] # [1, [1, 1]]lo[1].append(1) # [1, [1, 1, 1], 2, 3]lp # [1, [1, 1, 1]]由于可见,将切片结果取出,它可以作为独立对象使用,但是也要注意,是否取出了变长对象的元素。3、切片可作为占位符切片既可以作为独立对象被“取出”原序列,也可以留在原序列,作为一种占位符使用。在写《详解Python拼接字符串的七种方式》的时候,我介绍了几种拼接字符串的方法,其中三种格式化类的拼接方法(即 %、format()、template)就是使用了占位符的思想。对于列表来说,使用切片作为占位符,同样能够实现拼接列表的效果。特别需要注意的是,给切片赋值的必须是可迭代对象。li = [1, 2, 3, 4]# 在头部拼接li[:0] = [0] # [0, 1, 2, 3, 4]# 在末尾拼接li[len(li):] = [5,7] # [0, 1, 2, 3, 4, 5, 7]# 在中部拼接li[6:6] = [6] # [0, 1, 2, 3, 4, 5, 6, 7]# 给切片赋值的必须是可迭代对象li[-1:-1] = 6 # (报错,TypeError: can only assign an iterable)li[:0] = (9,) # [9, 0, 1, 2, 3, 4, 5, 6, 7]li[:0] = range(3) # [0, 1, 2, 9, 0, 1, 2, 3, 4, 5, 6, 7]上述例子中,若将切片作为独立对象取出,那你会发现它们都是空列表,即 li[:0]==li[len(li):]==li[6:6]==[] ,我将这种占位符称为“纯占位符”,对纯占位符赋值,并不会破坏原有的元素,只会在特定的索引位置中拼接进新的元素。删除纯占位符时,也不会影响列表中的元素。与“纯占位符”相对应,“非纯占位符”的切片是非空列表,对它进行操作(赋值与删除),将会影响原始列表。如果说纯占位符可以实现列表的拼接,那么,非纯占位符可以实现列表的替换。li = [1, 2, 3, 4]# 不同位置的替换li[:3] = [7,8,9] # [7, 8, 9, 4]li[3:] = [5,6,7] # [7, 8, 9, 5, 6, 7]li[2:4] = [‘a’,‘b’] # [7, 8, ‘a’, ‘b’, 6, 7]# 非等长替换li[2:4] = [1,2,3,4] # [7, 8, 1, 2, 3, 4, 6, 7]li[2:6] = [‘a’] # [7, 8, ‘a’, 6, 7]# 删除元素del li[2:3] # [7, 8, 6, 7]切片占位符可以带步长,从而实现连续跨越性的替换或删除效果。需要注意的是,这种用法只支持等长替换。li = [1, 2, 3, 4, 5, 6]li[::2] = [‘a’,‘b’,‘c’] # [‘a’, 2, ‘b’, 4, ‘c’, 6]li[::2] = [0]*3 # [0, 2, 0, 4, 0, 6]li[::2] = [‘w’] # 报错,attempt to assign sequence of size 1 to extended slice of size 3del li[::2] # [2, 4, 6]4、更多思考其它编程语言是否有类似于 Python 的切片操作呢?有什么差异?我在交流群里问了这个问题,小伙伴们纷纷说 Java、Go、Ruby……在查看相关资料的时候,我发现 Go 语言的切片是挺奇怪的设计。首先,它是一种特殊类型,即对数组(array)做切片后,得到的竟然不是一个数组;其次,你可以创建和初始化一个切片,需要声明长度(len)和容量(cap);再者,它还存在超出底层数组的界限而需要进行扩容的动态机制,这倒是跟 Python 列表的超额分配机制有一定相似性……在我看来,无论是用意,还是写法和用法,都是 Python 的切片操作更明了与好用。所以,本文就不再进行跨编程语言的比较了(唔,好吧我承认,其实是我不怎么懂其它编程语言……)最后,还有一个问题:Python 的切片操作有什么底层原理呢? 我们是否可以自定义切片操作呢?限于篇幅,我将在下次推文中跟大家一起学习,敬请期待。延伸阅读 :超强汇总:学习Python列表,只需这篇文章就够了详解Python拼接字符串的七种方式Python是否支持复制字符串呢?PS:本公众号(Python猫)已开通读者交流群,详情请通过菜单栏中的“交流群”了解。—————–本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。 ...

December 23, 2018 · 2 min · jiezi

Django搭建个人博客:文章分页

随着时间的推移(加上勤奋的写作!),你的博客文章一定会越来越多。如果不进行处理,可能同一个页面会挤上成百上千的文章,不美观不说,还降低了页面的反应速度。这个时候就需要对文章进行分页的处理。利用轮子写一个完善的分页功能是有些难度的,好在Django已经帮你准备好一个现成的分页模块了(Django把大部分基础功能都替你准备好了!)。内置模块虽然简单,但是对博客来说完全足够了。我们要用到的是Paginator类。在Shell中可以充分尝试它的用法:>>> from django.core.paginator import Paginator>>> objects = [‘john’, ‘paul’, ‘george’, ‘ringo’]>>> p = Paginator(objects, 2)>>> p.count4>>> p.num_pages2>>> p.page_rangerange(1, 3)>>> page1 = p.page(1)>>> page1<Page 1 of 2>>>> page1.object_list[‘john’, ‘paul’]>>> page2 = p.page(2)>>> page2.object_list[‘george’, ‘ringo’]>>> page2.has_next()False>>> page2.has_previous()True>>> page2.has_other_pages()True>>> page2.previous_page_number()1这是一个官网的例子。详见:Pagination有了这个类,剩下的工作就是把它应用到项目中去。轻车熟路要对文章列表分页,因此就要修改article/views.py的def article_list()视图:article/views.py…# 引入分页模块from django.core.paginator import Paginatordef article_list(request): # 修改变量名称(articles -> article_list) article_list = ArticlePost.objects.all() # 每页显示 1 篇文章 paginator = Paginator(article_list, 1) # 获取 url 中的页码 page = request.GET.get(‘page’) # 将导航对象相应的页码内容返回给 articles articles = paginator.get_page(page) context = { ‘articles’: articles } return render(request, ‘article/list.html’, context)…在视图中通过Paginator类,给传递给模板的内容做了手脚:返回的不再是所有文章的集合,而是对应页码的部分文章的对象,并且这个对象还包含了分页的方法。我们在前面的文章已经接触过一些将参数传递到视图的手段了:通过POST请求将表单数据传递到视图通过url将地址中的参数传递到视图这里用到了另一种方法:在GET请求中,在url的末尾附上?key=value的键值对,视图中就可以通过request.GET.get(‘key’)来查询value的值。然后改写模板,在最末尾的</div>前面,加入分页的内容:templates/article/list.html…<!– 页码导航 –><div class=“pagination row”> <div class=“m-auto”> <span class=“step-links”> <!– 如果不是第一页,则显示上翻按钮 –> {% if articles.has_previous %} <a href="?page=1" class=“btn btn-success”> &laquo; 1 </a> <span>…</span> <a href="?page={{ articles.previous_page_number }}" class=“btn btn-secondary” > {{ articles.previous_page_number }} </a> {% endif %} <!– 当前页面 –> <span class=“current btn btn-danger btn-lg”> {{ articles.number }} </span> <!– 如果不是最末页,则显示下翻按钮 –> {% if articles.has_next %} <a href="?page={{ articles.next_page_number }}" class=“btn btn-secondary” > {{ articles.next_page_number }} </a> <span>…</span> <a href="?page={{ articles.paginator.num_pages }}" class=“btn btn-success” > {{ articles.paginator.num_pages }} &raquo; </a> {% endif %} </span> </div></div>…内容也比较简单,用到了前面的Shell中演示的部分方法,判断当前页所处的位置。这样就行了!补充几篇文章(笔者共6篇),方便测试。刷新页面后是这样的:视图中设置了每页只有1篇文章,所以就真的只有1篇了。当然这只是为了测试,实际环境中肯定要远大于1篇的。点击第2页的按钮后是这样的:看到顶部地址栏中的变化了吗?思考一下page是如何从模板传递到视图的。总结除模板外,我们只写了4行代码,就有了还不错的分页导航,Django就是这么贴心。除了对文章列表,你可以对任何你想分页的地方运用此模块(比如以后要讲到的评论),满足用户各类的需求。读者还可以稍加阅读Bootstrap 4官方文档,改写一个符合自己品味的外观。有疑问请在杜赛的个人网站留言,我会尽快回复。或Email私信我:dusaiphoto@foxmail.com项目完整代码:Django_blog_tutorial转载请并注明出处。 ...

December 20, 2018 · 1 min · jiezi

如何一步步在生产环境上部署django和vue

本文由云+社区发表本文主要讲述了如何一步步在生产环境上部署django和vue,操作系统默认为centos说明:后文中出现的以下字符串均表示具体的路径或者名称,含义如下:DJANGO_DIR—-表示django的工程根目录DJANGO_NAME—-表示django的工程名称VUE_HTML_DIR—-表示vue编译好的index.html路径VUE_STATIC_DIR—-表示vue编译好的静态文件夹static的路径整体框架一个常用的web框架图如下图所示框架选用.jpg我们使用nginx + uwsgi来驱动django,因为uwsgi性能非常高720333-20170312154455592-1425120615.png一、安装和配置nginx安装使用yum安装即可yum -y install nginx启动service nginx start此时到浏览器输入对应的ip地址,出现下面页面即表示安装成功1324702136-57fb16aa00d21_articlex.png修改配置文件nginx可以新建一个配置,放在项目目录,暂时不修改nginx的默认配置,端口号可以换一个,然后在/etc/nginx/conf.d/内新建一个软链接指向该配置文件,这样nginx在读取配置时会将该配置一起读进去。这样,访问端口号8080的请求便会指向我们自己的这个配置。server { listen 8080; server_name 132.232.50.225; root /data/; charset utf-8; access_log /data/access_narwhals.log; error_log /data/error_narwhals.log; client_max_body_size 75M; location / { uwsgi_pass 127.0.0.1:9090; include /etc/nginx/uwsgi_params; } location ^~ /admin/ { uwsgi_pass 127.0.0.1:9090; include /etc/nginx/uwsgi_params; }}该配置中uwsgi_pass要指向uwsgi绑定的接口。(我们先假设uwsgi配置的是9090端口)二、安装和配置uwsgi安装使用yum或者pip均可安装yum install uwsgi# 或者pip install uwsgi不过这里需要注意,如果运行uwsgi出现下面错误uwsgi: option ‘–http’ is ambiguous; possibilities: ‘–http-socket’ ‘–https-socket-modifier2’ ‘–https-socket-modifier1’ ‘–https-socket’ ‘–http11-socket’ ‘–http-socket-modifier2’ ‘–http-socket-modifier1’getopt_long() error主要是用yum安装的uwsgi,缺少python的plugin,可以安装对应的插件yum install uwsgi-plugin-pythonplugins = python (加在ini配置文件中)配置uwsgi可以使用命令行启动,也可以使用配置文件来启动,推荐使用配置文件来启动守护进程,配置文件内容如下[uwsgi]socket = 127.0.0.1:9090stats = 127.0.0.1:9293workers = 4# 项目根目录chdir = DJANGO_DIRtouch-reload = DJANGO_DIRpy-auto-reload = 1# 在项目跟目录和项目同名的文件夹里面的一个文件module= DJANGO_NAME.wsgipidfile = /var/run/inner_manager.piddaemonize = /data/uwsgi9090.log# If you plan to receive big requests with lots of headers you can increase this value up to 64k (65535).buffer-size=65535这里以socket形式运行uwsgi,绑定了本地的9090端口,也就是上文nginx配置中uwsgi_pass指定的端口。大概解释下几个配置的含义:chdir—-应用加载前chdir到指定目录,一般设置为django的工程根目录touch-reload—-如果修改/碰了指定的文件,那么重载uWSGImodule—-加载一个WSGI模块的路径,如果django的话就指向对应的wsgi文件模块buffer-size—-设置请求的最大大小 (排除request-body),这一般映射到请求头的大小。默认情况下,它是4k。如果你接收到了一个更大的请求 (例如,带有大cookies或者查询字符串),那么你也许需要增加它。它也是一个安全度量,所以调整为你的应用需要,而不是最大输出。该值如果太小会报错具体参数含义可以到官方文档查找然后使用命令启动uwsgi进程,其中uwsgi.ini为上面内容的配置文件uwsgi -i uwsgi.ini可以看下日志文件有没有报错,或者看下ps -ef|grep uwsgi进程有没有跑起来。一定要确保进程正常run起来才行至此,DJANGO已经通过nginx+uwsgi可以访问了三、配置访问vue其实这里访问编译好的vue静态文件有很多方式,本文主要讲述通过nginx直接访问和通过django路由访问通过django路由访问其实我们也可以直接通过http://ip:8080/ 来经由django的路由来访问vue的页面。当然要做到这样要确保以下配置的正确找到DJANGO_DIR根目录下DJANGO_NAME同名文件夹下urls.py,使用通用视图创建最简单的模板控制器,增加一行路由url(r’^$’, TemplateView.as_view(template_name=“index.html”)),这样访问http://ip:8080/时会直接返回 index.html。上一步使用了Django的模板系统,所以需要配置一下模板使Django知道从哪里找到index.html。在project目录的settings.py下:TEMPLATES = [ { ‘BACKEND’: ‘django.template.backends.django.DjangoTemplates’, ‘DIRS’: [VUE_HTML_DIR], ‘APP_DIRS’: True, ‘OPTIONS’: { ‘context_processors’: [ ‘django.template.context_processors.debug’, ‘django.template.context_processors.request’, ‘django.contrib.auth.context_processors.auth’, ‘django.contrib.messages.context_processors.messages’, ], }, },]按照上述配置完成后,结合前面配置好的nginx和uwsgi,你已经可以通过http://ip:8080/ 来访问到对应的vue编译好的VUE_HTML_DIR目录下的index.html了,但是这时候你可能会有其他困扰,比如找不到css样式文件的问,这经常是静态配置有误导致找不到静态文件的问题。Django通过django.contrib.staticfiles来管理静态文件,首先确保django.contrib.staticfiles已经添加到INSTALLED_APPS。然后可以在DJANGO的配置文件settings.py中增加以下几个配置:STATIC_URL = ‘/static/‘STATIC_ROOT = os.path.join(BASE_DIR, “static”)# Add for vuejsSTATICFILES_DIRS = [ VUE_STATIC_DIR, # other static folders]STATIC_URL对外提供WEB访问时static的URL地址STATIC_ROOT设置绝对路径, 用来保存收集到的静态文件,服务器最终也将从该路径中获取文件进行转发。在collectstatic运行的时候会把STATICFILES_DIRS中的静态文件拷贝到这个目录中,达到从开发环境到生产环节过程中移植静态文件的作用。STATICFILES_DIRS用来配置一些开发环境下生成的静态文件的地址,即编译好的VUE_STATIC_DIR在url.py中添加路由 url(r’^static/(?P<path>.)$’, static.serve, {‘document_root’: settings.STATIC_ROOT}, name=‘static’),配置好以上配置后,编译好的静态文件还在VUE_STATIC_DIR目录下,我们最终要执行下面命令才能把STATICFILES_DIRS中的静态文件拷贝到STATIC_ROOT这个目录中,也就是最终生产环境指定的static的存放目录python manage.py collectstatic那么为什么不直接手动把构建好的VUE_STATIC_DIR中的文件拷过来呢,因为Django自带的App:admin 也有一些静态文件(css,js等),它会一并collect过来,毕竟nginx只认项目跟目录的静态文件,它不知道django把它自己的需求文件放到哪了这样你访问django的admin网址http://ip:8080/admin 时,也不会出现找不到css的问题了当然这种方式其实是通过django的路由来访问静态文件的,一般的,生产环境不会通过django来转发静态文件,而是通过其他服务器进行转发,比如nginx,apache等,所以这里我们需要再配置下nginx的配置文件,在8080的server中增加如下路径的配置 location /static/ { expires 30d; autoindex on; add_header Cache-Control private; alias VUE_STATIC_DIR; access_log off; }这样访问静态文件便会直接通过nginx来访问了,不用担心静态文件访问导致Django的处理速度变慢了。通过nginx直接访问如果你想直接通过nginx访问对应的前端vue文件,可以重新配置一个server来访问对应的html文件,比如上面已经使用了8080端口,我们可以用默认的80端口来配置个server,其中root可以指向存放index.html文件的路径,/static/路径下的root路径可以指向html对应的存放css和js的static文件夹,如果static就在index.html路径下,不指认也可以。直接修改/etc/nginx.conf即可,里面已经有配置好的80端口的server配置如下所示 server { listen 80 default_server; listen [::]:80 default_server; server_name _; root VUE_HTML_DIR; # Load configuration files for the default server block. include /etc/nginx/default.d/.conf; location / { } location /static/ { root VUE_STATIC_DIR; access_log off; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } }这样我们可以通过http://ip:80/ 来访问vue编译好的页面,使用http://ip:8080/ 访问django配置的cgi请求四、通过supervisor管理进程上面我们已经用到了uwsgi,后面可能还会用到redis、celery,都需要开启守护进程,其中celery自身还不支持守护进程。那么如何管理这么多进程呢,这时候可以考虑下supervisor安装使用pip安装即可pip install supervisor配置我们可以配置redis,celery,uwsgi进去,比如向下面一样[program:redis];指定运行目录directory=%(here)s/;执行命令(redis-server redis配置文件路径)command=redis-server /etc/redis.conf;启动设置numprocs=1 ;进程数autostart=true ;当supervisor启动时,程序将会自动启动autorestart=true ;自动重启;停止信号stopsignal=INT[program:celery.worker.default];指定运行目录directory=%(here)s/;运行目录下执行命令command=celery -A DjangoProject worker –loglevel info –logfile log/celery_worker.log -Q default -n %%h-%(program_name)s-%(process_num)02dprocess_name=%(process_num)02d;启动设置 numprocs=2 ;进程数autostart=true ;当supervisor启动时,程序将会自动启动 autorestart=true ;自动重启 ;停止信号,默认TERM ;中断:INT (类似于Ctrl+C)(kill -INT pid),退出后会将写文件或日志(推荐) ;终止:TERM (kill -TERM pid) ;挂起:HUP (kill -HUP pid),注意与Ctrl+Z/kill -stop pid不同 ;从容停止:QUIT (kill -QUIT pid) stopsignal=INT[program:uwsgi];指定运行目录directory=%(here)s/;运行目录下执行命令command=uwsgi -i conf/uwsgi/uwsgi9090.ini;启动设置numprocs=1 ;进程数autostart=true ;当supervisor启动时,程序将会自动启动autorestart=true ;自动重启;停止信号,默认TERM;中断:INT (类似于Ctrl+C)(kill -INT pid),退出后会将写文件或日志(推荐);终止:TERM (kill -TERM pid);挂起:HUP (kill -HUP pid),注意与Ctrl+Z/kill -stop pid不同;从容停止:QUIT (kill -QUIT pid)stopsignal=INT使用启动supervisor输入如下命令,使用具体的配置文件执行:supervisord -c supervisord.conf关闭supervisord需要通过supervisor的控制器:supervisorctl -c supervisord.conf shutdown重启supervisord也是通过supervisor的控制器:supervisorctl -c supervisord.conf reload一些特殊的变量%(here)s 配置文件所在路径(program_name)s program的名字%(process_num)02d 多进程时的进程号注意:command中如果含有%,需要进行转义%%多进程时如果不指定process_name会遇到如下错误Error: Format string ‘celery -A INTProject worker –loglevel info –logfile log/celery_worker.log -Q diff_task,caller_task -n %h’ for ‘program:celery.worker.mac.command’ is badly formatted: incomplete format in section ‘program:celery.worker.mac’ (file: ‘supervisord.conf’)中间可能遇到的坑*8107 recv() failed (104: Connection reset by peer) while reading response header from upstream, client 错误使用django+uwsgi+nginx,发现如下报错2018/10/08 14:34:33 [error] 12283#0: *8107 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 9.19.161.66, server: 132.232.50.225, request: “GET /auth/info?token=ZXlKaGJHY2lPaUprWldaaGRXeDBJaXdpZEhsd0lqb2lTbGRRSW4wOjFnOVA3aDp0bVZYcmg3XzJPR3RXSHJrbXFLRVdCZEpUdXc_ZXlKMWMyVnlibUZ0WlNJNkltVjBhR0Z1Wm1GdUlpd2lhV0YwSWpveE5UTTRPVGd3TkRjekxqZzVNekk1TVgwOjFnOVA3aDpMVXRHZkFiQkhrRTNaenFnS3NuS1RvOHBOMGM_3bdf34e6de16096f9982015a2382d3c8 HTTP/1.1”, upstream: “uwsgi://127.0.0.1:9090”, host: “int.oa.com”, referrer: “http://int.oa.com/"I finally found a reference to fastcgi and a 502 bad gateway error (https://support.plesk.com/hc/...). That lead me to look for a buffer size limit in the uwsgi configuration which exists as buffer-size. The default value is 4096. From the documentation, it says: If you plan to receive big requests with lots of headers you can increase this value up to 64k (65535).意思是uwsgi中有一项配置是buffer-size,表明收到的最大请求size,默认是4096,可以将其改成65535buffer-size=65535此文已由作者授权腾讯云+社区发布 ...

December 12, 2018 · 3 min · jiezi

你真的知道Python的字符串怎么用吗?

正如《你真的知道Python的字符串是什么吗?》所写,Python 中字符串是由 Uniocde 编码的字符组成的不可变序列,它具备与其它序列共有的一些操作,例如判断元素是否存在、拼接序列、切片操作、求长度、求最值、求元素的索引位置及出现次数等等。除此之外,它还有很多特有的操作,值得我们时常温故学习,所以,今天我就跟大家继续聊聊字符串。本文主要介绍 Python 字符串特有的操作方法,比如它的拼接、拆分、替换、查找及字符判断等使用方法,辨析了一些可能的误区。最后,还做了两个扩展思考:为什么 Python 字符串不具备列表类型的某些操作呢,为什么它不具备 Java 字符串的一些操作呢?两相比较,希望能帮助你透彻地理解——Python 的字符串到底怎么用?0. 拼接字符串字符串的拼接操作最常用,我专门为这个话题写过一篇《详解Python拼接字符串的七种方式》,建议你回看。在此,简单回顾一下:七种拼接方式从实现原理上划分为三类,即格式化类(%占位符、format()、template)、拼接类(+操作符、类元祖方式、join())与插值类(f-string),在使用上,我有如下建议——当要处理字符串列表等序列结构时,采用join()方式;拼接长度不超过20时,选用+号操作符方式;长度超过20的情况,高版本选用f-string,低版本时看情况使用format()或join()方式。不敢说字符串就只有这七种拼接方式,但应该说它们是最常见的了。有小伙伴说,我写漏了一种,即字符串乘法 ,可以重复拼接自身。没错,从结果上看,这是第八种拼接方式,视为补充吧。关于字符串拼接,还得补充一个建议,即在复杂场景下,尽量避免使用以上几类原生方法,而应该使用外置的强大的处理库。比如在拼接 SQL 语句的时候,经常要根据不同的条件分支,来组装不同的查询语句,而且还得插入不同的变量值,所以当面临这种复杂的场景时,传统拼接方式只会加剧代码的复杂度、降低可读性和维护性。使用 SQLAlchemy 模块,将有效解决这个问题。1. 拆分字符串在字符串的几种拼接方法中,join() 方法可以将列表中的字符串元素,拼接成一个长的字符串,与此相反,split() 方法可以将长字符串拆分成一个列表。前面已说过,字符串是不可变序列,所以字符串拆分过程是在拷贝的字符串上进行,并不会改变原有字符串。split() 方法可接收两个参数,第一个参数是分隔符,即用来分隔字符串的字符,默认是所有的空字符,包括空格、换行(n)、制表符(t)等。拆分过程会消耗分隔符,所以拆分结果中不包含分隔符。s = ‘Hello world’l = ‘‘‘Hi there , my name is Python猫Do you like me ?’’’# 不传参数时,默认分隔符为所有空字符s.split() >>> [‘Hello’, ‘world’]s.split(’ ‘) >>> [‘Hello’, ‘world’]s.split(’ ‘) >>> [‘Hello world’] # 不存在两个空格符s.split(‘world’) >>> [‘Hello’, ‘’]# 空字符包括空格、多个空格、换行符等l.split() >>> [‘Hi’, ’there’, ‘,’, ‘my’, ’name’, ‘is’, ‘Python猫’, ‘Do’, ‘you’, ’like’, ‘me’, ‘?’]split() 方法的第二个参数是一个数字,默认是缺省,缺省时全分隔,也可以用 maxsplit 来指定拆分次数。# 按位置传参l.split(’ ‘,3)>>> [‘Hi’, ’there’, ‘,’, ‘my name is Python 猫\nDo you like me ?\n’]# 指定传参l.split(maxsplit=3)>>> [‘Hi’, ’there’, ‘,’, ‘my name is Python 猫\nDo you like me ?\n’]# 错误用法l.split(3)—————TypeError Traceback (most recent call last)<ipython-input-42-6c16d1a50bca> in <module>()—-> 1 l.split(3)TypeError: must be str or None, not intsplit() 方法是从左往右遍历,与之相对,rsplit() 方法是从右往左遍历,比较少用,但是会有奇效。拆分字符串还有一种方法,即 splitlines() ,这个方法会按行拆分字符串,它接收一个参数 True 或 False ,分别决定换行符是否会被保留,默认值 False ,即不保留换行符。# 默认不保留换行符’ab c\n\nde fg\rkl\r\n’.splitlines()>>> [‘ab c’, ‘’, ‘de fg’, ‘kl’]‘ab c\n\nde fg\rkl\r\n’.splitlines(True)>>> [‘ab c\n’, ‘\n’, ‘de fg\r’, ‘kl\r\n’]2. 替换字符串替换字符串包括如下场景:大小写替换、特定符号替换、自定义片段替换……再次说明,字符串是不可变对象,以下操作并不会改变原有字符串。以上这些方法都很明了,使用也简单,建议你亲自试验一下。这里只说说 strip() 方法,它比较常用,可以去除字符串前后的空格,不仅如此,它还可以删除首末位置的指定的字符。s = ‘Hello world’s.strip(’*’) >>> ‘Hello world'3. 查找字符串查找字符串中是否包含某些内容,这是挺常用的操作。Python 中有多种实现方式,例如内置的 find() 方法,但是这个方法并不常用,因为它仅仅告诉你所查找内容的索引位置,而在通常情况下,这个位置并不是我们的目的。find() 方法与 index() 方法的效果一样,它们的最大的区别只在于,找不到内容时的返回值不同,一个返回 -1,一个抛出异常 :s = ‘Hello world’s.find(‘cat’) >>> -1s.index(‘cat’) >>> ValueError Traceback (most recent call last)<ipython-input-55-442007c50b6f> in <module>()—-> 1 s.index(‘cat’)ValueError: substring not found以上两个方法,只能用来满足最简单的查找需求。在实战中,我们常常要查找特定模式的内容,例如某种格式的日期字符串,这就得借助更强大的查找工具了。正则表达式和 re 模块就是这样的工具,正则表达式用来定制匹配规则,re 模块则提供了 match() 、find() 及 findall() 等方法,它们组合起来,可以实现复杂的查找功能。限于篇幅,今后再对这两大工具做详细介绍,这里有一个简单的例子:import redatepat = re.compile(r’\d+/\d+/\d+’)text = ‘Today is 11/21/2018. Tomorrow is 11/22/2018.‘datepat.findall(text)>>> [‘11/21/2018’, ‘11/22/2018’]4. 字符判断判断字符串是否(只)包含某些字符内容,这类使用场景也很常见,例如在网站注册时,要求用户名只能包含英文字母和数字,那么,当校验输入内容时,就需要判断它是否只包含这些字符。其它常用的判断操作,详列如下:5. 字符串不可以做的事上文内容都是 Python 字符串特有的操作方法,相信读完之后,你更清楚知道 Python 能够做什么了。但是,这还不足以回答本文标题的问题——你真的知道 Python 的字符串怎么用吗?这些特有的操作方法,再加上之前文章提到的序列共有的操作、字符串读写文件、字符串打印、字符串Intern机制等等内容,才差不多能够回答这个问题。尽管如此,为了体现严谨性,我试着再聊聊“Python 字符串不可以做的事”,从相反的维度来补充回答这个问题。下面是开拓思维,进行头脑风暴的时刻:(1)受限的序列与典型的序列类型相比,字符串不具备列表的如下操作:append()、clear()、copy()、insert()、pop()、remove(),等等。这是为什么呢?有几个很好理解,即append()、insert()、pop() 和 remove(),它们都是对单个元素的操作,但是,字符串中的单个元素就是单个字符,通常没有任何意义,我们也不会频繁对其做增删操作,所以,字符串没有这几个方法也算合理。列表的 clear() 方法会清空列表,用来节省内存空间,其效果等同于 anylist[:] = [] ,但是,奇怪的是,Python 并不支持清空/删除操作。首先,字符串没有 clear() 方法,其次,它是不可变对象,不支持这种赋值操作 anystr[:] = ’’ ,也不支持 del anystr[:] 操作:s = ‘Hello world’s[:] = ‘’>>> 报错:TypeError: ‘str’ object does not support item assignmentdel s[:]>>> 报错:TypeError: ‘str’ object does not support item deletion当然,你也别想通过 del s 来删除字符串,因为变量名 s 只是字符串对象的引用 (挖坑,以后写写这个话题),只是一个标签,删除标签并不会直接导致对象实体的消亡。如此看来,想要手动清空/删除 Python 字符串,似乎是无解。最后还有一个 copy() 方法,这就是拷贝嘛,可是字符串也没有这个方法。为什么呢?难道拷贝字符串的场景不多么?在这点上,我也没想出个所以然来,搁置疑问。通过以上几个常用列表操作的比较,我们可以看出字符串这种序列是挺受限的。列表可以看成多节车厢链接成的火车,而字符串感觉就只像多个座椅联排成的长车厢,真是同源不同相啊。(2)比就比,谁怕谁接下来,又到了 Python 字符串与 Java 字符串 PK 的时刻。在上一篇文章《你真的知道Python的字符串是什么吗?》中,它们已经在对象定义的角度切磋了两回合,胜利的天平倒向了 Python,这次看看会比出个啥结果吧。Java 中有 比较字符串 的方法,即 compareTo() 方法与 equals() 方法,前一个方法逐一比较两个字符串的字符编码,返回一个整型的差值,后一个方法在整体上比较两个字符串的内容是否相等。Python 字符串没有这两个单独的方法,但要实现类似的功能却很简便。 先看例子:myName = “Python猫"cmpName = “world"newName = myName# 直接用比较符号进行comparemyName > cmpName >>> FalsemyName == newName>>> TruecmpName != newName>>> True# 比较是否同一对象myName is cmpName>>> FalsemyName is newName>>> True上例中,如果把赋值的字符串换成列表或者其它对象,这些比较操作也是可以进行的。也就是说,作比较的能力 是 Python 公民们的一项基本能力,并不会因为你是字符串就给你设限,或者给你开特权。与此类似,Python 公民们自带求自身长度的能力 ,len() 方法是内置方法,可以直接传入任意序列参数,求解长度。Java 中则要求不同的序列对象,只能调用各自的 length() 方法。说个形象的比喻,Python 中共用一把秤,三教九流之辈都能拿它称重,而Java 中有多把秤,你称你的,我称我的,大家“井水不犯河水”。Python 中曾经有 cmp() 方法和 cmp() 魔术方法,但官方嫌弃它们鸡肋,所以在Python 3 中移除掉了。虽然在 operator 模块中还为它留下了一脉香火,但保不定哪天就会彻底废弃。import operatoroperator.eq(‘hello’, ’name’)>>> Falseoperator.eq(‘hello’, ‘hello’)>>> Trueoperator.gt(‘hello’, ’name’)>>> Falseoperator.lt(‘hello’, ’name’)>>> True(3)墙上的门在 Java 中,字符串还有一个强大的 valueOf() 方法,它可以接收多种类型的参数,如boolean、char、char数组、double、float、int等等,然后返回这些参数的字符串类型。 例如,要把 int 转为字符串,可以用 String.valueOf(anynum) 。Python 字符串依然没有这个单独的方法,但要实现相同的功能却很简便。对Python来说,不同的数据类型转换成字符串,那是小菜一碟,例如:str(123) >>> ‘123’str(True) >>> ‘True’str(1.22) >>> ‘1.22’str([1,2]) >>> ‘[1, 2]‘str({’name’:‘python’, ‘sex’:‘male’})>>> “{’name’: ‘python’, ‘sex’: ‘male’}“而从字符串转换为其它类型,也不难,例如,int(‘123’) 即可由字符串'123’ 得到数字 123。对比 Java,这个操作要写成 Integer.parseInt(‘123’) 。在Java 的不同数据类型之间,那道分隔之墙矗立得很高,仿佛需要借助一座更高的吊桥才能沟通两边,而在灵活的 Python 里,你可以很方便地打开墙上的那扇门,来往穿越。小结一下,跟 Java 相比,Python 字符串确实没有几项方法,但是事出有因,它们的天赋能力可不弱,所有这些操作都能简明地实现。一方面,Python 字符串做不到某些事,但是另一方面,Python 可以出色地做成这些事,孰优孰劣,高下立判。6. 总结写文章贵在善始善终,现在给大家总结一下:本文主要介绍 Python 字符串特有的操作方法,比如它的拼接、拆分、替换、查找及字符判断等使用方法,从正向回答,Python 字符串能做什么?最后,我们还从反向来回答了 Python 字符串不能做什么?有些不能做,实际上是 不为,是为了在其它地方更好地作为,归根到底,应该有的功能,Python 字符串全都有了。本文中依然将 Python 与 Java 做了比较,有几项小小的差异,背后反映的其实是,两套语言系统在世界观上的差异。古人云,以铜为镜,可以正衣冠。那么,在编程语言的世界里,以另一种语言为镜,也更能看清这种语言的面貌。希望这种跨语言的思维碰撞,能为你擦出智慧的火花。最后是福利时刻:本公众号(Python猫)由清华大学出版社赞助,将抽奖送出两本新书《深入浅出Python机器学习》,截止时间到11月29日18:18,点击 这个链接,马上参与吧。—————–本文原创并首发于微信公众号【Python猫】,后台回复“爱学习”,免费获得20+本精选电子书。扩展阅读: 《详解Python拼接字符串的七种方式》《你真的知道Python的字符串是什么吗?》Java字符串比较方法:https://blog.csdn.net/barryha…Python3为何取消cmp方法:https://www.zhihu.com/questio… ...

November 24, 2018 · 2 min · jiezi

限时抽奖,送2本《深入浅出Python机器学习》

福利规则: 1、本公众号(Python猫)读者,在后台回复 1123 获取抽奖码,即可参与2、书籍:清华大学出版社,《深入浅出Python机器学习》2本3、活动截止时间:2018年11月29日 18:18 4、附加规则:公布结果后24小时,若出现中奖者联系不上、或其自愿放弃领奖的情况,我会将奖品赠送给有过互动(高质量留言)的读者,数量有限,标准随心。5、附加福利:添加图书编辑 暖暖 的微信号(q703882),加入交流群,可以获得与书籍作者 段小手 交流的机会,更有其他作者直播与书籍抽奖等多重福利。福利背景: 前几天,我被邀入一个 Python 学习群,进去后发现这是 清华出版社 图书编辑建的群。群里不仅有Python 学习者,还有已出版书籍的作者。今天送出的《深入浅出Python机器学习》,其作者 段小手 就在这个群里。前几天,他给群友做了一场题为《Python如何帮助我们找到更好的工作》的语音直播。我听了挺有启发。所以,当出版社编辑找我,说可以送书做活动,我毫不犹豫就答应下来了。这次,我不打算写成荐书系列了,因为每期荐书都得花费太多时间阅读和搜集材料,几天前刚写完一期,所以我想放松一次,就简单送送福利好了。简单罗列一下基本信息:作者:段小手,君兮科技创始人,毕业于北京大学。具有10余年国内一线互联网/电子商务公司项目管理经验。其负责的跨境电子商务项目曾获得“国家发改委电子商务示范项目”“中关村现代服务业试点项目”“北京市信息化基础设施提升项目”“北京市外贸综合公共平台”等专项政策支持。目前重点研究领域为机器学习和深度学习等方面。 书籍简介:本书内容涵盖了有监督学习、无监督学习、模型优化、自然语言处理等机器学习领域所必须掌握的知识,从内容结构上非常注重知识的实用性和可操作性。全书采用由浅入深、循序渐进的讲授方式,完全遵循和尊重初学者对机器学习知识的认知规律。本书适合有一定程序设计语言和算法基础的读者学习使用。本公众号创建时间很短,虽然在荐书《黑客与画家》的时候办过一次送书活动。但是,像今天这种“商业合作”的活动,还是第一次办。曾经有过4个“广告商”联系我,但是因为对其“广告内容”不够认可,经过深思熟虑,我最后都婉拒掉了。送 技术书籍 的活动,我十分欢迎,这类的“广告”是真正的福利,对读者、出版社和本公众号是三方共赢。以后有其它出版社来赞助活动的话,我必然还是优先考虑合作的。有些同行公众号在送书的时候,会要求读者给赞赏、留言集赞、或者转发朋友圈,这些小手段或许能有效筛选“忠实”读者,或许能扩大活动效果,随他们去吧。这些做法,我就不跟风了。不过,我也有小小的私心:如果你觉得本公众号的文章不错,请不吝分享给其他可能需要的小伙伴,在此,我先行感谢了! 关于书籍,这里有前六章试读:http://www.tup.tsinghua.edu.c…若想直接购买,京东有售:https://item.jd.com/12401357….

November 24, 2018 · 1 min · jiezi