第一次从静态语言到动态语言的人肯定在思维上需要一个比较大的跳跃,主要是许多静态语言中编译器干的事情到动态语言中后,或是不存在了,或是需要在运行时进行。 典型的例子包括:类型检查,重载,访问控制,常量。(暂时就想到这几个,还有一些代码生成的技术像define、template我们就不提了) 1、类型检查。
对于类型检查我想大部分人倾向于可选地进行,毕竟动态语言不是静态语言,duck typing还是给动态语言带来了巨大的灵活性的。 python对类型检查的实现只搜到这么一个:http://oakwinter.com/code/typecheck/ ,粗略看了一下文档,似乎已经相当完善了。
而我自己出于学习的目的也写了个超级简单的:http://huangyilib.googlecode.com/svn/trunk/typecheck.py,这个代码做为学习的材料也还是不错的。而且写完这个我自己也感觉对python的函数参数的处理机制有了更完善的认识。
给大家看下测试输出先,从中大家可以一窥其功能:
call temp(1, 'hello') call temp(1, 'hello', c=4) call temp(1, c=4, b='hello') call temp(a=1, c=4, b='hello') call temp(1, 2) TypecheckError : the value 2 of argument 'b' is not type <type 'str'> call temp(1, 'hello', c='hello') TypecheckError : the value 'hello' of argument 'c' is not type <type 'int'> call temp(1, c=1) TypeError : temp() takes at least 2 non-keyword arguments (1 given) temp() has not this keyword argument 'd' the default value 1 of argument 'c' is not type <type 'str'> temp() has not so meny arguments 4 test success另外还值得一提的就是,python3000 中的 pep-3107 提议一种给函数增加元数据的方式:
def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):不过这个东西并非为类型检查而生的,类型检查只是它潜在的一个应用而已,它本身只负责存储元数据,具体元数据是啥和元数据怎么用由第三方库决定,其他潜在应用包括:文档生成、rpc、与静态语言之间的交互等等。 2、重载。 关于重载首先要说的一点是 python 中灵活的参数传递机制可以减少大量使用重载的场景,不过剩下那些基于实参类型的重载python仍然无能为力。而幸运的是我们有PEAK,其中有个RuleDispatch便是干这事的,而Guido这篇博客:Python 3000 - Adaptation or Generic Functions?说到要把这东西加到python3k中去,也掀起一阵热烈的讨论,只不过在这里我们不叫它重载,叫它Generic Function,但实质是一样的,就是根据传入的不同类型的实参调用合适的函数,比如:
>>> class PrettyPrinter: ... @generic ... def pformat(self, object): ... """Return the pretty string representation of object""" ... return repr(object) ... >>> @PrettyPrinter.pformat.when(object=list) ... def pformat_list(self, object): ... s = '[' ... for item in object: ... s += (' '*self.indent) + self.pformat(item) + ',\n' ... return s + (' '*self.indent) + ']' ...然后当调用
PrettyPrinter().pformat([1,2,3])时,实际调用到的函数其实是下面那个pformat_list 。 3、访问控制。 python是不对属性做强制性的访问控制的,而是依赖于约定,一方面是坚持相信程序员的信条,另一方面我觉的是确实不好实现,程序中对属性的访问是如此的常见,如果在运行时进行检查,效率上损失太大,得不偿失。 ruby 是进行强制性访问控制的,对象所有属性都只能通过方法暴露,然后对方法进行访问控制,也就是说,每一次你访问一个对象暴露出来的属性,实际上你都是通过调 用一个方法,而调用方法之前访问控制机制还要先判断该调用地点是否可以调用该方法!所以说ruby慢不光是因为它的实现慢,它的语言设计本身就慢!(如对 ruby 有误解,欢迎指出) 另外还有一个原因是ruby中函数不是第一型对象,可以调用函数但不能获取函数对象本身。python中函数是第一型对象,函数对象本身可以当参数传递, 而且class中的方法其实只是普通的函数而已,完全可以把一个外部定义的函数对象交给class给它当方法用,这带来巨大的灵活性,但也使得这种情况下 对方法实现访问控制是根本不可能!你想啊:在class外部定义的函数自然是不能访问 class 的私有属性的,但是当它作为class的方法后就突然变得可以了吗? 4、常量。 常量换句话说就是只读的变量,在静态语言中它也是通过编译器在编译期间对代码进行约束。那么在动态语言中又该如何来实现呢? 这个问题最近在两个邮件里都提出来:请教:在python中要实现类似define的功能怎么办?,怎么不用property来实现只读属性?。 最常见的方法莫过于使用property实现只读的属性:
>>> class Person(object): ... def __init__(self, name): ... self.__name = name ... @property # 只读属性 ... def name(self): ... return self.__name ... >>> p = Person('huangyi') >>> p.name 'huangyi' >>> p.name = 'another' Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: can't set attribute我记得我当时在邮件的讨论中就很详细得总结了一下各种做法,但是刚才去搜的时候,竟然发现它不见了!难道出现了幻觉?估计是gmail当时出了点问题。不过幸好我在另一个地方存了一份;-) 顺便把它改成了doctest的形式。 下面直接粘贴一份语法加亮过的版本,你也可以在这里找到代码和语法加亮的html:
# -*- coding: utf-8 -*- ''' 感觉楼主的这篇和上次用 python 实现 define 的那篇帖子,想说的都是 一个东西,就是静态语言中的 const,第一次初始化后不能修改的东西。 说起来,python 对象中其实是有这样的东西,就是 imutable object 。 不过常量针对的是名字而非对象,所以在 python 中常量的准确定义应该 是:在第一次绑定后不能重新绑定其他对象的名字。 遗憾的是 python 中没有这样的东西。 其实和类型检查、访问控制等东西一样,静态语言中常量是通过编译器在 编译时进行检查,而 python 就算实现那也只能是在运行时进行计算,势 必损耗性能,我想这也是 python 中没有这样的东西的原因。 但是正如 python 中的访问控制是通过对名字的约定来做的一样,其实常 量也比较适合这样做。 如果实在要用动态语言模拟 const,那么关键在于对名字的绑定进行控制。 下面总结一下各种做法: ''' def a_const_value(): ''' 方法1是通过使用函数替代对名字的直接访问,好像是比较傻的方法。 不过 ruby 中函数调用可以省略括号就有点像了 >>> a_const_value() 'const' ''' return 'const' class Temp(object): ''' class 中通过 property 可以做得更漂亮: >>> t = Temp() >>> t.a_const_value 'const' >>> t.a_const_value = 'another value' Traceback (most recent call last): ... AttributeError: can't set attribute ''' @property def a_const_value(self): return 'const' class ConstError(Exception): pass class Consts(object): ''' 方法2是将常量名字放入一个 class 中统一进行管理: >>> consts = Consts() >>> consts.a = 2 >>> consts.a 2 >>> consts.a = 3 Traceback (most recent call last): ... ConstError: can't rebind const name 不过需要注意的是,仍然可以通过 __dict__ 直接访问常量: >>> consts.__dict__['a'] = 3 >>> consts.a 3 ''' def __setattr__(self, name, value): if name in self.__dict__: raise ConstError, 'can\'t rebind const name' else: self.__dict__[name] = value class ConstBase(object): ''' 或者让 class 自己指定那些是常量: >>> class Temp(ConstBase): ... __consts__ = {'a':None, 'b':2} ... def __init__(self, a): ... self.a = a ... >>> t = Temp(2) >>> t.a 2 >>> t.b 2 >>> t.a = 3 Traceback (most recent call last): ... ConstError: can't rebind const name >>> t.b = 3 Traceback (most recent call last): ... ConstError: can't rebind const name >>> t.c = 5 >>> t.c 5 使用这种方式,也可以直接通过 __dict__ 对常量进行修改: >>> t.__dict__['a']= 3 >>> t.a 3 ''' __consts__ = {} def __setattr__(self, name, value): if name in self.__consts__: if self.__consts__[name] == None: self.__consts__[name] = value else: raise ConstError, 'can\'t rebind const name' else: super(ConstBase, self).__setattr__(name, value) def __getattr__(self, name): if name in self.__consts__: return self.__consts__[name] else: return super(ConstBase, self).__getattr__(name, value) if __name__ == '__main__': import doctest doctest.testmod()
1 评论:
文章越写越棒了。
常量那一段应该贴到 cookbook上去,让老外也学习学习:)
发表评论