python黑法力之参数字传送递,python进阶教程详解
分类:编程应用

python黑魔法之参数传递,python魔法传递

我们都听说,python世界里面,万物皆对象。
怎么说万物皆对象呢?最常见的:

> class A: pass
> a = A()

我们说a是一个对象。
那么既然是万物了,其实A也是对象。3 也是对象。True 也是对象。"hello" 也是对象。
> def Func(): pass o~yee, Func 也是对象。
那么对象之间的传递是如何呢?我们看看下面两个简单的例子:

> a = 3
> b = a
> b = 3 + 1
> print b
4
> print a
3

> a = []
> b = a
> b.append(1)

> print a
[1]
> print b
[1]

不是都说python所有对象都是引用传递吗?为毛第一个b不是3?
好吧。事实是,在python的实现上,对象分为mutable 和 immutable。
这里说的对象分类,是说在实现上具备这样的特性。而非对象本身的属性。
什么是immutable?表示对象本身不可改变。这里先记住一点,是对象 本身 不可改变。
什么叫做对象本身不可改变呢?
一个简单的例子:

> a = (1,2,3)
> a[0] = 10  

TypeError: 'tuple' object does not support item assignment
元组的元素在初始化后就不能再被改变。也就是说,元组对象具备immutable的特性。
那么很简单,相对的,mutable 就是可变的。比如:

> a = {}
> a[0] = 10

有了上面的两个例子,相信大家已经有了基本的认识。
那么,在python世界中,哪些是具备immutable特性,哪些又是mutable的呢?
简单讲,基本类型都是immutable, 而object都是mutable的。
比如说:int, float, bool, tuple 都是immutable。
再比如:dict, set, list, classinstance 都是mutable的。
那么问题来了。既然说基本类型是 immutable ,那么最上面的 b = 3 + 1 为什么不会像tuple一样,抛异常呢?
原因在于,int 对+操作会执行自己的__add__方法。而__add__方法会返回一个新的对象。
事实是,当基本类型被改变时,并不是改变其自身,而是创建了一个新的对象。最终返回的是新的对象的引用。
怎么证明?
我们可以使用一个叫做id()的函数。该函数会返回对象的一个唯一id(目前的实现可以间接理解为对象的内存地址)。
那么我们看下:

> a = 3
> id(a)
140248135804168

> id(3)
140248135804168

> id(4)
140248135804144

> a = a + 1
> id(a)
140248135804144

you see ? 当我们执行a=a+1 后,id(a) 已经改变了。
深究一点,为什么会这样呢?
其实,a = a + 1 经历了两个过程:

  • 1、a + 1
  • 2、a 赋值

第2步只是一个引用的改变。重点在第1步。a + 1,那么python实际上会调用a.__add__(1)。
对于int类型__add__函数的实现逻辑,是创建了一个新的int对象,并返回。
不知道细心的你有没有发现一个特别的地方?
id(4)的值等于id(3+1) 。这个只是python对int,和bool做的特殊优化。不要以为其他基本类型只要值一样都会指向相同的对象。
有个特殊的例子,str。做个简单的实验:

> a = "hello"
> id(a)
4365413232
> b = "hell"
> id(b)
4365386208

> id(a[:-1])
4365410928
> id(a[:-1])
4365413760

看到了吗?虽然值相同,但是还是指向(创建)了不同的对象,尤其是最后两句,哪怕执行相同的操作,依然创建了不同的对象。
python这么傻,每次都创建新的对象?
no no no 他只是缓存了“一些”结果。我们可以再试试看:

> a = "hello"
> ret = set()
> for i in range(1000):
  ret.add(id(a[:-1]))
> print ret
{4388133312, 4388204640}

看到了吗?python还是挺聪明的。不过具体的缓存机制我没有深究过,期望有同学能分享下。
再次回到我们的主题,python中参数是如何传递的?
答案是,引用传递。
平时使用静态语言的同学(比如我),可能会用下面的例子挑战我了:

def fun(data):
  data = 3

a = 100
func(a)

print a # 100

不是尼玛引用传递吗?为毛在执行func(a)后,a 的值没有改变呢?这里犯了一个动态语言基本的错误。
data=3,语义上是动态语言的赋值语句。千万不要和C++之类的语言一个理解。
看看我们传入一个mutable 的对象:

> def func(m):
  m[3] = 100

> a = {}
> print a
{}
> func(a)
> print a
{3:100}

现在同学们知道该如何进行参数传递了吧?好嘞,进阶!
像很多语言如C++,js,swift... 一样,python 的函数声明支持默认参数:
def func(a=[]): pass
不知道什么意思?自己看书去!
我这里要说的是,如果我们的默认参数是mutable类型的对象,会有什么黑魔法产产生?
我们看看下面的函数:

def func(a=[]):
  a.append(3)
  return a

可能有同学会说了:我去!这么简单?来骗代码的吧?
但是,真的这么简单吗?我们看下下面的调用结果:

> print func()
[3]
> print func()
[3,3]
> print func()
[3,3,3]

这真的是你想要的结果吗?
No,我要的是[3],[3],[3]!
原因?好吧,我们再用下id()神奇看看:

def func(a=[]):
  print id(a)
  a.append(3)
  return a

> print func()
4365426272
[3]
> print func()
4365426272
[3, 3]
> print func()
4365426272
[3, 3, 3]

明白没?原来在python中,*默认参数不是每次执行时都创建的!*
这下你再想想,曾经嘲笑过的代码(至少我)为什么要 多此一举:

def func(a=None):
  if a is None:
    a = []

这里在顺带提一下==, is:
== : 值比较
is : 比较左右两边是否是同一个对象。 a is b ==> id(a) == id(b)
ok, let's move on!
我们都知道,在python中,不定参数我们可以这样定义:
def func(*args, **kv): pass
什么你不知道?看书去!
那args和kv到底是什么情况呢?到底是mutable 还是 immutable 呢?
再一次请出id()神器:

def func(*args):
  print id(args)


> a = [1,2]
> print id(a)
4364874816
> func(*a)
4364698832
> func(*a)
4364701496

看到了吧?实际上args也会产生一个新的对象。但是值是填入的传入参数。那么每一个item也会复制吗?
我们再看看:

def func(*args):
  print id(args[0])

> a = [1,2]
> print id(a[0])
140248135804216
> func(*a)
140248135804216

答案是,No。值会像普通list赋值一样,指向原先list(a)所引用的对象。
那么为什么会这样呢?
python的源码就是这么写的.......
最最后,还记得我说过的一句话吗?
immutable 限制的是对象本身不可变
意思就是说,对象的immtable 只是限制自身的属性能否被改变,而不会影响到其引用的对象。
看下下面的例子:

> a = [1,2]
> b = (a,3)
> b[1] = 100
TypeError: 'tuple' object does not support item assignment

> print b
([1, 2], 3)
> b[0][0] = 10
> print b
([10, 2], 3)

最最最后,我有个对象,它本身应该是 mutable 的,但是我想让他具备类似immutable的特性,可以吗?
答案是,可以模拟!
还是之前说的,immutable 限制的是其自身属性不能改变。
那么,我们的可以通过重定义(重载)属性改变函数,来模拟immutable特性。
python可以吗?O~Yee
在python的类函数中,有这样的两个函数: __setattr__ 和 __delattr__。分别会在对象属性赋值和删除时执行。
那么我们可以进行简单重载来模拟immutable:

class A:
  def __setattr__(self, name, val):
    raise TypeError("immutable object could not set attr")

以上就是为大家介绍的python黑魔法,希望对大家的学习有所帮助。

python进阶教程之动态类型详解,python进阶教程详解

动态类型(dynamic typing)是Python另一个重要的核心概念。我们之前说过,Python的变量(variable)不需要声明,而在赋值时,变量可以重新赋值为任意值。这些都与动态类型的概念相关。

动态类型

在我们接触的对象中,有一类特殊的对象,是用于存储数据的。常见的该类对象包括各种数字,字符串,表,词典。在C语言中,我们称这样一些数据结构为变量。而在Python中,这些是对象。

对象是储存在内存中的实体。但我们并不能直接接触到该对象。我们在程序中写的对象名,只是指向这一对象的引用(reference)。

引用和对象分离,是动态类型的核心。引用可以随时指向一个新的对象:

复制代码 代码如下:

a = 3
a = 'at'

第一个语句中,3是储存在内存中的一个整数对象。通过赋值,引用a指向对象3。

第二个语句中,内存中建立对象‘at',是一个字符串(string)。引用a指向了'at'。此时,对象3不再有引用指向它。Python会自动将没有引用指向的对象销毁(destruct),释放相应内存。

(对于小的整数和短字符串,Python会缓存这些对象,而不是频繁的建立和销毁。)

复制代码 代码如下:

a = 5
b = a
a = a + 2

再看这个例子。通过前两个句子,我们让a,b指向同一个整数对象5(b = a的含义是让引用b指向引用a所指的那一个对象)。但第三个句子实际上对引用a重新赋值,让a指向一个新的对象7。此时a,b分别指向不同的对象。我们看到,即使是多个引用指向同一个对象,如果一个引用值发生变化,那么实际上是让这个引用指向一个新的引用,并不影响其他的引用的指向。从效果上看,就是各个引用各自独立,互不影响。

其它数据对象也是如此:

复制代码 代码如下:

L1 = [1,2,3]
L2 = L1
L1 = 1

但注意以下情况

复制代码 代码如下:

L1 = [1,2,3]
L2 = L1
L1[0] = 10
print L2

在该情况下,我们不再对L1这一引用赋值,而是对L1所指向的表的元素赋值。结果是,L2也同时发生变化。

原因何在呢?因为L1,L2的指向没有发生变化,依然指向那个表。表实际上是包含了多个引用的对象(每个引用是一个元素,比如L1[0],L1[1]..., 每个引用指向一个对象,比如1,2,3), 。而L1[0] = 10这一赋值操作,并不是改变L1的指向,而是对L1[0], 也就是表对象的一部份(一个元素),进行操作,所以所有指向该对象的引用都受到影响。

(与之形成对比的是,我们之前的赋值操作都没有对对象自身发生作用,只是改变引用指向。)

列表可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可变数据对象(mutable object),词典也是这样的数据类型。

而像之前的数字和字符串,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。

我们之前学的元组(tuple),尽管可以调用引用元素,但不可以赋值,因此不能改变对象自身,所以也算是immutable object.

从动态类型看函数的参数传递

函数的参数传递,本质上传递的是引用。比如说:

复制代码 代码如下:

def f(x):
    x = 100
    print x

a = 1
f(a)
print a

参数x是一个新的引用,指向a所指的对象。如果参数是不可变(immutable)的对象,a和x引用之间相互独立。对参数x的操作不会影响引用a。这样的传递类似于C语言中的值传递。

如果传递的是可变(mutable)的对象,那么改变函数参数,有可能改变原对象。所有指向原对象的引用都会受影响,编程的时候要对此问题留心。比如说:

复制代码 代码如下:

def f(x):
    x[0] = 100
    print x

a = [1,2,3]
f(a)
print a

动态类型是Python的核心机制之一。可以在应用中慢慢熟悉。

总结

引用和对象的分离,对象是内存中储存数据的实体,引用指向对象。

可变对象,不可变对象

函数值传递

您可能感兴趣的文章:

  • python下函数参数的传递(参数带星号的说明)
  • Python def函数的定义、使用及参数传递实现代码
  • python进阶教程之函数参数的多种传递方法
  • 跟老齐学Python之总结参数的传递
  • 用实例分析Python中method的参数传递过程
  • python传递参数方式小结
  • Python函数可变参数定义及其参数传递方式实例详解
  • Python中函数的参数传递与可变长参数介绍

我们都听说,python世界里面,万物皆对象。 怎么说万物皆对象呢?最常见的: class A: pass a = A() 我们...

python的类中怎实现动态化函数?

给你这样一个例子吧,这个例子里面有动态增加类的函数。
声明一个类,类初始化的时候读取配置文件,根据配置列表加载特定目录下的模块下的函数,函数和模块同名,将此函数动态加载为类的成员函数。
代码如下所示:
class WinBAS(Bas):
def __init__(self):
self.__baslist = {}
self.__Init_Modules()
pass
def __Init_Modules(self):
import modplugs
for m in modplugs.__moduleset__:
mh = __import__('modules.' + m)# + '.' + m)
ma = getattr(mh, m)# + '.' + m)
ma = getattr(ma, m)
setattr(self.__class__, m, ma)
modplugs.py是模块配置文件如下:
__moduleset__ = [
'BAS_GetUserList',
]
然后建立目录modules下面建立一个空的__init__.py文件,把目录变为一个包,在modules目录下建立真正的BAS_GetUserList实现:BAS_GetUserList文件中有个BAS_GetUserList函数如下:
def BAS_GetUserList(self, strs):
return [0, strs]

这样WinBAS类就可以动态加入了BAS_GetUserList函数。  

python中想动态创建对象应该怎做?

class BaseObject(object):
def __init__(self, **kwg):
self.__dict__ = dict(kwg)

content = '''Tim.Wang|M|43
Bruce.Wang|M|38
Amay.Song|F|21'''

def ln2obj(ln):
name,sex,age = ln.split('|')
return BaseObject(name=name,sex=sex,age=int(age))

students = map(ln2obj, content.splitlines())  

动态类型(dynamic typing)是Python另一个重要的核心概念。我们之前说过,Python的变量(variab...

本文由正版必中一肖图发布于编程应用,转载请注明出处:python黑法力之参数字传送递,python进阶教程详解

上一篇:以二个投票程序的实例来说学Python的Django框架使 下一篇:简单的实现树莓派的WEB控制,使用Python简单的实
猜你喜欢
热门排行
精彩图文