最近在补 Python 进阶的内容,学习资源来自:Python 进阶,是《Intermediate Python》的中译本。这里面的一些内容,对于我来说是比较容易忽略的知识点。趁最近心态涣散,整理一些相对比较简单的内容。
*args 和 **kwargs
和这对老兄弟,经常结伴出现。它们主要用于函数定义,可以将未知数量的参数传递给一个函数。传递的是非键值对的参数列表,而用于传递键值对参数列表。下面是几个例子:
的例子:
In[2]:deftest_var_arg(f_arg,*args):
...:print("first normal arg:",f_arg)
...:forarginargs:
...:print("another arg through *argv:",arg)
...:
In[3]:test_var_arg("cao","qi","95")
firstnormalarg:cao
anotherargthrough*argv:qi
anotherargthrough*argv:95
的例子:
In[5]:defgreet_me(**kwargs):
...:forkey,valueinkwargs.items():
...:print(" == ".format(key,value))
...:
In[6]:greet_me(name="caoqi95")
name==caoqi95
使用场景及顺序:
# 使用 *args
>>>args= ("two",3,5)
>>>test_args_kwargs(*args)
arg1:two
arg2:3
arg3:5
# 使用 **kwargs:
>>>kwargs= {"arg3":3,"arg2":"two","arg1":5}
>>>test_args_kwargs(**kwargs)
arg1:5
arg2:two
arg3:3
# 如果想在一个函数里,同时使用标准参数,*args 和 **kwargs,顺序如下:
function_name(normal_arg,*args,**kwargs)
装饰器(Decorator)
在 Python 中,函数可以作为参数被另一个函数所调用。我们先看看函数作为参数被调用的例子,然后再讲装饰器的内容,这样会比较容易理解。
In[11]:defhi():
...:return"hi, caoqi95"
...:
...:
In[12]:defdoSomeThingBeforeHi(func):
...:print("I am doing some boring work before executing hi() function")
...:print(func())
...:
In[13]:doSomeThingBeforeHi(hi)
Iamdoingsomeboringworkbeforeexecutinghi()function
hi,caoqi95
上面的例子中,就是把函数作为参数,然后被函数调用。其实,装饰器起到的作用也是实现一个函数作为参数被另一个函数调用的功能,即给当前函数增加额外的一些功能。现在,将上面的代码写成装饰器的形式:
In[7]:defdoSomeThingBeforeHi(func):
...:defcall():
...:print("I am doing some boring work before hi() function")
...:print(func())
...:returncall
...:
In[8]:defhi():
...:return"hi, caoqi95"
...:
In[9]:hi=doSomeThingBeforeHi(hi)# 用 doSomeThingBeforeHi 函数装饰 hi 函数
In[10]:hi()
Iamdoingsomeboringworkbeforehi()function
hi,caoqi95
在上面的代码中,函数被当做参数,赋值给了函数。然后在变量后面加上一对小括号执行函数。整个结合起来,可以看出,函数被装饰了,多出了额外的功能。下面再写成专业一点的装饰器,用来表示:
In[1]:defdoSomeThingBeforeHi(func):
...:defcall():
...:print("I am doing some boring work before hi() function")
...:print(func())
...:returncall
...:
...:@doSomeThingBeforeHi
...:defhi():
...:return"hi, caoqi95"
In[2]:hi()
Iamdoingsomeboringworkbeforehi()function
hi,caoqi95
改写完成,可以发现取代了这行代码,使代码变得简洁,符合 Python 的核心价值观。但是,上面的装饰器还会存在一点小问题:
In[3]:print(hi.__name__)
call
运行完发现,函数的名字从变成了。在 Python 中,可以用函数解决这个问题,如下所示:
fromfunctoolsimportwraps
defdoSomeThingBeforeHi(func):
@wraps(func)
defcall():
print("I am doing some boring work before hi() function")
print(func())
returncall
@doSomeThingBeforeHi
defhi():
return"hi, caoqi95"
在这里也可以发现也是一个装饰器。总结到这里,你也可以试着自己写一个装饰器,不会很难的。
对象可变与不可变
Python 中的数据类型包含可变(mutable)与不可变(immutable),有时候会让人很头疼。
首先让我们来看一个例子:
>>>before= ['hi']
>>>after=before
>>>after+= ['hello']
>>>before
['hi','hello']
>>>after
['hi','hello']
再来看一个例子:
>>>before="hi"
>>>after=before
>>>after+="hello"
>>>before
'hi'
>>>after
'hihello'
我们对第一个例子的预期应该同第二个例子一样,变量前后的值是不变的,只有的值会变化:
>>>before= ['hi']
>>>after=before
>>>after+= ['hello']
>>>before
['hi']
>>>after
['hi','hello']
但是,实际结果并不符合预期,这是为什么?因为 Pyhton 中对象可变性在作祟。什么是对象可变?就是每当将一个变量赋值为另一个可变类型的变量时,对这个数据的任意改动会同时反映到这两个变量上去。
这种情况只是针对可变数据类型。列表(list)是属于可变类型的,因此 ,第一个例子中的变量前后的值是不一样的。而字符串(string)是属于不可变类型的,所以,第二个例子中的变量的值前后不变。
下面再举一个例子说明,这样可以更理解对象可变这个概念,同时又能够避免一些问题。
defadd_to(num,target=[]):
target.append(num)
returntarget
>>>add_to(1)
[1]
>>>add_to(2)
[1,2]
>>>add_to(3)
[1,2,3]
你期待的函数的表现应该是这个样子的:
defadd_to(num,target=[]):
target.append(num)
returntarget
>>>add_to(1)
[1]
>>>add_to(2)
[2]
>>>add_to(3)
[3]
这也是因为列表是可变对象的原因。上面的函数还暴露出一个问题,即在 Python 中,当函数被定义时,默认参数只会运算一次,而不是每次被调用时都会重新运算。所以,应该避免定义可变类型的参数。如果希望每次调用函数的时候,默认函数都重新运算,那么上面的函数可以改写成下面的形式:
defadd_to(num,target=None):
iftarget==None:
target= []
target.append(num)
returntarget
>>>add_to(1)
[1]
>>>add_to(2)
[2]
>>>add_to(3)
[3]
最后,总结一下 Python 中哪些对象可变,哪些对象不可变。
可变对象:,
不可变对象:,,,
slots 魔法
在 Python 中,每个类都有实例属性。在默认的情况下,会用一个字典来保存一个对象的实例属性。但是,有时候这样会浪费很多内存。尤其是在创建的对象的规模十分大的时候,会非常消耗内存。这是因为 Python 不能在对象创建的时候,直接分配一个固定的内存来保存所有的属性。
但是,可以使用来解决消耗内存的问题。会告诉 Python 不要使用字典,而且只给一个固定集合的属性分配空间。可以看看下面两个例子:
不使用:
classMyclass(object):
def__init__(self,name,identifier):
self.name=name
self.identifier=identifier
使用:
classMyclass(object):
__slots__= ['name','identifier']
def__init__(self,name,identifier):
self.name=name
self.identifier=identifier
下面可以通过使用的扩展模块ipython_memory_usage,来查看内存的使用情况。
首先,安装这个模块:
pipinstallipython_memory_usage
安装成功后,在命令行窗口输入(提前已安装好模块),来开启模式。
E:\Python>ipython
Python3.6.5|Anaconda,Inc.|(default,Mar292018,13:32:41) [MSCv.190064bit(AMD64)]
Type'copyright','credits'or'license'formoreinformation
IPython6.4.0--AnenhancedInteractivePython.Type'?'forhelp.
In[1]:importipython_memory_usage.ipython_memory_usageasimu
In[2]:imu.start_watching_memory()
In[2]used0.3945MiBRAMin35.71s,peaked0.00MiBabovecurrent,totalRAMusage40.01MiB
In[3]:fromslotsimportMyclassasMy1
In[3]used0.0469MiBRAMin0.11s,peaked0.00MiBabovecurrent,totalRAMusage40.05MiB
In[4]:num=1024*256
In[4]used0.0039MiBRAMin0.10s,peaked0.00MiBabovecurrent,totalRAMusage40.06MiB
In[5]:x= [My1(1,1)foriinrange(num)]
In[5]used16.1797MiBRAMin0.28s,peaked0.00MiBabovecurrent,totalRAMusage56.24MiB
In[6]:fromno_slotsimportMyclassasMy2
In[6]used-0.0039MiBRAMin0.11s,peaked0.00MiBabovecurrent,totalRAMusage56.23MiB
In[7]:x= [My2(1,1)foriinrange(num)]
In[7]used28.6367MiBRAMin0.31s,peaked0.00MiBabovecurrent,totalRAMusage84.87MiB
首先在的目录下,创建和这两个文件,内容和上面两个代码块的内容一样。然后再开启模式,运行查看个别的占用内存情况。可以发现,的情况,占用了 28.6 MB;而的情况,占用了 16.2 MB。相比之下,减少了不少内存。
领取专属 10元无门槛券
私享最新 技术干货