声明变量的Pythonic方式是什么?
通常在VBScript或JavaScript中,直接在赋值时声明变量被认为是一种最佳实践,虽然这样做是被允许的。
那么,为什么Python要你在使用变量的时候才创建它呢?因为Python对大小写是敏感的,这样会不会因为你拼错了变量名而导致错误呢?
你会怎么避免这种情况呢?
8 个回答
如果你想要一个“锁定”实例属性的类,其实很简单,比如:
class LockedDown(object):
__locked = False
def __setattr__(self, name, value):
if self.__locked:
if name[:2] != '__' and name not in self.__dict__:
raise ValueError("Can't set attribute %r" % name)
object.__setattr__(self, name, value)
def _dolock(self):
self.__locked = True
class Example(LockedDown):
def __init__(self):
self.mistakes = 0
self._dolock()
def onemore(self):
self.mistakes += 1
print self.mistakes
def reset(self):
self.mitsakes = 0
x = Example()
for i in range(3): x.onemore()
x.reset()
你会发现,调用 x.onemore
是没问题的,但 reset
会报错,因为属性拼错成了 mitsakes
。这里的规则是,__init__
方法必须给所有属性设置初始值,然后调用 self._dolock()
来禁止再添加任何属性。我会把“超级私有”的属性(以 __
开头的)排除在外,这种属性通常很少用,主要用于特定的角色,并且作用范围非常有限(这样在仔细检查时很容易发现拼写错误),不过这只是风格上的选择,随时可以改变;同样,选择让锁定状态“不可逆”(通过“正常”手段,也就是说需要非常明确的解决方法来绕过)也是如此。
这不适用于其他类型的名称,比如函数内部的名称;这也没什么大不了的,因为每个函数应该很小,而且是完全独立的作用域,检查起来非常简单(如果你写了100行的函数,那你可能有更严重的问题;-)。
这样做值得吗?不值得,因为半 decent 的单元测试应该能轻松捕捉到所有这样的拼写错误,这自然是因为它们会全面测试类的功能。换句话说,你并不需要额外的单元测试来捕捉拼写错误:你本来就需要的单元测试已经能捕捉到那些简单的语义错误(比如多加了1,或者应该是-1的地方加了1等等),也会捕捉到所有的拼写错误。
罗伯特·马丁和布鲁斯·埃克尔在7年前分别在不同的文章中阐述了这一观点——埃克尔的博客现在暂时无法访问,但马丁的文章在 这里,而当埃克尔的网站恢复时,文章应该在 这里。这个观点有争议(杰夫·阿特伍德和他的评论者在 这里 讨论过),但有趣的是,马丁和埃克尔都是静态语言(如C++和Java)的知名专家(虽然他们分别对Ruby和Python有偏爱),而且他们并不是唯一发现单元测试重要性的人……而且一个好的单元测试套件,作为副作用,使得静态语言的严格性变得多余。
顺便说一下,检查你的测试套件的一种方法是“错误注入”:系统地在你的代码中引入一个拼写错误——运行测试确保它们确实失败,如果没有,就添加一个会失败的错误,纠正拼写错误,然后重复。这个过程可以相对自动化(不是“添加测试”那部分,而是找到测试套件未覆盖的潜在错误),还有其他一些错误注入的形式(逐个将每个整数常量加一或减一;将每个 <
改为 <=
等等;将每个 if
和 while
的条件反转;……),而其他一些错误注入的形式则需要更多的人工智慧。不幸的是,我不知道有公开的错误注入框架套件(适用于任何语言)——这可能是一个很酷的开源项目;-)。
这是因为Python受到了“教学语言”的启发,所以有些奇怪的地方。它的设计目的是让语言更容易上手,完全去掉了“声明”这个步骤。出于某种原因(可能是为了“简单”),Python没有像VB那样的“Option Explicit”选项来强制要求声明变量。虽然这可能会导致一些错误,但正如其他回答所说,优秀的程序员可以养成一些习惯,来弥补语言中的不足。而且,就不足之处而言,这个问题算是比较小的。
在Python中,可以把声明变量想象成把值绑定到名字上。
尽量不要拼错这些名字,否则你会得到新的变量(假设你是在说赋值语句 - 如果你引用错误的名字,会出现错误)。
如果你是在讨论实例变量,那么之后你将无法使用它们。
举个例子,如果你有一个类叫做 myclass
,在它的 __init__
方法里写了 self.myvar = 0
,那么如果你尝试引用 self.myvare
,就会出现错误,而不是给你一个默认值。