美文网首页
Python 中一些容易忽略的知识点(1)

Python 中一些容易忽略的知识点(1)

作者: 捡个七 | 来源:发表于2019-07-17 16:58 被阅读0次

最近在补 Python 进阶的内容,学习资源来自:Python 进阶,是《Intermediate Python》的中译本。这里面的一些内容,对于我来说是比较容易忽略的知识点。趁最近心态涣散,整理一些相对比较简单的内容。

*args 和 **kwargs

*args**kwargs 这对老兄弟,经常结伴出现。它们主要用于函数定义,可以将未知数量的参数传递给一个函数。*arg 传递的是非键值对的参数列表,而 **kwargs 用于传递键值对参数列表。下面是几个例子:

  • *args 的例子:
In [2]: def test_var_arg(f_arg, *args):
  ...:     print("first normal arg:", f_arg)
  ...:     for arg in args:
  ...:         print("another arg through *argv:", arg)
  ...:

In [3]: test_var_arg("cao", "qi", "95")
first normal arg: cao
another arg through *argv: qi
another arg through *argv: 95
  • **kwargs 的例子:
In [5]: def greet_me(**kwargs):
   ...:     for key , value in kwargs.items():
   ...:         print("{0} == {1}".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]: def hi():
    ...:     return "hi, caoqi95"
    ...:
    ...:

In [12]: def doSomeThingBeforeHi(func):
    ...:     print("I am doing some boring work before executing hi() function")
    ...:     print(func())
    ...:

In [13]: doSomeThingBeforeHi(hi)
I am doing some boring work before executing hi() function
hi, caoqi95

上面的例子中,就是把函数 hi 作为参数,然后被函数 doSomeThingBeforeHi(func) 调用。其实,装饰器起到的作用也是实现一个函数作为参数被另一个函数调用的功能,即给当前函数增加额外的一些功能。现在,将上面的代码写成装饰器的形式:

In [7]: def doSomeThingBeforeHi(func):
   ...:     def call():
   ...:         print("I am doing some boring work before hi() function")
   ...:         print(func())
   ...:     return call
   ...:
In [8]: def hi():
   ...:     return "hi, caoqi95"
   ...:
In [9]: hi = doSomeThingBeforeHi(hi) # 用 doSomeThingBeforeHi 函数装饰 hi 函数
In[10]: hi()
I am doing some boring work before hi() function
hi, caoqi95

在上面的代码中,hi 函数被当做参数,赋值给了 doSomeThingBeforeHi 函数。然后在 hi 变量后面加上一对小括号执行函数。整个结合起来,可以看出,hi 函数被装饰了,多出了额外的功能。下面再写成专业一点的装饰器,用 @ 来表示:

In [1]: def doSomeThingBeforeHi(func):
   ...:     def call():
   ...:         print("I am doing some boring work before hi() function")
   ...:         print(func())
   ...:     return call
   ...:   
   ...: @doSomeThingBeforeHi
   ...: def hi():
   ...:     return "hi, caoqi95"

In [2]: hi()
I am doing some boring work before hi() function
hi, caoqi95

改写完成,可以发现 @doSomeThingBeforeHi 取代了 hi = doSomeThingBeforeHi(hi) 这行代码,使代码变得简洁,符合 Python 的核心价值观。但是,上面的装饰器还会存在一点小问题:

In [3]: print(hi.__name__)
call

运行完发现,hi() 函数的名字从 hi 变成了 call。在 Python 中,可以用 wraps 函数解决这个问题,如下所示:

from functools import wraps

def doSomeThingBeforeHi(func):
    @wraps(func)
    def call():
        print("I am doing some boring work before hi() function")
        print(func())
    return call

@doSomeThingBeforeHi
def hi():
    return "hi, caoqi95"

在这里也可以发现 wraps() 也是一个装饰器。总结到这里,你也可以试着自己写一个装饰器,不会很难的。

对象可变与不可变

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 变量前后的值是不变的,只有 after的值会变化:

>>> before = ['hi']
>>> after = before
>>> after += ['hello']
>>> before
['hi']
>>> after 
['hi', 'hello']

但是,实际结果并不符合预期,这是为什么?因为 Pyhton 中对象可变性在作祟。什么是对象可变?就是每当将一个变量赋值为另一个可变类型的变量时,对这个数据的任意改动会同时反映到这两个变量上去

这种情况只是针对可变数据类型。列表(list)是属于可变类型的,因此 ,第一个例子中的before 变量前后的值是不一样的。而字符串(string)是属于不可变类型的,所以,第二个例子中的 before 变量的值前后不变。

下面再举一个例子说明,这样可以更理解对象可变这个概念,同时又能够避免一些问题。

def add_to(num, target=[]):
    target.append(num)
    return target

>>> add_to(1)
[1]
>>> add_to(2)
[1, 2]
>>> add_to(3)
[1, 2, 3]

你期待的函数的表现应该是这个样子的:

def add_to(num, target=[]):
    target.append(num)
    return target

>>> add_to(1)
[1]
>>> add_to(2)
[2]
>>> add_to(3)
[3]

这也是因为列表是可变对象的原因。上面的函数还暴露出一个问题,即在 Python 中,当函数被定义时,默认参数只会运算一次,而不是每次被调用时都会重新运算。所以,应该避免定义可变类型的参数。如果希望每次调用函数的时候,默认函数都重新运算,那么上面的函数可以改写成下面的形式:

def add_to(num, target=None):
    if target == None:
        target = []
    target.append(num)
    
    return target

>>> add_to(1)
[1]
>>> add_to(2)
[2]
>>> add_to(3)
[3]

最后,总结一下 Python 中哪些对象可变,哪些对象不可变。

  • 可变对象:dictlist
  • 不可变对象:stringintfloattuple

slots 魔法

在 Python 中,每个类都有实例属性。在默认的情况下,会用一个字典来保存一个对象的实例属性。但是,有时候这样会浪费很多内存。尤其是在创建的对象的规模十分大的时候,会非常消耗内存。这是因为 Python 不能在对象创建的时候,直接分配一个固定的内存来保存所有的属性。

但是,可以使用 __slots_ 来解决消耗内存的问题。__slots__ 会告诉 Python 不要使用字典,而且只给一个固定集合的属性分配空间。可以看看下面两个例子:

  • 不使用 __slots__ :

    class Myclass(object):
        def __init__(self, name, identifier):
            self.name = name
            self.identifier = identifier
    
  • 使用 __slots__

    class Myclass(object):
        __slots__ = ['name', 'identifier']
        def __init__(self, name, identifier):
            self.name = name
            self.identifier = identifier
    

下面可以通过使用 Ipython 的扩展模块 ipython_memory_usage,来查看内存的使用情况。

首先,安装这个模块:

pip install ipython_memory_usage

安装成功后,在命令行窗口输入 ipython (提前已安装好 ipython 模块),来开启 IPython 模式。

E:\Python>ipython
Python 3.6.5 |Anaconda, Inc.| (default, Mar 29 2018, 13:32:41) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import ipython_memory_usage.ipython_memory_usage as imu

In [2]: imu.start_watching_memory()
In [2] used 0.3945 MiB RAM in 35.71s, peaked 0.00 MiB above current, total RAM usage 40.01 MiB

In [3]: from slots import Myclass as My1
In [3] used 0.0469 MiB RAM in 0.11s, peaked 0.00 MiB above current, total RAM usage 40.05 MiB

In [4]: num = 1024*256
In [4] used 0.0039 MiB RAM in 0.10s, peaked 0.00 MiB above current, total RAM usage 40.06 MiB

In [5]: x = [My1(1,1) for i in range(num)]
In [5] used 16.1797 MiB RAM in 0.28s, peaked 0.00 MiB above current, total RAM usage 56.24 MiB

In [6]: from no_slots import Myclass as My2
In [6] used -0.0039 MiB RAM in 0.11s, peaked 0.00 MiB above current, total RAM usage 56.23 MiB

In [7]: x = [My2(1,1) for i in range(num)]
In [7] used 28.6367 MiB RAM in 0.31s, peaked 0.00 MiB above current, total RAM usage 84.87 MiB

首先在 E:\Python 的目录下,创建 slots.pyno_slots.py 这两个文件,内容和上面两个代码块的内容一样。然后再开启 Ipython 模式,运行查看个别的占用内存情况。可以发现,no_slots.py 的情况,占用了 28.6 MB;而 slots.py 的情况,占用了 16.2 MB。相比之下,减少了不少内存。

相关文章

  • Python 中一些容易忽略的知识点(1)

    最近在补 Python 进阶的内容,学习资源来自:Python 进阶,是《Intermediate Python》...

  • Python 中一些容易忽略的知识点(2)

    最近在补 Python 进阶的内容,学习资源来自:Python 进阶,是《Intermediate Python》...

  • 微信小程序中的一些小问题

    本文持续更新中......本文总结一下在微信小程序中,比较容易忽略掉的一些小的知识点,在这里做一个汇总整理:1、整...

  • JavaScript中容易忽略的知识点

    本文整理了JavaScript中容易出错,或者易被忽略的知识点。 1. String是不可变类型 字符串类型Str...

  • python输出杨辉三角

    知识点 1. python中的生成器 generator python生成器 2. python中-1索引表示容器...

  • python核心编程学习笔记(1-5章)

    为了打好python基础,特地重新看了python核心编程,把一些容易忽略但是比较重要的点做了摘录,放到这里分享 ...

  • c#知识点

    记录自己在学习c#遇到的知识点(容易忽略容易忘记得,或一些小技巧) using结构只是保证可以调用 cmd.Dis...

  • Java小知识点

    --记录容易出错或忽略的地方、小知识点等 1) Integer.parseInt(String s, int ra...

  • js基本概念

    这里主要和大家谈一些不容易注意或者平时没有系统整理过的知识点,经常用但是最总是被忽略的 1.语法 1.区分大小写 ...

  • python中的奇技淫巧--(新手项)

    长期更新Python中的小技巧和一些知识点,面向新手,很多特性基于python3.x Python所应该遵循的代码...

网友评论

      本文标题:Python 中一些容易忽略的知识点(1)

      本文链接:https://www.haomeiwen.com/subject/wxbrsqtx.html