阿布云

你所需要的,不仅仅是一个好用的代理。

编写高效且优雅的 Python 代码

阿布云 发表于

p1.png

使用生成器

考虑使用生成器来改写直接返回列表的函数

# 定义一个函数,其作用是检测字符串里所有 a 的索引位置,最终返回所有 index 组成的数组

def get_a_indexs(string):

result=[]

forindex,letterinenumerate(string):

ifletter=='a':

result.append(index)

returnresult

用这种方法有几个小问题:

每次获取到符合条件的结果,都要调用append方法。但实际上我们的关注点根本不在这个方法,它只是我们达成目的的手段,实际上只需要index就好了

返回的result可以继续优化

数据都存在result里面,如果数据量很大的话,会比较占用内存

因此,使用生成器generator会更好。生成器是使用yield表达式的函数,调用生成器时,它不会真的执行,而是返回一个迭代器,每次在迭代器上调用内置的next函数时,迭代器会把生成器推进到下一个yield表达式:

def get_a_indexs(string):

forindex,letterinenumerate(string):

ifletter=='a':

yieldindex

获取到一个生成器以后,可以正常的遍历它:

string='this is a test to find a\' index'

indexs=get_a_indexs(string)

# 可以这样遍历

foriinindexs:

print(i)

# 或者这样

try:

whileTrue:

print(next(indexs))

exceptStopIteration:

print('finish!')

# 生成器在获取完之后如果继续通过 next() 取值,则会触发 StopIteration 错误

# 但通过 for 循环遍历时会自动捕获到这个错误

如果你还是需要一个列表,那么可以将函数的调用结果作为参数,再调用list方法

results=get_a_indexs('this is a test to check a')

results_list=list(results)

可迭代对象

需要注意的是,普通的迭代器只能迭代一轮,一轮之后重复调用是无效的。解决这种问题的方法是,你可以 定义一个可迭代的容器类 :

classLoopIter(object):

def __init__(self,data):

self.data=data

# 必须在 __iter__ 中 yield 结果

def __iter__(self):

forindex,letterinenumerate(self.data):

ifletter=='a':

yieldindex

这样的话,将类的实例迭代重复多少次都没问题:

string='this is a test to find a\' index'

indexs=LoopIter(string)

print('loop 1')

for_inindexs:

print(_)

# loop 1

# 8

# 23

print('loop 2')

for_inindexs:

print(_)

# loop 2

# 8

# 23

但要注意的是,仅仅是实现__iter__方法的迭代器,只能通过for循环来迭代;想要通过next方法迭代的话则需要使用iter方法:

string='this is a test to find a\' index'

indexs=LoopIter(string)

next(indexs)# TypeError: 'LoopIter' object is not an iterator

iter_indexs=iter(indexs)

next(iter_indexs)# 8

使用位置参数

有时候,方法接收的参数数目可能不一定,比如定义一个求和的方法,至少要接收两个参数:

def sum(a,b):

returna+b

# 正常使用

sum(1,2)# 3

# 但如果我想求很多数的总和,而将参数全部代入是会报错的,而一次一次代入又太麻烦

sum(1,2,3,4,5)# sum() takes 2 positional arguments but 5 were given

对于这种接收参数数目不一定,而且不在乎参数传入顺序的函数,则应该利用位置参数*args:

def sum(*args):

result=0

fornuminargs:

result+=num

returnresult

sum(1,2)# 3

sum(1,2,3,4,5)# 15

# 同时,也可以直接把一个数组带入,在带入时使用 * 进行解构

sum(*[1,2,3,4,5])# 15

但要注意的是,不定长度的参数args在传递给函数时,需要先转换成元组tuple。这意味着,如果你将一个生成器作为参数带入到函数中,生成器将会先遍历一遍,转换为元组。这可能会消耗大量内存:

def get_nums():

fornuminrange(10):

yield num

nums=get_nums()

sum(*nums)# 45

# 但在需要遍历的数目较多时,会占用大量内存

使用关键字参数

关键字参数可提高代码可读性

可以通过关键字参数给函数提供默认值

便于扩充函数参数

定义只能使用关键字参数的函数

普通的方式,在调用时不会强制要求使用关键字参数

# 定义一个方法,它的作用是遍历一个数组,找出等于(或不等于)目标元素的 index

def get_indexs(array,target='',judge=True):

forindex,iteminenumerate(array):

ifjudgeanditem==target:

yield index

elifnotjudgeanditem!=target:

yield index

array=[1,2,3,4,1]

# 下面这些都是可行的

result=get_indexs(array,target=1,judge=True)

print(list(result))# [0, 4]

result=get_indexs(array,1,True)

print(list(result))# [0, 4]

result=get_indexs(array,1)

print(list(result))# [0, 4]

使用 Python3 中强制关键字参数的方式

# 定义一个方法,它的作用是遍历一个数组,找出等于(或不等于)目标元素的 index

def get_indexs(array,*,target='',judge=True):

forindex,iteminenumerate(array):

ifjudgeanditem==target:

yield index

elifnotjudgeanditem!=target:

yield index

array=[1,2,3,4,1]

# 这样可行

result=get_indexs(array,target=1,judge=True)

print(list(result))# [0, 4]

# 也可以忽略有默认值的参数

result=get_indexs(array,target=1)

print(list(result))# [0, 4]

# 但不指定关键字参数则报错

get_indexs(array,1,True)

# TypeError: get_indexs() takes 1 positional argument but 3 were given

使用 Python2 中强制关键字参数的方式

# 定义一个方法,它的作用是遍历一个数组,找出等于(或不等于)目标元素的 index

# 使用 **kwargs,代表接收关键字参数,函数内的 kwargs 则是一个字典,传入的关键字参数作为键值对的形式存在

def get_indexs(array,**kwargs):

target=kwargs.pop('target','')

judge=kwargs.pop('judge',True)

forindex,iteminenumerate(array):

ifjudgeanditem==target:

yield index

elifnotjudgeanditem!=target:

yield index

array=[1,2,3,4,1]

# 这样可行

result=get_indexs(array,target=1,judge=True)

print(list(result))# [0, 4]

# 也可以忽略有默认值的参数

result=get_indexs(array,target=1)

print(list(result))# [0, 4]

# 但不指定关键字参数则报错

get_indexs(array,1,True)

# TypeError: get_indexs() takes 1 positional argument but 3 were given

关于参数的默认值

算是老生常谈了: 函数的默认值只会在程序加载模块并读取到该函数的定义时设置一次

也就是说,如果给某参数赋予动态的值( 比如[]或者{}),则如果之后在调用函数的时候给参数赋予了其他参数,则以后再调用这个函数的时候,之前定义的默认值将会改变,成为上一次调用时赋予的值:

def get_default(value=[]):

returnvalue

result=get_default()

result.append(1)

result2=get_default()

result2.append(2)

print(result)# [1, 2]

print(result2)# [1, 2]

因此,更推荐使用None作为默认参数,在函数内进行判断之后赋值:

def get_default(value=None):

ifvalueisNone:

return[]

returnvalue

result=get_default()

result.append(1)

result2=get_default()

result2.append(2)

print(result)# [1]

print(result2)# [2]

__slots__

默认情况下,Python 用一个字典来保存一个对象的实例属性。这使得我们可以在运行的时候动态的给类的实例添加新的属性:

test=Test()

test.new_key='new_value'

然而这个字典浪费了多余的空间 — 很多时候我们不会创建那么多的属性。因此通过__slots__可以告诉 Python 不要使用字典而是固定集合来分配空间。

classTest(object):

# 用列表罗列所有的属性

__slots__=['name','value']

def __init__(self,name='test',value='0'):

self.name=name

self.value=value

test=Test()

# 此时再增加新的属性则会报错

test.new_key='new_value'

# AttributeError: 'Test' object has no attribute 'new_key'

__call__

通过定义类中的__call__方法,可以使该类的实例能够像普通函数一样调用。

classAddNumber(object):

def __init__(self):

self.num=0

def __call__(self,num=1):

self.num+=num

add_number=AddNumber()

print(add_number.num)# 0

add_number()# 像方法一样的调用

print(add_number.num)# 1

add_number(3)

print(add_number.num)# 4

通过这种方式实现的好处是,可以通过类的属性来保存状态,而不必创建一个闭包或者全局变量。

@classmethod & @staticmethod

资料:

Python @classmethod and @staticmethod for beginner

Difference between staticmethod and classmethod in python

@classmethod和@staticmethod很像,但他们的使用场景并不一样。

类内部普通的方法,都是以self作为第一个参数,代表着通过实例调用时,将实例的作用域传入方法内;

@classmethod以cls作为第一个参数,代表将类本身的作用域传入。无论通过类来调用,还是通过类的实例调用,默认传入的第一个参数都将是类本身

@staticmethod不需要传入默认参数,类似于一个普通的函数

来通过实例了解它们的使用场景:

假设我们需要创建一个名为Date的类,用于储存 年/月/日 三个数据

classDate(object):

def __init__(self,year=0,month=0,day=0):

self.year=year

self.month=month

self.day=day

@property

def time(self):

return"{year}-{month}-{day}".format(

year=self.year,

month=self.month,

day=self.day

)

上述代码创建了Date类,该类会在初始化时设置day/month/year属性,并且通过property设置了一个getter,可以在实例化之后,通过time获取存储的时间:

date=Date('2016','11','09')

date.time# 2016-11-09

但如果我们想改变属性传入的方式呢?毕竟,在初始化时就要传入年/月/日三个属性还是很烦人的。能否找到一个方法,在不改变现有接口和方法的情况下,可以通过传入2016-11-09这样的字符串来创建一个Date实例?

你可能会想到这样的方法:

date_string='2016-11-09'

year,month,day=map(str,date_string.split('-'))

date=Date(year,month,day)

但不够好:

在类外额外多写了一个方法,每次还得格式化以后获取参数

这个方法也只跟Date类有关

没有解决传入参数过多的问题

此时就可以利用@classmethod,在类的内部新建一个格式化字符串,并返回类的实例的方法:

# 在 Date 内新增一个 classmethod

@classmethod

def from_string(cls,string):

year,month,day=map(str,string.split('-'))

# 在 classmethod 内可以通过 cls 来调用到类的方法,甚至创建实例

date=cls(year,month,day)

returndate

这样,我们就可以通过Date类来调用from_string方法创建实例,并且不侵略、修改旧的实例化方式:

date=Date.from_string('2016-11-09')

# 旧的实例化方式仍可以使用

date_old=Date('2016','11','09')

好处:

在@classmethod内,可以通过cls参数,获取到跟外部调用类时一样的便利

可以在其中进一步封装该方法,提高复用性

更加符合面向对象的编程方式

而@staticmethod,因为其本身类似于普通的函数,所以可以把和这个类相关的 helper 方法作为@staticmethod,放在类里,然后直接通过类来调用这个方法。

# 在 Date 内新增一个 staticmethod

@staticmethod

defis_month_validate(month):

returnint(month)<=12andint(month)>=1

将与日期相关的辅助类函数作为@staticmethod方法放在Date类内后,可以通过类来调用这些方法:

month='08'

ifnotDate.is_month_validate(month):

print('{} is a validate month number'.format(month))

创建上下文管理器

上下文管理器,通俗的介绍就是:在代码块执行前,先进行准备工作;在代码块执行完成后,做收尾的处理工作。with语句常伴随上下文管理器一起出现,经典场景有:

with open('test.txt','r')asfile:

forlineinfile.readlines():

print(line)

通过with语句,代码完成了文件打开操作,并在调用结束,或者读取发生异常时自动关闭文件,即完成了文件读写之后的处理工作。如果不通过上下文管理器的话,则会是这样的代码:

file=open('test.txt','r')

try:

forlineinfile.readlines():

print(line)

finally:

file.close()

比较繁琐吧?所以说使用上下文管理器的好处就是,通过调用我们预先设置好的回调,自动帮我们处理代码块开始执行和执行完毕时的工作。而通过自定义类的__enter__和__exit__方法,我们可以自定义一个上下文管理器。

classReadFile(object):

def __init__(self,filename):

self.file=open(filename,'r')

def __enter__(self):

returnself.file

def __exit__(self,type,value,traceback):

# type, value, traceback 分别代表错误的类型、值、追踪栈

self.file.close()

# 返回 True 代表不抛出错误

# 否则错误会被 with 语句抛出

returnTrue

然后可以以这样的方式进行调用:

with ReadFile('test.txt')asfile_read:

forlineinfile_read.readlines():

print(line)

在调用的时候:

with语句先暂存了ReadFile类的__exit__方法

然后调用ReadFile类的__enter__方法

__enter__方法打开文件,并将结果返回给with语句

上一步的结果被传递给file_read参数

在with语句内对file_read参数进行操作,读取每一行

读取完成之后,with语句调用之前暂存的__exit__方法

__exit__方法关闭了文件

要注意的是,在__exit__方法内,我们关闭了文件,但最后返回True,所以错误不会被with语句抛出。否则with语句会抛出一个对应的错误。怎么样才能学好python学好python你需要一个良好的环境,一个优质的开发交流群,群里都是那种相互帮助的人才是可以的,我有建立一个python学习交流群,在群里我们相互帮助,相互关心,相互分享内容,这样出问题帮助你的人就比较多,群号是301,还有056,最后是051,这样就可以找到大神聚合的群,如果你只愿意别人帮助你,不愿意分享或者帮助别人,那就请不要加了,你把你会的告诉别人这是一种分享。