3款工具,可以让你的Python代码更加优雅、可读、直观、易维护。Python提供了一组独特的工具和语言特性,使您的代码更加优雅、可读和直观。针对正确的问题选择正确的工具,您的代码将更易于维护。在本文中,我们将研究其中的三种工具:魔法方法、迭代器和生成器以及方法魔法。魔法方法魔法方法可以被认为是Python管道。它们被称为“低级”方法,用于某些内置方法、符号和操作。您可能熟悉的常见魔术方法是__init__(),当我们想要初始化一个类的新实例时调用它。您可能已经看到其他常见的魔术方法,如__str__和__repr__。Python中有一整套神奇的方法,通过实现其中的一些,我们可以修改对象的行为,甚至使其表现得像数字、列表或字典等内置数据类型。让我们创建一个Money类,例如:self):return'%s%.2f'%(self.symbol,self.amount)defconvert(self,other):"""将其他金额转换为我们的货币"""new_amount=(other.amount/self.currency_rates[other.symbol]*self.currency_rates[self.symbol])returnMoney(self.symbol,new_amount)此类定义定义给定货币符号和汇率的货币汇率,并指定一个初始化程序(也称为构造函数),并实现了__repr__,所以当我们打印这个类时,我们会看到一个友好的表示形式,如$2.00,它是Money('$',2.00)的一个实例,带有货币符号和amount。最重要的是,它定义了一种方法,允许您使用不同汇率在不同货币之间进行转换。打开Pythonshell,假设我们定义了两种不同货币的食物成本如下:>>>soda_cost=Money('$',5.25)>>>soda_cost$5.25>>>pizza_cost=Money('',7.99)>>>pizza_cost7.99我们可以使用魔术方法使此类的实例相互交互。假设我们希望能够将此类的两个实例加在一起,即使它们是不同的货币。为此,我们可以在Money类上实现__add__魔法方法:"new_amount=self.amount+self.convert(other).amountreturnMoney(self.symbol,new_amount)现在我们可以非常直观地使用这个类:>>>soda_cost=Money('$',5.25)>>>pizza_cost=Money('',7.99)>>>soda_cost+pizza_cost$14.33>>>pizza_cost+soda_cost12.61当我们将两个实例相加时,我们得到第一个中定义的货币符号表示的结果。所有转换都在后台无缝完成。如果我们愿意,我们还可以实现__sub__进行减法,__mul__进行乘法等。阅读模拟数字类型或魔术方法指南以获取更多信息。我们了解到__add__映射到内置运算符+。其他魔术方法可以映射到[]等符号。比如在字典中通过index或者key获取item,实际使用的是__getitem__方法:>>>d={'one':1,'two':2}>>>d['two']2>>>d.__getitem__('two')2一些魔术方法甚至映射到内置函数,例如__len__()映射到len()。classAlphabet:letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ'def__len__(self):returnlen(self.letters)>>>my_alphabet=Alphabet()>>>len(my_alphabet)26个自定义迭代器,适用于新手和有经验的Python开发人员迭代器是一个非常强大但令人困惑的主题。许多内置类型,例如列表、集合和字典,已经实现了允许它们在后台迭代的协议。这使我们可以轻松地迭代它们。>>>对于['Pizza','Fries']中的食物:print(food+'.Yum!')Pizza.百胜!薯条。百胜!我们如何迭代我们自己的自定义类?首先,让我们澄清一些术语。要成为一个可迭代对象,一个类需要实现__iter__()__iter__()方法需要返回一个迭代器要成为一个迭代器,一个类需要实现__next__()(或Python2中的next()),当没有要迭代更多项目,必须抛出StopIteration异常。称呼!听起来很复杂,但是一旦你记住了这些基本概念,你就可以随时迭代。我们什么时候想使用自定义迭代器?让我们想象一个场景,我们有一个服务器实例在不同的端口上运行不同的服务,如http和ssh。其中一些服务处于活动状态,而另一些则处于非活动状态。类服务器:服务=[{'active':False,'protocol':'ftp','port':21},{'active':True,'protocol':'ssh','port':22},{'active':True,'protocol':'http','port':80},]我们在遍历Server实例的时候,只想遍历那些活跃的服务。让我们创建一个IterableServer类:classIterableServer:def__init__(self):self.current_pos=0def__next__(self):pass#TODO:ImplementandremembertothrowStopIteration首先,我们将当前位置初始化为0。然后,我们定义一个__next__()方法返回下一个项目。我们还将确保在不再返回任何项目时抛出StopIteration。到目前为止这么好!现在,让我们实现__next__()方法。classIterableServer:def__init__(self):self.current_pos=0.#我们将当前位置初始化为0def__iter__(self):#我们可以在这里返回self因为__next__已经实现returnselfdef__next__(self):whileself.current_pos>>deffoo():return'foo'>>>foo()'foo'>>>bar=foo>>>bar()'foo'我们稍后会看到它做了什么。Python提供了一个方便的内置函数,称为getattr(),它接受对象、名称、默认参数并返回对象的属性名称。这种编程风格允许我们访问实例变量和方法。例如:>>>classDog:sound='Bark'defspeak(self):print(self.sound+'!',self.sound+'!')>>>fido=Dog()>>>fido.sound'Bark'>>>getattr(fido,'sound')'Bark'>>>fido.speak>>>>getattr(fido,'speak')<绑定方法Dog.speakof<__main__.Dogobjectat0x102db8828>>>>>fido.speak()Bark!吠!>>>speak_method=getattr(fido,'speak')>>>speak_method()吠!吠!这是一个很酷的技巧,但我们如何在实践中使用getattr呢?让我们看一个示例,我们在其中编写了一个小型命令行工具来动态处理命令。类操作:defsay_hi(self,name):print('Hello,',name)defsay_bye(self,name):print('Goodbye,',name)defdefault(self,arg):print('这个操作不支持。')if__name__=='__main__':operations=Operations()#假设我们做了错误处理命令,argument=input('>').split()func_to_call=getattr(operations,command,operations.default)func_to_call(argument)脚本的输出是:$pythongetattr.py>say_hiNinaHello,Nina>blahblah不支持此操作。接下来,让我们看一下partial。例如,functool.partial(func,*args,**kwargs)允许您返回一个新的部分对象,其行为类似于func,采用参数args和kwargs。如果传入更多args,它们将附加到args。如果传入更多kwargs,它们将扩展并覆盖kwargs。让我们看一个简短的例子:>>>fromfunctoolsimportpartial>>>basetwo=partial(int,base=2)>>>basetwo>>>basetwo('10010')18#这等同于>>>int('10010',base=2)让我们看看这个方法魔法是如何在我喜欢的库agithub的一些示例代码中结合在一起的,这是一个(低名)RESTAPI客户端,具有透明的语法允许您使用最少的配置快速构建任何RESTAPI(不仅仅是GitHub)的原型。我发现这个项目很有趣,因为它非常强大,但只有大约400行Python代码。您可以在大约30行配置代码中添加对任何RESTAPI的支持。agithub知道协议(REST、HTTP、TCP)所需的一切,但它没有考虑上游API。让我们深入了解它的实现。下面是我们如何为GitHubAPI和任何其他相关连接属性定义端点URL的简化版本。在此处查看完整代码。类GitHub(API):def__init__(self,token=None,*args,**kwargs):props=ConnectionProperties(api_url=kwargs.pop('api_url','api.github.com'))self.setClient(Client(*args,**kwargs))self.setConnectionProperties(props)然后,一旦配置了访问令牌,就可以开始使用GitHubAPI。>>>gh=GitHub('token')>>>status,data=gh.user.repos.get(visibility='public',sort='created')>>>#^映射到GET/user/repos>>>data...['tweeter','snipey','...']请注意,您需要确保URL拼写正确,因为我们不会验证URL。如果URL不存在或出现其他错误,将返回API抛出的错误。那么,这一切是如何运作的呢?让我们找出来。首先,我们将看一个API类的简化示例:在API类上的每次调用都会调用IncompleteRequest类作为指定的键。classIncompleteRequest:#...其他方法...def__getattr__(self,key):ifkeyinself.client.http_methods:htmlMethod=getattr(self.client,key)returnpartial(htmlMethod,url=self.url)else:self.url+='/'+str(key)returnself__getitem__=__getattr__classClient:http_methods=('get')#还有post、put、patch等。defget(self,url,headers={},**params):returnself.request('GET',url,None,headers)如果最后调用的不是HTTP方法(比如get、post等)),然后返回带有附加路径的IncompleteRequest。否则,它从Client类获取HTTP方法的正确函数并返回部分。如果我们给出一条不存在的路径会怎样?>>>status,data=this.path.doesnt.exist.get()>>>status...404因为__getattr__是__getitem__的别名:>>>owner,repo='nnja','tweeter'>>>>status,data=gh.repos[owner][repo].pulls.get()>>>#^映射到GET/repos/nnja/tweeter/pulls>>>data....#{....}这真是一些神奇的方法!了解更多Python提供了大量工具来使您的代码更优雅、更易于阅读和理解。挑战在于找到适合工作的工具,但我希望本文为您的工具箱添加了一些新工具。而且,如果您想更进一步,可以在我的博客nnja.io上阅读有关装饰器、上下文管理器、上下文生成器和命名元组的内容。当你成为一名更好的Python开发人员时,我鼓励你去那里阅读一些设计良好的项目的源代码。Requests和Flask是两个很好的代码库。