关于Python中很酷的功能(例如变量解包、部分函数、枚举迭代)的文章有很多,但是在Python中还有很多要讨论的,所以在这篇文章中,我将尝试展示一些我知道和使用但在其他文章中很少提及的功能。那么让我们开始吧。1.“Sanitize”输入字符串“Sanitize”用户输入的内容,这个问题几乎适用于你写的所有程序。通常将字符转换为小写或大写就足够了,有时你也可以使用正则表达式来完成工作,但对于复杂的情况有更好的方法:user_input="This\nstringhas\tsomewhitespaces...\r\n"character_map={ord('\n'):'',ord('\t'):'',ord('\r'):None}user_input.translate(character_map)#Thisstringhassomewhitespaces..."在这个例子中,你可以看到空白字符'\n'和'\t'被替换为一个空格,而'\r'被完全删除。这是一个微不足道的例子,但我们可以更进一步进一步并使用unicodedata库及其combining()函数生成更大的重映射表并使用它从字符串中删除所有重音符号。2.切片迭代器如果您尝试直接切片迭代器,您将得到一个TypeError说该对象不可订阅,但有一个简单的解决方案:importitertoolss=itertools.islice(range(50),10,20)#forvalins:...使用itertools.islice我们可以创建一个islice对象,它是一个产生我们需要的迭代器。但是有一个重要的警告,它会消耗切片之前和切片对象islice中的所有元素。3.跳过可迭代对象的开头有时你不得不处理某些以可变数量的不必要行(比如注释)开头的文件。itertools再次提供了一个简单的解决方案:string_from_file="""//Author:...//License:...////Date:...Actualcontent..."""importitertoolsforlineinitertools。dropwhile(lambdaline:line.startswith("//"),string_from_file.split("\n")):print(line)这段代码只会打印初始注释部分之后的内容。如果我们只想丢弃迭代器的开头(本例中的注释),并且不知道有多少个,则此方法很有用。4.只支持关键字参数的函数(kwargs)当一个函数需要提供(强制)更清晰的参数时,创建一个只支持关键字参数的函数可能会有用:deftest(*,a,b):passtest("valuefora","valueforb")#TypeError:test()takes0positionalarguments...test(a="value",b="value2")#Works...如你所见,你可以通过在关键字参数之前放置一个参数来轻松解决此问题。显然,如果我们将它们放在参数之前,位置参数也是可能的。5、创建一个支持with语句的对象我们都知道with语句的用法,比如打开文件或者获取锁,但是我们可以自己实现吗?是的,我们可以使用__enter__和__exit__方法来实现上下文管理器协议:classConnection:def__init__(self):...def__enter__(self):#初始化连接...def__exit__(self,type,value,traceback):#Closeconnection...withConnection()asc:#__enter__()executes...#conn.__exit__()executes这是在Python中实现上下文管理最常见的方法,但还有另一种更简单的方法:fromcontextlibimportcontextmanager@contextmanagerdeftag(name):print(f"<{name}>")yieldprint(f"")withtag("h1"):print("这是标题.")上面的代码片段使用contextmanager装饰器实现了内容管理协议。输入with语句时执行标记函数的第一部分(yield之前),然后执行with块,最后执行标记函数的其余部分。5.用__slots__节省内存如果你曾经编写过创建某个类的大量实例的程序,你可能已经注意到你的程序突然需要大量内存。那是因为Python使用字典来表示类实例的属性,这使得它速度很快,但内存效率不是很高。通常这不是问题,但是,如果您的程序遇到问题,您可以尝试使用__slots__:):self.first_name=first_nameself.last_name=last_nameself.phone=phone这里发生的事情是当我们定义__slots__属性时,Python使用小型固定大小的数组而不是字典,这大大减少了实例所需的内存.使用__slots__也有一些缺点——我们不能声明任何新属性,只能使用__slots__中的属性。同样,带有__slots__的类不能使用多重继承。6.限制CPU和内存使用如果你不想优化程序内存或CPU使用,而是想直接限制到一个固定的数量,那么Python也有一个库可以做到:importsignalimportresourceimportos#ToLimitCPUtimedeftime_exceeded(signo,frame):print("CPUexceeded...")raiseSystemExit(1)defset_max_runtime(seconds):#安装信号处理程序并设置资源限制soft,hard=resource.getrlimit(resource.RLIMIT_CPU)resource.setrlimit(resource.RLIMIT_CPU,(seconds,hard))signal.signal(signal.SIGXCPU,time_exceeded)#限制内存使用defset_max_memory(size):soft,hard=resource.getrlimit(resource.RLIMIT_AS)resource.setrlimit(resource.RLIMIT_AS,(size,hard))在这里我们可以看到两个选项来设置最大CPU运行时间和内存使用上限。对于CPU限制,我们首先获取该特定资源(RLIMIT_CPU)的软硬限制,然后将其设置为参数指定的秒数和先前获得的硬限制。最后,我们注册一个信号,如果超过CPU时间,系统将退出。至于内存,我们再次获得软硬限制,并使用带有大小参数的setrlimit和我们获得的硬限制来设置它们。8.控制可以导入的内容有些语言对于导出成员(变量、方法、接口)有非常明显的机制,比如Golang,它只导出大写字母开头的成员。另一方面,在Python中,除非我们使用__all__,否则一切都会被导出:deffoo():passdefbar():pass__all__=["bar"]使用上面的代码片段,我们可以限制fromsome_moduleimport*可以导入的内容在使用的时候。对于上面的示例,通配时只会导入bar。此外,我们可以将__all__设置为空,这样它就无法导出任何内容,并且在从该模块使用通配符导入时会引发AttributeError。9.比较运算符的简单方法为一个类实现所有比较运算符可能很烦人,因为有很多比较运算符-__lt__、__le__、__gt__或__ge__。但如果有更简单的方法呢?functools.total_ordering可以保存:fromfunctoolsimporttotal_ordering@total_orderingclassNumber:def__init__(self,value):self.value=valuedef__lt__(self,other):returnself.valueNumber(3))print(Number(1)=Number(15))print(Number(10)<=Number(2))这究竟是如何工作的?total_ordering装饰器用于简化我们类实例的排序实现。只需定义__lt__和__eq__,这是最低限度的,装饰器将映射其余操作——它为我们填补了空白。10.使用slice函数命名切片。使用大量硬编码的索引值会很快破坏可维护性和可读性。一种方法是对所有索引值使用常量,但我们可以做得更好:#IDFirstNameLastNameline_record="2JohnSmith"ID=slice(0,8)FIRST_NAME=slice(9,21)LAST_NAME=slice(22,27)name=f"{line_record[FIRST_NAME].strip()}{line_record[LAST_NAME].strip()}"#name=="JohnSmith"在这个例子中我们可以避免神秘的索引,这样做的方法是在使用它们之前使用slice函数来命名它们。您还可以通过.start、.stop和.stop属性了解有关切片对象的更多信息。11.运行时提示用户输入密码许多命令行工具或脚本需要用户名和密码才能运行。所以如果你碰巧写了这样的程序,你可能会发现getpass模块很有用:名称,可以提示用户输入密码。但是请注意,并非每个系统都支持隐藏密码。Python会尝试警告您,因此请记住阅读命令行上的警告。12.查找词/字符串的近似匹配现在,关于Python标准库中的一些晦涩的特性。如果您发现自己需要使用Levenshtein距离[2]之类的方法来查找与某些输入字符串相似的单词,Python的difflib可以满足您的需求。importdifflibdifflib.get_close_matches('appel',['ape','apple','peach','puppy'],n=2)#returns['apple','ape']difflib.get_close_matches会找到最好的A“足够好”的比赛。这里,第一个参数与第二个参数匹配。我们还可以提供一个可选参数n,它指定要返回的最大匹配数。另一个可选的关键字参数cutoff(默认值0.6)可以设置字符串匹配分数的阈值。13.使用IP地址如果你必须使用Python进行web开发,你可能会发现ipaddress模块??非常有用。一种情况是从CIDR(无类域间路由)生成一系列IP地址:importipaddressnet=ipaddress.ip_network('74.125.227.0/29')#也适用于IPv6#IPv4Network('74.125.227.0/29')foraddrinnet:print(addr)#74.125.227.0#74.125.227.1#74.125.227.2#74.125.227.3#...另一个不错的功能是检查IP地址的网络成员资格:ip=ipaddress.ip_address("74.125.227.3")ipinnet#Trueip=ipaddress.ip_address("74.125.227.12")ipinnet#False有很多有趣的函数,可以在这里找到[3],我不再赘述。但是请注意,ipaddress模块和其他网络相关模块之间的互操作性有限。例如,您不能将IPv4Network实例视为地址字符串-您需要先使用str转换它们。14.在shell中调试程序崩溃如果你是一个拒绝在Vim或Emacs中使用IDE和代码的人,那么你可能会遇到这样的情况,在IDE中有一个调试器会很有用。你知道吗?你有一个-只需使用python3.8-i运行你的程序-一旦你的程序终止,-i启动一个交互式shell,你可以在其中查看所有变量和调用函数。很好,但是使用实际的调试器(pdb)呢?让我们使用以下程序(script.py):deffunc():return0/0func()并使用python3.8-iscript.py运行脚本:#Scriptcrashes...Traceback(mostrecentcalllast):文件“script.py”,第4行,在func()中文件“script.py”,第2行,在func中return0/0ZeroDivisionError:divisionbyzero>>>importpdb>>>pdb.pm()#Post-mortemdebugger>script.py(2)func()->return0/0(Pdb)我们看到崩溃在哪里,现在让我们设置一个断点:deffunc():breakpoint()#importpdb;pdb.set_trace()return0/0func()现在再次运行:script.py(3)func()->return0/0(Pdb)#westarthere(Pdb)stepZeroDivisionError:divisionbyzero>script.py(3)func()->return0/0(Pdb)大多数时候,打印语句和错误信息足以进行调试,但有时,您需要四处寻找以了解程序内部发生了什么。在这些情况下,您可以设置一个断点,程序将在断点处停止执行,您可以检查程序,例如列出函数参数、评估表达式、列出变量,或者只是一步一步地做。pdb是一个功能齐全的Pythonshell,理论上你可以执行任何东西,但你还需要一些调试命令,可以在这里找到[4]。15.在一个类中定义多个构造函数函数重载是编程语言(不包括Python)中非常普遍的一个特性。即使你不能重载普通函数,你仍然可以使用类方法重载构造函数:@classmethoddeftoday(cls):t=datetime.datetime.now()returncls(t.year,t.month,t.day)d=Date.today()print(f"{d.day}/{d.month}/{d.year}")#14/9/2019你可能想将替代构造函数的所有逻辑放入__init__中,使用args、*kwargs和一堆if语句,而不是使用类方法来解决。这可能有效,但它变得难以阅读和维护。因此,我建议将很少的逻辑放入__init__中,并在单独的方法/构造函数中完成所有操作。这样,结果是为类的维护者和用户提供干净的代码。16.使用装饰器来缓存函数调用你是否曾经编写过一个执行昂贵的I/O操作或一些相当慢的递归的函数,并且该函数可能受益于缓存(存储)其结果?如果有,那么有一个简单的解决方案,即使用functools的lru_cache:fromfunctoolsimportlru_cacheimportrequests@lru_cache(maxsize=32)defget_with_cache(url):try:r=requests.get(url)returnr.text除了:为["https://google.com/"、"https://martinheinz.dev/"、"https://reddit.com/"、"https://google"中的url返回“未找到”.com/","https://dev.to/martinheinz","https://google.com/"]:get_with_cache(url)print(get_with_cache.cache_info())#CacheInfo(hits=2,misses=4,maxsize=32,currsize=4)在这个例子中我们使用了可缓存的GET请求(最多32个缓存结果)。您还可以看到我们可以使用cache_info方法检查函数的缓存信息。装饰器还提供了一个clear_cache方法来使缓存结果无效。我还想指出,这个函数不应该与有副作用的函数一起使用,或者在每次调用时创建可变对象。17.查找可迭代对象中最常出现的元素查找列表中最常见的元素是一个很常见的任务,可以使用for循环和字典(映射),但这不是必需的,因为在collections模块:从collectionsimportCountercheese=["gouda","brie","feta","creamcheese","feta","cheddar","parmesan","parmesan","cheddar","mozzarella","cheddar","gouda","parmesan","camembert","emmental","camembert","parmesan"]cheese_count=Counter(cheese)print(cheese_count.most_common(3))#打印:[('parmesan',4),('cheddar',3),('gou??da',2)]实际上,Counter只是一个将元素映射到事件的字典,因此您可以将它用作普通字典:pythonprint(cheese_count["mozzarella"])¨K40Kcheese_count["mozzarella"]+=1print(cheese_count["mozzarella"])¨K41K除此之外,您可以使用update(more_words)方法轻松添加更多元素。Counter的另一个很酷的功能是您可以使用数学运算(加法和减法)组合和减去Counter的实例。总结在日常的Python编程中,并非所有这些功能都是必不可少的和有用的,但其中一些功能可能会不时派上用场,并且它们还可以简化原本可能冗长且烦人的任务。我还想指出,所有这些特性都是Python标准库的一部分,尽管其中一些在我看来非常像标准库中的非标准内容。所以每当你想用Python实现一些东西,首先要在标准库中查找,如果找不到,那么你可能还没有仔细查找(如果它真的不存在,那么它必须在某些第三方库中)-派对图书馆)。如果你使用Python,那么我认为这里分享的大部分技巧几乎每天都会有用,所以我希望它们能派上用场。另外,如果你对这些Python技巧和窍门有什么想法,或者你知道解决上述问题的更好方法,请告诉我!以上就是本次分享的全部内容。想了解更多python知识,请前往公众号:Python编程学习圈,发“J”免费领取,每日干货分享