`
arganzheng
  • 浏览: 101385 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Python语言学习笔记

阅读更多

Python语言学习笔记

2010-09-23 星期四 寒流+雨

中秋有三天假,感觉老长似的,专门跑出枫林晚借了本《Learning Python》的书,准备假期看看。厄,现在是假期的中间,书确实是看完了,700页左右的英文书,前后花了3个小时左右,当然只是看,没有编码。

发现自己现在看语言方面的书真是太快了,因为语言的东西确实相差无几。特别是语法方面的细节,在看书的时候我几乎就是一扫而过。比如,如何定义一个类,它的if-else,for循环语法,etc。因为这些只是实现上的差异(语法定义上的差异),跟这个语言的功能与编程范式无关——当然,我承认,语法很影响一个人对一门语言的选择与偏爱,它就像人的相貌一样。

但是,一门的语言最重要的差别不在于语法,而在于其它几个重要的方面。这里直接给出vczh同学的结论,因为我同意他的观点:

计算机语言作为一个计算的定义,在我们开发脚本引擎之前需要先进行了解。对于目前流行的若干种语言,我们可以抽象出一组正交属性来描述他们。

一、命令式与描述式

一门语言是命令式或者描述式取决于这门语言是用来告诉计算机怎样做还是做什么的。举个例子,SQL和Prolog是描述式语言,而C++、C#等则是命令式语言。我们在使用SQL的时候告诉服务器的是我们需要满足什么条件的数据项,而不是告诉服务器我们需要通过什么计算来获得自己所需要的数据项。描述式的语言的优点在于其可读性好。C# 3.0为数据查询加入了LINQ让我们可以在C#中书写类似SQL的代码查询数据。

另一个比较模糊的例子则是Haskell。Haskell很难区分是命令式语言还是描述式语言。因为从形式上来说我们告诉编译器的是我们想做什么而不是我们想怎么做,但是Haskell给我们的工具的粒度太细以至于我们为了告诉编译器做什么的同时仍然需要考虑一个问题是如何被解决的。

二、按值计算与惰性计算

惰性计算的语言很少出现以至于可能很多人都不知道"原来语言可以是这个样子的"。惰性计算的精神是不去执行没用的代码。什么是没用的代码呢?只要是这段代码的值不对外界产生任何影响,譬如没有往屏幕、硬盘或者是其他什么地方写点什么数据,就是没有用的。当然,至于这段代码中间做了些什么事情那是不管的。

举一个比较简单的例子,假设现在有如下代码:

function PrintAndReturn(Message,Result)
{
    Print(Message);

    return Result;
}

function DoSomething(BoolA,BoolB)
{
    If(BoolA || BoolB) Print("!");
}

DoSomething(PrintAndReturn("Hello",true),PrintAndReturn("World",false));

DoSomething函数传入两个参数,都是布尔类型的。如果这两个参数其中有一个是true的话那么就往屏幕上打出一个感叹号。PrintAndReturn函数接受两个参数,往屏幕上打出第一个参数,函数返回第二个参数。

对于一门按值计算的语言,也就是我们平常见到的那种,执行的结果是"HelloWorld!"。因为为了调用DoSomething我们需要首先获得两个布尔值。

对于一门惰性计算的语言,执行的结果是"Hello!"。因为DoSomething在对BoolA || BoolB进行求值的时候计算了BoolA,发现是true,于是BoolB这个参数就没有用了,因此PrintAndReturn("World",false)也就不会执行了,导致"World"不会显示在屏幕上。

当然,对于上面举的这个例子来说,这种语言有着惰性计算的属性并不合理。一门语言为了不具有二义性,在存在惰性计算的同时必须对自己的类型系统进行改造。关于这方面的资料可以查阅Haskell语言中Monad的原理。Haskell作为一门惰性计算的语言,在不关心求值顺序的同时,仍然保证结果的一致性。上面这个例子,如果程序对||的求值是从右操作数开始的话,那么输出的结果就变成"HelloWorld!"了。惰性计算的好处在于可以在逻辑上表达无穷大的对象,而在实际的计算过程中并不需要将这个无穷大的对象一次性计算出来,而是需要哪里算到哪里。举个例子:

function MakeArray(Index)
{
    return [Index]++MakeArray(Index+1);
}

function Sum(Array,Count)
{
    Result=0;

    for i=0 to Count-1

        Result+=Array[i];

   return Result;
}

Print(Sum(MakeArray(1),10));

在这个例子中,Index代表一个只有一个元素的数组,其内容是Index,而++操作符将两个数组接起来。于是MakeArray(1)就产生了一个无穷长的数组,其内容是1,2,3,4,...。Sum计算数组的前若干个数字的和。对于一门惰性计算的语言,这个例子将输出55,因为我们需要的仅仅是前10个数字,因此MakeArray只需要递归10次就自动挺下来了。而对于一门按值计算的语言来说,将发生死循环而出现不可停机现象。

三、强类型、弱类型与无类型

一门语言是无类型当且仅当一个固定的符号的类型可以在运行时改变。譬如如下代码:

TheVariable=1;

TheVariable="I am a string!";

第一行创建了一个int类型的TheVariable变量,而第二行则将TheVariable修改成了字符串类型。一门无类型语言的对象类型可以是数值、字符串、数组、类、闭包、函数指针等等的东西。

只要不是无类型的,那必然就是强类型或者弱类型的了。强类型与弱类型的分界线比较明显。只要存在隐式类型转换的语言则是弱类型的,譬如C语言能将int隐式转换为double。不存在隐式转换的语言也是存在的,譬如Haskell。在Haskell里面不能创建一个实数类型的名字但是绑定一个整数的值上去。因为整数跟实数的类型是不同的,而且不存在隐式转换。

四、函数与闭包

凡是支持闭包的语言必然是支持函数的,但是并不是所有支持函数的语言都支持闭包,而且也并不是所有的语言都有函数。Windows的批处理文件所能理解的语言就是不支持函数的语言的一个例子。

至于什么是闭包呢?闭包就是可以保持函数执行的上下文的一种强大的函数指针。举个例子:

function Add(a)
    {
        return function(b)
        {
            Return a+b;
        }
    }

    Inc=Add(1);

    Inc10=Add(10);

    Print(Inc(5));

    Print(Inc10(5));

这个例子将输出6和15。执行Inc=Add(1);的时候,Add函数返回了一个新的函数,这个函数接受参数b并返回参数a和b相加的结果。返回的这个函数将参数a记了下来。所以Inc和Inc10在执行的时候,虽然执行的是同一个函数,但是这个函数所看到的a确是不同的。a的值的不同代表着Inc和Inc10执行函数的不同。这也就是闭包是可以保持函数执行的上下文的由来了。当然,一门不支持闭包的语言是不能允许上面这种写法的。

这四种属性是区分语言特征的重要属性。至于一门语言是否支持面向对象的写法或者支持元编程或者泛型之类的东西,并不是十分重要的特性,虽然我们使用起来的感觉非常不同。

不过话说回来,虽然说语言的编程范式(如面向对象或者面向过程),跟语言本身不是特别有关系(用面向过程的语言,一样可以编写面向对象的代码),但是这是非常大的语法糖,所以也是一个非常重要的考虑方面。异常处理和泛型编程也是一样的。

另外需要补充几点的是:1. 语言本身的内存管理也是一个非常重要的考虑方面,即是否支持垃圾回收和引用计数。2. 语言的动态程度,这个与vczh的第三点有点重叠,但是支持多大程度的元编程确实是个问题。3. 参数和返回值的传递方式(按值传递还是按引用传递,etc.)。4. 作用域规则

貌似有点扯远了。。回到主题。学习Python,其实最主要是掌握它的对象视图。下面是笔者的总结。

在Python中,所有的变量都是引用(指针,地址,名字,标签),不包含任何类型信息,这就是前面vczh同学说的无类型,指的就是变量(Variable)是无类型的。但是基本上目前任何一门语言都是有类型的(整型,字符型,etc.)。Python也不例外。它的类型信息是跟对象(Object)挂钩的。变量指向创建的对象。在Python中,一切都是对象,包括函数,甚至“类型”本身就是一个对象。它是一门基于对象的语言(all values are objects),同时支持面向对象的编程范式。

Figure 9-3. Python’s major built-in object types, organized by categories. Everything is a type of object in Python, even the type of an object! The type of any object is an object of type “type.”

In fact, even types themselves are an object type in Python: a call to the built-in function type(X) returns the type object of object X. Type objects can be used for manual type comparisons in Python if statements. 

One note on type names: as of Python 2.2, each core type has a new built-in name added to support type customization through object-oriented subclassing: dict, list, str, tuple, int, long, float, complex, unicode, type, and file (file is a synonym for open). Calls to these names are really object constructor calls, not simply conversion functions, though you can treat them as simple functions for basic usage.

另外,由于Python中变量是无类型的,对象是有类型的,所以它采用了将“变量赋值”作为“变量声明”的做法。因为赋值操作其实就是创建(可能会复用)一个对象,然后将这个对象的地址信息赋值给这个变量,这样就可以通过该变量引用这个对象了。所以:

>>> a=3

Python编译器将做如下事情:

1. Create an object to represent the value 3.(这里python会推断出3其实是一个整数,所以其类型信息是Int。)

2. Create the variable a, if it does not yet exist.

3. Link the variable a to the new object 3.

Each object also has two standard header fields: a type designator used to mark the type of the object, and a reference counter used to determine when it’s OK to reclaim the object. 

根据值来推断变量类型,其实是很有道理的,在静态类型语言中如C,对复制语句做类型是否一致检查,依据的也是这样的。所以是有道理的。只不过将赋值语句作为声明,混淆了赋值语句的含义,所以当你真正需要是修改一个变量值(使其指向另一个对象),而不是重新定义一个新变量时,需要一些特殊的标识来告诉编译器。如:

X = 88             # Global X

def func():
    global X       # Global X: outside def
    X = 99        

func()
print X            # Prints 99
 

如果没有global X这个显示作用域声明,那么在func中X=99,其实是声明了一个local作用域的新变量X,函数外面的X其实没有变化。

 
这其实是Python的一个设计败笔,因为Python的作用域原则上采用的是Lexical Scoping(文法作用域)。亦即:
The scope of a variable (where it can be used) is always determinged by where it is assigned in your source code, and has nothing to do with which functions call which. If a variabl is assigned inside a def, it is local to that function; if assigned outside a def, it is global to the entire file. We call this lexical scoping because variable scopes are determined entired by the locations of the variables in the source code of your program files, not by function calls.
 
在Ruby中,为了避免这种奇怪的问题,采用了是显示的作用域声明机制(通过变量名约定的形式)。

Punctuation characters may appear at the start and end of Ruby identifiers. They have the following meanings:

$ Global variables are prefixed with a dollar sign. Following Perl's example, Ruby defines a number of global variables that include other punctuation characters, such as $_ and $-K.
@ Instance variables are prefixed with a single at sign, and class variables are prefixed with two at signs.
? As a helpful convention, methods that return Boolean values often have names that end with a question mark.
! Method names may end with an exclamation point to indicate that they should be used cautiously. This naming convention is often to distinguish mutator methods that alter the object on which they are invoked from variants that return a modified copy of the original object.
= Methods whose names end with an equals sign can be invoked by placing the method name, without the equals sign, on the left side of an assignment operator.

Here are some example identifiers that contain leading or trailing punctuation characters:

$files          # A global variable
@data           # An instance variable
@@counter       # A class variable
empty?          # A Boolean-valued method or predicate
sort!           # An in-place alternative to the regular sort method
timeout=        # A method invoked by assignment

A number of Ruby's operators are implemented as methods, so that classes can redefine them for their own purposes. It is therefore possible to use certain operators as method names as well. In this context, the punctuation character or characters of the operator are treated as identifiers rather than operators. See Section 4.6 for more about Ruby's operators.

there are four kinds of variables in Ruby, and lexical rules govern their names. Variables that begin with $ are global variables, visible throughout a Ruby program. Variables that begin with @ and @@ are instance variables and class variables, used in object-oriented programming and explained in Chapter 7. And variables whose names begin with an underscore or a lowercase letter are local variables, defined only within the current method or block. (See Section 5.4.3 for more about the scope of local variables.)

常量也是通过变量名约定的。 

4.5.1. Assigning to Variables
When we think of assignment, we usually think of variables, and indeed, these are the most common lvalues in assignment expressions. Recall that Ruby has four kinds of variables: local variables, global variables, instance variables, and class variables. These are distinguished from each other by the first character in the variable name. Assignment works the same for all four kinds of variables, so we do not need to distinguish between the types of variables here. 
Keep in mind that the instance variables of Ruby's objects are never visible outside of the object, and variable names are never qualified with an object name. Consider this assignment: 
point.x, point.y = 1, 2

The lvalues in this expression are not variables; they are attributes, and are explained shortly. 
Assignment to a variable works as you would expect: the variable is simply set to the specified value. The only wrinkle has to do with variable declaration and an ambiguity between local variable names and method names. Ruby has no syntax to explicitly declare a variable: variables simply come into existence when they are assigned. Also, local variable names and method names look the same—there is no prefix like $ to distinguish them. Thus, a simple expression such as x could refer to a local variable named x or a method of self named x. To resolve this ambiguity, Ruby treats an identifier as a local variable if it has seen any previous assignment to the variable. It does this even if that assignment was never executed. The following code demonstrates: 
Code View:

class Ambiguous
  def x; 1; end # A method named "x". Always returns 1
  def test
    puts x      # No variable has been seen; refers to method above: prints 1
    # The line below is never evaluated, because of the "if false" clause. But
    # the parser sees it and treats x as a variable for the rest of the method.
    x = 0 if false
    puts x    # x is a variable, but has never been assigned to: prints nil
    x = 2     # This assignment does get evaluated
    puts x    # So now this line prints 2
  end
end

Groovy就更进一步了,采用的是显式的变量定义——使用def关键字。这样就将定义与赋值分离开来。从而避免了作用域混淆情况。
因为对象与类型信息挂钩(包括操作),所以在这些脚本语言中,支持这样的操作:3.times { print "Ruby! " },9.downto(1) {|n| print n }  ,etc.
 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics