python粘性拓展_Python基础之:拓展解决问题的思路

0、锤子原理

在手里拿着一把锤子的人眼中,世界就像一根钉子。

大多人试图以一种思维模型来解决问题,而其思维往往只来自某一专业学科,

但你必须知道各种重要学科的重要理论。

一一《穷查理宝典》

在过去十年的工作中,我经常看到一些不可思议的代码,这些代码有时候看起来相当的愚蠢。而且大部分时候,这些代码都有非常简单而高效的替代方案。而写出这些代码的人,往往是因为没有掌握相关的基础知识,或者是因为总是用一个思路去解决问题,形成了思维惯性。

要巧妙地解决某些问题,有时候可能需要掌握非常专业和生僻的知识;但大部分时候,你只需要掌握一些非常基础的知识,和一个拓展性的思维。

本文皆在抛砖引玉,用非常基础的Python知识,用不同的思路巧妙地解决相似的问题。

1、判定元素是否存在

在列表在查找一个元素,判定元素是否存在,是一个相当常见的操作。

在贯穿本小节的所有例子中,我们都是为了查找符合某个条件的元素是否存在。如果存在,则做dealWhenFound操作;如果不存在,则做dealWhenNotFound操作。后文中用到这两个函数,我们将直接使用,不再进行声明。

def dealWhenFound(elem):

# 如果元素找到了,做点什么

print("{}is found".format(elem))

def dealWhenNotFound(elem):

# 如果元素没有找到,做点什么

print("{}is not found".format(elem))

假如我们有一个名字列表,现在需要在其中查找某个元素是否存在。通用我们可以这么做:

names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby"]

is_found = False

target = "Tom"

is_found = False

for name in names:

if name == target:

is_found = True

break

if is_found:

dealWhenFound(target)

else;

dealWhenNotFound(target)

这是一份通用可行且非常样板式的代码。但在Python中,我们有更加高效且解决方案。正如本小节的标题所述的,用in关键字就可以了。

if name in names:

dealWhenFound(target)

else:

dealWhenNotFound(target)

in操作符是用来判定一个元素是否存在一个可迭代对象中(list、tuple、dict、set等)。对于这种查找条件比较简单的搜索,思路就是这么简单,甚至不值得一提。但对于稍微复杂一点的查找条件,in就不那么胜任了。

我们把查找条件修改为:判定是否存在以某个字母开头的名字。这个时候,我们就没有办法用in操作符来直接判定了。我们发现反而是第1份for代码,才能更好地解决我们的问题。

prefix = "J"

is_found = False

for name in names:

if name.startswith(prefix): # 判定name是否以J开头

is_found = True

break

if is_found:

dealWhenFound(prefix)

else;

dealWhenNotFound(prefix)

Python考虑到了这种情况的普遍性,为我们提供了for/else结构。

for iter in a_list:

if some_test(iter):

break

else:

# 如果循环结果,且没有break语句被执行,则else块会被执行

在for/else结构中,如果for循环正常结束(即没有break语句被执行),则else下的代码会被执行;否则else下的代码不会被执行。

利用这个特性,我们可以将代码进行如下的优化:

prefix = "J"

for name in names:

if name.startswith(prefix): # 判定name是否以J开头

dealWhenFound(prefix)

break

else:

dealWhenNotFound(prefix)

如果你知道any函数,那你应该知道这份代码还会优化的空间。any函数接受一个可迭代对象(包括生成器)做为参数,并且只要任意一个元素被判定为True,则返回True。配合map,我们的代码可以进一步地简化:

prefix = "J"

if any(map(lambda name: name.startswith(prefix), names)):

dealWhenFound(prefix)

else:

dealWhenNotFound(prefix)

如果你觉得上面这份代码不好理解,我们可以进行拆解。

test_func = lambda name: name.startswith(prefix)

map_obj = map(test_func, names)

if any(map_obj):

dealWhenFound(prefix)

else:

dealWhenNotFound(prefix)

除了本小节用到的一些关键字和函数之外,Python也为我们提供了很多其它便利。在这里我们列举一些比较常用的,但不再深入介绍用法。

in

any

all

for/else

map

reduce

filter

enumerate

zip

2、频数统计

在实际开发过程,统计是另一个常见的需求。

还是以名字列表为例,将首字母相同的名字放在同一个分组(列表)里边。我们很容易想到使用dict数据结构:用首字母做为key,以一个list对象做为value即可。

names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]

groups = dict()

for name in names:

key = name[0]

if key in groups:

groups[key].append(name)

else:

g = [name]

groups[key] = g

使用dict的setdefault函数,上面这段代码可以进行简化:

for name in names:

key = name[0]

groups.setdefault(key, []).append(name)

d.setdefault(key, dft_val)的操作是,检测key是否存在,如果存在则返回value;如果不存在,则将dft_val存储到d[key],并返回dft_val。在上面的例子中, 我们在dict中存储了list对象,所以我们可以通过链式调用,在一行代码里完成比较复杂的操作。

到目前为止,一切都简单到不值得一提。但如果我们把分组的需求改为,编者首字母相同的名字的个数,那就是另一个情况了。这时候dict的value类型是int,我们不可以进行简单的链式操作,所以使用setdfault也就不存在优势了。一个比较直观的实现,还是对第一段代码进行简单的改造:

names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]

groups = dict()

for name in names:

key = name[0]

if key in groups:

groups[key] += 1

else:

groups[key] = 1

或者使用get函数来简化代码:

for name in names:

key = name[0]

val = groups.get(key, 0)

groups[key] = val + 1

对于集合类型的操作,Python提供了一个更加高效便捷的库collections。利用collections,我们可以对代码进行进一步的简化:

import collections

names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]

groups = collections.defaultdict(int)

for name in names:

groups[name[0]] += 1

collections.Counter类为我们提供了统计列表(可迭代对象)元素数量的便利,配合列表解析表达式(list comprehension),我们可以用一行代码就完成统计的操作。

import collections

names = ["Jim", "Tom", "Mary", "Hugo", "Tim", "John", "Alby", "Abigal", "Hamish", "Jeremy"]

groups = collections.Counter(name[0] for name in names)

print(groups)

# 打印结果:

# Counter({'J': 3, 'T': 2, 'H': 2, 'A': 2, 'M': 1})

collections为我们提供了更容易使用的容器类型(如list、tuple、dict等)的子类及其它一些便利。本文只是抛砖引玉,并不打算深入介绍collections的用法。在阅读本文之后,各位读者可自行深入学习。以下两个链接都来自Python官方文档,第一个是英文链接,第二个是中文链接。8.3. collections - High-performance container datatypes - Python 2.7.18 documentation​docs.python.orghttps://docs.python.org/zh-cn/3/library/collections.html​docs.python.org

3、多次条件判定

我们经常会遇到一种情况,在执行特定操作之前,往往需要通过多次的条件判定。只有在所有的条件都满足的情况下,才会进行目标操作。

有一个改名的需要求,只有当名字满足一系列的条件,才可以对名字进行更改;否则提示改名失败的原因。名字需要满足的一系列条件是:

1、长度不得大于10

2、只包含26个英文字母

3、有且只有首字母大写,其它字母都是小写

4、最后一个字母必须是元音字母

我们先为各种判定结果定义一些常量,方便后面使用:

# Python没有enum类型,我们可以通过class来模拟

class EAlterRet:

Succ = 0,

SizeOutOfRange = 1,

InvalidCharacter = 2,

NotCapitalized = 3,

EndWithConsonant = 4,

AlterNameErrors = (

"Succ", # 成功

"SizeOutOfRange", # 太长

"InvalidCharacter", # 非法字符

"NotCapitalized", # 非首字母大写的

"EndWithConsonant", # 未以元音字母结尾

)

第一个实现方式,也就是最容易想到的实现方式,自然是多个if语句了。

import re

class Human:

def __init__(self, name):

self.name = name

def dealWithErrors(self, target, code):

ret = AlterNameErrors[code]

msg = "Alter name to '{}', result:{}".format(target, ret)

print(msg)

def alterName(self, target):

# 长度是否大于10

if len(target) > 10:

self.dealWithErrors(target, EAlterRet.SizeOutOfRange)

return

# 是否存在非法字符

if re.search(r'[^a-zA-z]', target):

self.dealWithErrors(target, EAlterRet.InvalidCharacter)

return

# 是否有且只有首字母大写

if target.lower().capitalize() != target:

self.dealWithErrors(target, EAlterRet.NotCapitalized)

return

# 是否以无意结尾

if not re.search(r'[AEIOUaeiou]$', target):

self.dealWithErrors(target, EAlterRet.EndWithConsonant)

return

# 改名成功

self.name = target

第二种方式是使用类似于do/while(false)的结构。由于Python没有do/while(false)结构,我们可以使用一次for循环来替换。

import re

class Human:

def __init__(self, name):

self.name = name

def alterName(self, target):

error = EAlterRet.Succ

for i in range(0, 1):

if len(target) > 10:

error = EAlterRet.SizeOutOfRange

break

if re.search(r'[^a-zA-z]', target):

error = EAlterRet.InvalidCharacter

break

if target.lower().capitalize() != target:

error = EAlterRet.NotCapitalized

break

if not re.search(r'[AEIOUaeiou]$', target):

error = EAlterRet.EndWithConsonant

break

if error == EAlterRet.Succ: # 条件满足,改名成功

self.name = target

else: # 条件不满足,处理错误

ret = AlterNameErrors[code]

msg = "Alter name to '{}', result:{}".format(target, ret)

print(msg)

使用for/break的好处是,我们可以把错误放到后面统一处理,避免使用重复的错误处理代码。

第三种方式是得用异常。虽然我们这个例子引入异常有点牵强,但举一反三,各位读者在以后的实际开发过程中,就可以多一个思路。

import re

class Human:

def __init__(self, name):

self.name = name

def alterName(self, target):

error = EAlterRet.Succ

try:

if len(target) > 10:

raise Exception(EAlterRet.SizeOutOfRange)

if re.search(r'[^a-zA-z]', target):

raise Exception(EAlterRet.InvalidCharacter)

if target.lower().capitalize() != target:

raise Exception(EAlterRet.NotCapitalized)

if not re.search(r'[AEIOUaeiou]$', target):

raise Exception(EAlterRet.EndWithConsonant)

expect Exception as ex:

ret = AlterNameErrors[ex.args[0]]

msg = "Alter name to '{}', result:{}".format(target, ret)

print(msg)

else:

self.name = target

finally: # 如果有需要的话,可以有finally语句

# 做点别的什么事情

在不考虑效率的情况下,使用异常应该是三种方式中最简洁的方式。使用异常还有一个好处,就是可以在finally中做点别的什么事件。因为无论try中有raise还是有return,finally的语句总是会被执行。也就是说,无论发生什么情况,我们总是可以在finally做一些清理工作,如关闭之前打开的文件、关闭socket、或者打一些日志……

4、猜猜看

猜猜下面的这段代码中,构造函数做了些什么。请在评价区中进行留言和讨论 。

class FancyConstructor:

def __init__(self, a, b, c, d, e, f, g):

self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值