在前面的几篇文章中分别介绍了Python的迭代器,生成器装饰器,把这三样搞定后,其他的就很轻松了.

本文来看一下Python的其他特性.只有熟练掌握这些特性后,才能写出pythonic的代码.

赋值

有一个数组a = [1,1,1,1],现在想创建另一个数组[2,1,1,1],很容易想到将a复制一份,然后把第一个值改成2就可以了:

In [5]:
a = [1,1,1,1]

b = a
b[0] = 2

print('a=',a)
print('b=',b)

结果出乎预料,我们没想改a,只想复制一份a给到b,但是a却被改变了。

在Python中要格外注意这种情况,Python的赋值是传址。这和其他的主流语言都不一样。比如说b=a,是将b指向a的内存,而不是复制一份a的值给到b.

来看一种对传址的错误理解:

In [4]:
a = [1,1,1,1]

b = a
b = 2

print('a=',a)
print('b=',b)
a= [1, 1, 1, 1]
b= 2

既然是传址,b = a后,a,b应该指向同一块内存,那么a,b应该都被修改为2才对啊?

Python的赋值不能用“修改”来理解,Python不会去修改原来的内存,所有的赋值都是改变指向的内存,比如将变量a改为2,并不是将a原来的内存修改掉,而是将2的地址赋给a:

In [6]:
a = 1
print(id(a))
a = 2
print(id(a))
139969984083296
139969984083328

我们看到两次赋值a的内存地址是不相同的,所以Python的赋值并不会去修改原来的内存,而是将变量指向另外的内存。

函数传值

在函数传值的时候,Python也没有将内存复制一份,而是函数里的变量和函数外的变量指向同一块内存:

In [13]:
def test(arr):
    arr[0] = 888
    print(arr)
    
a = [1,1,1]
test(a)
print(a)
[888, 1, 1]
[888, 1, 1]

函数外的变量a与函数内的变量arr指向同一个内存,对arr的某一个节点进行赋值时,相当与仅更改了这个节点的内存指向,所以arr的内存指向没有变更,也就发生了函数内外变量同时变化的情况。

如果对arr整体进行赋值相当于更改了arr的内存指向,所以仅会函数内部的变量值更改。

In [15]:
def test(arr):
    arr = [6,6,6]
    print(arr)
    
a = [1,1,1]
test(a)
print(a)
[6, 6, 6]
[1, 1, 1]

字符串格式化

可以使用format函数来填充字符串中的{}

In [1]:
"There are an estimated {} people worldwide in {}".format("7.53 billion", 2019)

枚举

如果想在循环的时候返回计数,可以使用enumerate函数

In [2]:
mylist = ['a','b','c']
for key, value in enumerate(mylist):
        print("{} : {}".format(key, value))

循环字典类型的变量如果想返回键,可以使用items函数

In [3]:
mydict = {'frist':'a','second':'b','third':'c'}
for key, value in mydict.items():
        print("{} : {}".format(key, value))

三元运算符

其他语言的三元运算符都是a?b:c的形式,但Python的三元运算符不是这样的,它是if else的形式:

In [4]:
condition = False
1 if condition else 2
In [5]:
condition = True
1 if condition else 2

推导式

推导式是Python的一大特点,它可以快速生成列表:

In [6]:
[x**2 for x in range(10)]

推导式还可以和判断连用:

In [7]:
[x**2 for x in range(10) if x % 3 is 0]

上下文管理器

python的上下文管理器可以创建一个临时变量,这个变量仅在with下起作用.

如果你使用过其他语言的文件读写,应该知道文件读写分为三个步骤:

  1. 打开文件
  2. 读写文件
  3. 关闭文件,释放资源

而在Python中,完全不用这么麻烦,只需要:

In [8]:
with open('some_file', 'w') as file:
    file.write('hello!')

不需要释放资源么?不需要,因为with语法已经帮你做了.

在进入with的时候会自动触发open类中的__enter__方法,做好初始化操作,然后返回一个变量赋值给as后面的变量;当退出with的时候,会自动触发open类中的__exit__方法,做垃圾清理工作,比如说关闭文件的步骤在这里已经做好了.

所以,能用with的时候尽量使用with,只有好处没有坏处.

For - Else

for循环还有一个else从句,我们大多数人并不熟悉。这个else从句会在循环正常结束时执行。这意味着,循环没有遇到任何break. 一旦你掌握了何时何地使用它,它真的会非常有用。

考虑一个寻找某些东西的程序,当找到的时候会触发break,如果没找到会进入else:

In [ ]:
for item in container:
    if search_something(item):
        # 找到了
        process(item)
        break
else:
    # 没找到
    not_found_in_container()

调试器

Python贴心的给我们提供了调试器,当遇到pdb.set_trace()的时候函数会暂停并等待我们的输入,这时我们可以打印此刻的变量值,也可以执行下面的指令:

命令 作用
b 设置断点
c 继续执行程序
l 查看当前行的代码段
s 进入函数
r 执行代码直到从当前函数返回
q 中止并退出
n 执行下一行
enter 重复执行上一条命令
In [11]:
import pdb

def make_bread():
    for i in range(3):
        pdb.set_trace()

make_bread()

*args 和 **kwargs

这是两个魔法变量,只需要一个例子就能弄明白他们的用法:

In [12]:
def test_var_args(f_arg, *args, **kwargs):
    print("normal arg:", f_arg)
    
    for arg in args:
        print("*args:", arg)

    for key, value in kwargs.items():
        print("**kwargs:{0} == {1}".format(key, value))

test_var_args('driving', 'python', 'eggs', 'test', name='a', address='b', usertype ='c',)

上述函数中只定义了一个常规参数,但是传参的时候传了一大堆.多出来的参数会被*args**kwargs捕获,其中*args会捕获多出的没有指定变量名的参数,而**kwargs捕获到指定变量名的参数.注意,他们使用的时候是有顺序的,同时使用的时候要按照fargs, *args, **kwargs的顺序.

posted @ 2019-01-29 15:36:39
评论加载中...

发表评论