Python 函数和类的别名

简单的一个小测试的记录,但因为我习惯记录前因后果,写得有点啰嗦。

起因

我尝试把一些 C/C++ 的库用 Python 重新实现一遍(纯练习 Python 和 熟悉库的原理,实际应用当然是Python直接调用C/C++),先逐句翻译成Python代码,再根据实际情况优化。

struct 当然先是转成 class。然后我发现,一些结构体里反复出现的公共成员,别人会先定义一个宏来减少重复工作,类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
#define CommonVars type1 var1; type2 var2; type3 var3
struct std_struct{
CommonVars;
};
struct struct1{
CommonVars;
type4 var4;
};
// struct2...

对应在Python里的一对一翻译,就是分别定义 CommonVars, StdStruct, Struct1, Struct2… 这么几个类,其中后面的都 继承 CommonVars。当然,很明显CommonVars 和 StdStruct 其实是一回事,但是为了保持跟C++代码的一一对应,我还是保留了两个类。

后来一想,何必继承呢,要不干脆定义完 CommonVars 之后 StdStruct = CommonVars?

实测

Python 一切皆对象,包括 函数 和 类 都是可以作为右值给变量赋值的,这样做相当与给 函数 / 类 起了别名,是可以运行的。
但是别名背后的运作是怎么样的呢,最好的办法就是实际试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#! /usr/bin/python3
def funA():
return 'This is funA.'
funB = funA
print('run funA:', funA())
print('run funB:', funB())
print('name of funA:', funA.__name__)
print('name of funB:', funB.__name__)
class ClsA:
pass
ClsB = ClsA
print('new ClsA:', ClsA())
print('new ClsB:', ClsB())
print('name of ClsA:', ClsA.__name__)
print('name of ClsB:', ClsB.__name__)

输出如下:

1
2
3
4
5
6
7
8
9
$ ./fun_class_alias.py
run funA: This is funA.
run funB: This is funA.
name of funA: funA
name of funB: funA
new ClsA: <__main__.ClsA object at 0x7f268e744278>
new ClsB: <__main__.ClsA object at 0x7f268e744278>
name of ClsA: ClsA
name of ClsB: ClsA

结果很清晰地说明了:

  1. 这样是可以正常运行的
  2. 别名纯粹只是个别名,是对原 函数 / 类 的一个引用,不影响实际的名字。(通过 .__name__ 获取实际的名字,仍然是定义时那一个。),也不会改变行为

但有一个问题,为什么两次获得的实例居然是一个地址?!

我把第二个也改为 ClsA() ,结果还是一样,说明跟别名无关。那按理说第二次实例化得到的地址必须不一样才对啊!想了一会,难道是因为内存回收?于是把代码改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#! /usr/bin/python3
class ClsA:
_cnt = 0
def __init__(self):
self.__class__._cnt += 1
self.idx = self._cnt
ClsB = ClsA
insA = ClsA()
print('new ClsA:', insA)
print('idx of insA:', insA.idx)
insB = ClsB()
print('new ClsB:', insB)
print('idx of insB:', insB.idx)

这次地址就不一样了:

1
2
3
4
5
./fun_class_alias.py
new ClsA: <__main__.ClsA object at 0x7f8f729ccf98>
idx of insA: 1
new ClsB: <__main__.ClsA object at 0x7f8f729d2ef0>
idx of insB: 2

这样看来,之前只是因为匿名引用之后内存被马上回收,然后下次申请的又是同一块内存,产生看起来好像是同一个实例的错觉。但只要给他们都加一个引用,就清楚了。

其他

然后我又查了一下,并没有发现其他人讨论这个内容(当然也可能是关键字选得不对,欢迎留言打脸),到时在官方文档发现类似这样的一句话:在Windows上,WindowsError 是 OSError的别名。

于是我翻了一下Python的源码,然后又在 Windows 上测试了一下这两个Error,作为这个记录的结尾:

1
2
3
4
5
6
7
8
// Python3.4.4\Objects\exceptions.c
// ...
INIT_ALIAS(EnvironmentError, OSError)
INIT_ALIAS(IOError, OSError)
#ifdef MS_WINDOWS
INIT_ALIAS(WindowsError, OSError)
#endif
//...

INIT_ALIAS 是一个比较复杂的宏,就不展开了,顾名思义,就是定义前者作为后者的别名。

1
2
3
4
5
6
#! /usr/bin/python3
print('OSError is:', OSError.__name__)
print('EnvironmentError is:', EnvironmentError.__name__)
print('IOError is:', IOError.__name__)
print('WindowsError is:', WindowsError.__name__)
1
2
3
4
5
> py test_WindowsError.py
OSError is: OSError
EnvironmentError is: OSError
IOError is: OSError
WindowsError is: OSError

知识共享 “署名-非商业性使用-相同方式共享” 4.0 (CC BY-NC-SA 4.0)”许可协议
本文为本人原创,采用知识共享 “署名-非商业性使用-相同方式共享” 4.0 (CC BY-NC-SA 4.0)”许可协议进行许可。
本作品可自由复制、传播及基于本作品进行演绎创作。如有以上需要,请留言告知,在文章开头明显位置加上署名(Jayce Chant)、原链接及许可协议信息,并明确指出修改(如有),不得用于商业用途。谢谢合作。
详情请点击查看协议具体内容。