变量的申明
通常情况下,我们申明一个变量
a=1
b='abc'
c=1000000000000000000000L
d=2.3
上面我们分别申明了值为整数、字符串、长整数、浮点数的四个变量。作为解释性语言,我们可以享受这种带来的便利,想想我们的java吧,有int,float,long
当然,上面的长整型,在java中怎么办?
BigDecimal big1 = new BigDecimal("123456789012345678901234567890");
BigDecimal big2 = new BigDecimal("123456789012345678901234567890");
BigDecimal sum = big1.multiply(big2);
System.out.println(sum.toPlainString());
而在python中,就可以很简单的处理
123456789012345678901234567890L*123456789012345678901234567890L
在什么变量的时候需要注意几点
- 标识符的第一个字符必须是字母表中的字母(大写或小写)或者一个下划线(’_ ‘)。
- 标识符名称的其他部分可以由字母(大写或小写)、下划线(’_‘)或数字(0-9)组成。
- 标识符名称是对大小写敏感的。例如,
myname
和myName
不是一个标识符。注意前者中的小写n和后者中的大写N。 - 有效 标识符名称的例子有
i、__my_name、name_23
和a1b2_c3
。 - 无效 标识符名称的例子有
2things、this is spaced out
和my-name
。
再来看看python中的批量命名
a, b, c = (1, 2, 3)
这里申明的a,b,c分别指向了元组[或列表]对应的元素。这叫将序列解包绑定到单独的参数上如:
name,age,address=['robin',10,('aaa',222,'ccc')]
name,age,(city,zcode,road)=['robin',10,('aaa',222,'ccc')]
需要注意变量个数和序列的个数保持一致,否则会抛出ValueError
.以下两个申明方式都有问题
a,b,c=[1,2,3,4]
a,b,c=[1,2]
这种解包不仅适用于元组,列表。对strings,files,iterators
等都可以
a,b,c,d,e='hello'
有时对序列中的多个元素只需要关注特定的几个元素,其他忽略:
_,age,_=['robin',10,('aaa',222,'ccc')]
考虑序列中多个参数,不想一一对应,而采取批量命名的方式
a,b,*c=[1,2,3,4,5] #c=[3,4,5]
a,*b,c=[1,2,3,4,5] #b=[2,3,4]
变量的作用域
先来看两个例子
if True:
d = 10
print d #这里输出10
这里先在if
块内申明了变量d,在块外面使用,这里正确输出。而这在java中是不能这样处理的,出了if
块后申明就失效了
def fun1(a):
print a
a = 10
print a
a = 100
fun1(a)
print a
def fun2():
global b
print b
b = 10
print b
b = 100
fun2()
print b
这里包含了两个函数,其中一个变量用了global
,在函数内改变后即全局的改变,而未有任何修饰的a
则只是局部的改变。
这里有几点需要注意
- python能够改变变量作用域的代码段是
def、class、lamda
if/elif/else、try/except/finally、for/while
并不能涉及变量作用域的更改,也就是说他们的代码块中的变量,在外部也是可以访问的- 变量搜索路径是:
本地变量->全局变量
看下面的函数
def fun4():
global a1
print a1
a1 = 100
print a1
a1=1000
fun4()
print a1
在函数体内,第一次print的时候并没有赋值,将会搜索本地变量,即外部定义的a1=1000
而赋值后则遵循global
左右的范围
传值还是引用
需要明确一点,在python中没有传值一说,所有变量都是传的引用。
def method1():
a = 1;
b = a
b = 2;
print a,b
这里输出的1, 2
而不是2, 2
。来看看具体的过程
a=1
将a的地址指向内存中1的地址,b=a
将b的地址指向于a一样的内存引用,b=2
将b的地址指向新的内存地址2的引用,但a的地址引用并没有改变
没问题了么?那么我们再来看看下面这个函数
def method2():
a = [1]
b = a
b[0] = 2
print a[0],b[0]
那么这个将输出什么,既然都这么问了如果再回答1, 2
肯定对不起观众了,是的输出是2, 2
?!。前面不是说了都是传引用,这是什么情况,且慢且慢
可变参数与不可变参数
前面的问题涉及到python中的可变参数和不可变参数,而在上面的例子中恰好a,b为列表属于可变参数。在python中列表(list)、字典(dictionary)为可变参数,而元组(tuple),字符串,整形为不可变参数。实际上上面可以理解为整个列表指向了一个地址,而b[0]恰恰也操作了这个地址内的元素
再来看看下面的函数
def method3(b = []):
b.append(0)
return b
这里将列表b作为参数传给函数,我们在运行函数几次后来看看列表b的值
print method3()
print method3()
print method3()
你回答对了吗?可惜真相面前了无遁形
[0]
[0, 0]
[0, 0, 0]
说好的引用呢,每次我传递进去的参数可都是空[]
啊?都是坑啊或者我们这样调用
print method3(b=[])
print method3(b=[])
print method3(b=[])
你又答对了吗?
默认入参
是不是又一次感受到了自己的无力,其实认真观察,你会发现上面的例子入参是这样b = []
,这是python函数的默认传值,如果在调用的使用没有传值那么将会使用这个默认值,比如上面的函数可以这样调用method3()
而如果函数这样申明
def method3(b):
b.append(0)
return b
那么在调用时需要指定的参数
def method3(b):
b.append(0)
return b
print method3(b=[])
print method3(b=[])
print method3(b=[])
那么这样输出”正常”了吧?再回到上面的问题,需要了解一个函数dir()
,这可以理解为我们java的内省,通过他我们可以观察到具体的申明,我们来看看上面这个method3()
做了什么
print dir(method3)
这里输出了一个列表,包含的各种属性
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
我们需要关注的是__defaults__
,让我们更详细看下调用的过程
method3()
print method3.__defaults__
method3()
print method3.__defaults__
method3()
print method3.__defaults__
而这就是__defaults__
([0],)
([0, 0],)
([0, 0, 0],)
为了对比,我们来看看另外一个函数
def method4(c, b = [], a = 1):
b.append(0)
a+=1
return b,a
method4(c=1)
print method4.__defaults__
method4(c=2)
print method4.__defaults__
method4(c=3)
print method4.__defaults__
我们可以看看其中__defaults__
的输出,这样我们可以体会可变和不可变的区别、以及默认参数与非默认参数的区别了。其实python在处理函数默认传参时如果申明了默认参数如b=[]
那么会体现在__defaults__
上
以上并不是特意选取了一些奇淫技巧,而是需要深刻理解的,因为上面的场景都是很常见的