2004年12月31日

Subclassing built-in types

这里还用的是subclass,看有些文章已经不用subclass,而改用subtype这个词。这是为了区别于class专指classic class而来的。等到全部统一到new-style class时,使用subclass还是习惯一些吧。

这是2.2的一个明显的变化,built-in type可以被子类化了。不过,2.2版中已经把type和class合二为一统称为type了,这就非常的自然了。其实这里更主要的意思我认为是想说:以前的built-in type象list, dict原来是不可以被子类化的,现在已经可以子类化了。

Let’s start with the juiciest bit: you can subtype built-in types like dictionaries and lists. All you need is a name for a base class that is a built-in type and you’re in business.

There’s a new built-in name, “dict”, for the type of dictionaries. (In version 2.2b1 and before, this was called “dictionary”; while in general I don’t like abbreviations, “dictionary” was just too long to type, and we’ve been saying “dict” for years.)

有一个内的内置名字,“dict”,它是字典的类型。在2.2b1和它之前,字典被称为“dictionary”,Guido通常是不喜欢使用缩写的,但“dictionary”对type来说有点太长了,而且大家使用“dict”也有几年了。

Here’s an example of a simple dict subclass, which provides a “default value” that is returned when a missing key is requested:

class defaultdict(dict):

    def __init__(self, default=None):
        dict.__init__(self)
        self.default = default

    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            return self.default

上面是一个从dict派生的例子。它的作用是当访问一个不存在的元素时,返回一个缺省值。

This example shows a few things. The __init__() method extends the dict.__init__() method. Like __init__() methods are wont to do, it has a different argument list than the base class __init__() method. Likewise, the __getitem__() method extends the base class __getitem__() method.

__init__()方法扩展了dict.__init__()方法,它比基类__init__()方法多了一个不同的参数。同样,__getitem__()方法扩展了基类的__getitem__()方法。

The __getitem__() method could also be written as follows, using the new “key in dict” test introduced in Python 2.2:

def __getitem__(self, key):
    if key in self:
        return dict.__getitem__(self, key)
    else:
        return self.default

上面是__getitem__()方法的另一种写法,它使用了在 Python 2.2 中新引进的”key in dict”的条件判断。(这种用法其实相当于:key in dict.keys()。)

I believe that this version is less efficient, because it does the key lookup twice. The exception would be when we expect that the requested key is almost never in the dictionary: then setting up the try/except statement is more expensive than the failing “key in self” test.

不过上面的新版本效率有些低,因为它要查找键值两次(一次是key in self,一次就是真正把值取出来)。然而使用异常机制的代价要比”key in self”判断高一些。(没做过测试,我想可能要看字典的大小吧。)

To be complete, the get() method should probably also be extended, to make it use the same default as __getitem__():

def get(self, key, *args):
    if not args:
        args = (self.default,)
    return dict.get(self, key, *args)

(Although this function is declared with a variable-length argument list, it really should only be called with one or two arguments; if more are passed, the base class method call will raise a TypeError exception.)

为了完整起见,get()方法也应该进行扩展。尽管方法是使用可变长度参数列表来声明的,它应该只使用一个或两个参数来调用,如果传入更多的参数,基类方法的调用会引发一个TypeError的异常来。

We’re not restricted to extending methods defined on the base class. Here’s a useful method that does something similar to update(), but keeps existing values rather than overwriting them with new values if a key exists in both dictionaries:

def merge(self, other):
    for key in other:
        if key not in self:
            self[key] = other[key]

This uses the new “key not in dict” test as well as the new “for key in dict:” to iterate efficiently (without making a copy of the list of keys) over all keys in a dictionary. It doesn’t require the other argument to be a defaultdict or even a dictionary: any mapping object that supports “for key in other” and other[key] will do.

我们并没有限制只可以扩展在基类中存在的方法。这里有一个有用的方法可以实现与update()相似的功能,但当一个键存在时,它是保持对应的值不变,而不是象update一样,覆盖对应的值。它也使用了新的”kek not in dict”判断和新的”for key in dict”来有效地列取(不用生成键列表的拷贝)一个字典中所有键。merge方法并不要求另一个参数是defaultdict类型或dict类型:任何支持”for key in order”和other[key]的映射类型的对象都可以使用。(其实从这里看,Python中就已经有interface的概念,但因为Python获得一个对象的方法非常容易,就是a.x就行,因此并不需要显示有一种interface的定义。不过,这也造成,我们一般是通过一些文档来描述interface的信息,而没有特别的强制手段来保证传入的对象是满足条件的。只有运行的时候引发异常,可能才会知道我们传入的对象是存在问题的。现在许多framework正在试图定制强制手段来实现interface的可见性,象zope 3x中就大量使用了interface。可以阅读我的这篇Blog看一看[Reading]Python Interfaces are not Java Interfaces了解一些Python中Interface的使用习惯。)

Iterator也是Python中很大的变化,我专门有一篇Blog是讲它的,阅读[Python学习]Iterator 和 Generator的学习心得。Iterator的主要好处就是减少内存占用,我想也可以提高一些速度。

Here’s the new type at work:

>>> print defaultdict # show our type
<class ‘__main__.defaultdict’>
>>> print type(defaultdict) # its metatype
<type ‘type’>
>>> a = defaultdict(default=0.0) # create an instance
>>> print a # show the instance
{}
>>> print type(a) # show its type
<class ‘__main__.defaultdict’>
>>> print a.__class__ # show its class
<class ‘__main__.defaultdict’>
>>> print type(a) is a.__class__ # its type is its class
1
>>> a[1] = 3.25 # modify the instance
>>> print a # show the new value
{1: 3.25}
>>> print a[1] # show the new item
3.25
>>> print a[0] # a non-existant item
0.0
>>> a.merge({1:100, 2:200}) # use a dictionary method
>>> print a # show the result
{1: 3.25, 2: 200}
>>>

新的类工作很好。(为了在donews上输出我使用了全角大括号。)

We can also use the new type in contexts where classic only allows “real” dictionaries, such as the locals/globals dictionaries for the exec statement or the built-in function eval():

>>> print a.keys()
[1, 2]
>>> exec “x = 3; print x” in a
3
>>> print a.keys()
['__builtins__', 1, 2, 'x']
>>> print a['x']
3
>>>

新的type还可以用在仅允许“真正”字典的上下文中,例如,在exec语句或内置函数eval()中使用的locals/globals字典。(这样,新定义的类型与原来的dict类型没有什么区别。)

However, our __getitem__() method is not used for variable access by the interpreter:

>>> exec “print foo” in a
Traceback (most recent call last):
File “<stdin>”, line 1, in ?
File “<string>”, line 1, in ?
NameError: name ‘foo’ is not defined
>>>

Why doesn’t this print 0.0? The interpreter uses an internal function to access the dictionary, which bypasses our __getitem__() override. I admit that this can be a problem (although it is only a problem in this context, when a dict subclass is used as a locals/globals dictionary); it remains to be seen if I can fix this without compromising performance in the common case.

然而,__getitem__()方法不能被解释器和来访问变量。(象上面的exec的意思就是在a这个名字空间,把变量foo打印出来。)为什么不打印0.0呢?(为什么是0.0呢?因为在生成实例a时,我们已经指定缺省值为0.0,因此,这里的意思就是说:在a中打印foo,因为foo不存在,应该可以打印缺省值0.0。)这是因为解释器使用一个内部的函数来访问字典,它会跳过我们的__getitem__()。要承认这的确是一个问题(仅管它只存在于这个上下文,当一个子类化的字典被作用一个locals/globals字典时)。

Now we’ll see that defaultdict instances have dynamic instance variables, just like classic classes:

>>> a.default = -1
>>> print a["noway"]
-1
>>> a.default = -1000
>>> print a["noway"]
-1000
>>> print a.__dict__.keys()
['default']
>>> a.x1 = 100
>>> a.x2 = 200
>>> print a.x1
100
>>> print a.__dict__.keys()
['default', 'x2', 'x1']
>>> print a.__dict__
{’default’: -1000, ‘x2′: 200, ‘x1′: 100}
>>>

我们可以看到defaultdict的实例拥有动态的实例变量。象classic class一样。你可以修改它。

This is not always what you want; in particular, using a separate dictionary to hold a single instance variable doubles the memory used by a defaultdict instance compared to using a regular dictionary! There’s a way to avoid this:

class defaultdict2(dict):

    __slots__ = ['default']

    def __init__(self, default=None):
        …(like before)…

但这不总是你想要的,特别是,defaultdict使用一个分离的字典来保持一个单个实例变量,同通常的字典比较看,它占用两倍的内存。(为什么说两倍呢?因为通常的字典信息都放在字典内部。而defaultdict保存实例属性时,又多了一个字典,这样相当于存在了两个字典。因此要占两倍的空间。不过,为什么对实例变量赋值会放在__dict__中呢?我想可能是因为属性赋值(a.x)与字典赋值(a[x])不同,一个是调用__setattr__()一个是调用__setitem__()。而一般我们操作字典都是调用的__getitem__()和__setitem__()。如果你试一下,你对一般的字典使用a.x这样的形式会报错。而我们生成的的确是一个新的类,它让你这样做。)

因此上面提出了一个解决办法,那就是使用新的属性__slots__。

The __slots__ declaration takes a list of instance variables, and reserves space in the instance for exactly these in the instance. When __slots__ is used, other instance variables cannot be assigned to:

>>> a = defaultdict2(default=0.0)
>>> a[1]
0.0
>>> a.default = -1
>>> a[1]
-1
>>> a.x1 = 1
Traceback (most recent call last):
File “<stdin>”, line 1, in ?
AttributeError: ‘defaultdict2′ object has no attribute ‘x1′
>>>

__slots__保存着实例变量的列表,并且在实例中保留空间以确定它们在实例中。一旦使用了__slots__,其它的实例变量就不能被赋值了。(象上面a.x1,因为x1不在__slots__中,因此引发异常。也就是说,你只可以访问在__slots__中存在的实例属性,对不存在的属性引用就会出错。)

这样做的结果就是值不会再生成一个__dict__字典来保存了,节省了内存。而且,限定了可以使用的属性变量。

Some noteworthy tidbits and warnings about __slots__:

  • An undefined slot variable will raise AttributeError as expected. (Note that in Python 2.2b2 and earlier, slot variables had the value None by default, and “deleting” them restores this default value.)

    一个未定义的slot变量将按我们所期望地引发AttributeError异常。(注意在 Python 2.2b2和更早的版本,slot变量缺省为None,并且“删除”它们会恢复这个缺省值。)(什么意思?经过试验,原来,当你在一个类中定义了__slots__=['x'],并不表示你就可以使用a.x来使用它了。在2.3中,这样会引发一个AttributeError异常。而a.x=1后,再使用a.x就可以了。这说明__slots__的表示只是保留,但它不会真正创建对应的变量,还是要你来创建。在你未创建之前,就叫未定义的slot变量。一旦我们执行了对slot变量的赋值操作,slot变量就创建了。因此后面补充到2.2b2之前的版本与此有所不同。在2.2b2之后,如果你删除了slot变量,再引用时,它一样变成未定义,会引发异常。)

  • You cannot use a class attribute to define a default value for an instance variable defined by __slots__. The __slots__ declaration creates a class attribute containing a descriptor for each slot, and setting a class attribute to a default value would overwrite this descriptor.

    你不能使用类属性来为使用__slots__定义的实例变量提供缺省值。__slots__声明会为每一个slot变量创建一个包含descriptor(描述符)的类属性,而设置类属性为一个缺省值会覆盖这个descriptor。(通过在类定义中声明类属性一般可以为实例对象提供一个属性的缺省值,但是使用__slots__时不要这么做。关于descriptor又是漫漫长路,详情参见How-To Guide for Descriptors。其实在2.3+的文档中已经有descriptor的描述,但感觉不易懂,可能是语言的缘故,看这篇文章可能会好一些。)

  • There’s no check to prevent name conflicts between the slots defined in a class and the slots defined in its base classes. If a class defines a slot that’s also defined in a base class, the instance variable defined by the base class slot is inaccessible (except by retrieving its descriptor directly from the base class; this could be used to rename it). Doing this renders the meaning of your program undefined; a check to prevent this may be added in the future.

    没有检查来阻止一个类中定义的slots和基类中定义的slots产生的名字冲突问题。如果一个类定义了一个slot,并且这个slot也定义在基类中,那么基类中定义的实例变量是不可访问的(除非直接从基类的descriptor来获取,这样可以用来重命名)。这样做会致使你的程序未被定义(还有这种意思?)。以后会加入对此的检查。
  • Instances of a class that uses __slots__ don’t have a __dict__ (unless a base class defines a __dict__); but instances of derived classes of it do have a __dict__, unless their class also uses __slots__.

    使用了__slots__的类实例不能再有__dict__(除非一个基类定义了__dict__)。但从它派生的类可以拥有一个__dict__,除非它也使用了__slots__。
  • You can define an object with no instance variables and no __dict__ by using __slots__ = [].

    你不能定义通过定义__slots__=[]来定义一个即没有实例变量,又没有__dict__的对象。

  • You cannot use slots with “variable-length” built-in types as base class. Variable-length built-in types are long, str and tuple.

    你不能把slots用在以“可变长度”的built-in type作为基类的类上。可变长度built-in type是long, str和tuple。(long也算可变长度真是很奇怪,而dict却不算。)

  • A class using __slots__ does not support weak references to its instances, unless one of the strings in the __slots__ list equals “__weakref__”. (In Python 2.3, this feature has been extended to “__dict__”)

    一个使用了__slots__的类不支持对实例的弱引用(weak reference),除非在__slots__列表中的一个字符串等于”__weakref__”。(在Python 2.3中,这一特性已经被扩展到”__dict__”上)(我试了试,如果在__slots__中加入了__dict__,我就可以定义在slots之外的实例变量了。)

>>> class A(object):
…     __slots__=['x', '__dict__']

>>> a=A()
>>> a.b=1

  • The __slots__ variable doesn’t have to be a list; any non-string that can be iterated over will do, and the values returned by the iteration are used as the slot names. In particular, a dictionary can be used. You can also use a single string, to declare a single slot. However, in the future, an additional meaning may be assigned to using a dictionary, for example, the dictionary values may be used to restrict the type of an instance variable or provide a doc string; the effect of using something that’s not a list renders the meaning of your program undefined.

    __slots__变量不一定是一个list,任何可以被列举的非字符串都可以是,并且列举所返回的值将作为slot的名字。特别是,可以使用字典。你也可以使用单个字符串来声明一个单一slot。然而,在未来,可能对于使用字典会赋予特别的意义,例如,字典值可以用来限制一个实例变量的类型,或提供一个doc string。使用非列表的东西会致使你的程序处于非定义的意思。

注意,__slots__只是用来定义类可以有哪些实例变量名,并不生成实际的实例变量。与__slots__定义的名字对应的实例变量可以称为slot变量。你需要自已来生成它,通过赋值语句。如果未生成即进行引用,则引发异常。

Note that while in general operator overloading works just as for classic classes, there are some differences. (The biggest one is the lack of support for __coerce__; new-style classes should always use the new-style numeric API, which passes the other operand uncoerced to the __add__ and __radd__ methods, etc.)

There’s a new way of overriding attribute access. The __getattr__ hook, if defined, works the same way as it does for classic classes: it is only called if the regular way of searching for the attribute doesn’t find it. But you can now also override __getattribute__, a new operation that is called for all attribute references.

有一种新的覆盖属性访问的方式。如果定义了__getattr__钩子,它将同classic class一样的方式工作。只有当正常查找的属性找不到时它才会被调用。但你也可以覆盖__getattribute__,它可以用来对所有的属性引用进行处理。

When overriding __getattribute__, bear in mind that it is easy to cause infinite recursion: whenever __getattribute__ references an attribute of self (even self.__dict__!), it is called recursively. (This is similar to __setattr__, which gets called for all attribute assignments; __getattr__ can also suffer from this when it is carelessly written and references a non-existent attribute of self.)

当覆盖__getattribute__时,在心里要明白,这样很容易引起无限递归:只要__getattribute__引用self的一个属性(甚至是self.__dict__!),它都将递归调用。(很象__setattr__和__getattr__)

The correct way to get any attribute from self inside __getattribute__ is to call the base class’s __getattribute__ method, in the same way any method that overrides a base class method can call the base class method: Base.__getattribute__(self, name). (See also the discussion of super() below if you want to be correct in a multiple inheritance world.)

从self获得任何属性的正确的方法是在__getattribute__中调用基类的__getattribute__方法,同其它调用基类方法一样:Base.__getattribute__(self, name)。(如果想要正确处理多得继承,看后面对super的讨论)

Here’s an example of overriding __getattribute__ (really extending it, since the overriding method calls the base class method):

class C(object):
    def __getattribute__(self, name):
        print “accessing %r.%s” % (self, name)
        return object.__getattribute__(self, name)

这是一个正确调用的例子。

A note about __setattr__: sometimes attributes are not stored in self.__dict__ (for example when using __slots__ or properties, or when using a built-in base class). The same pattern as for __getattribute__ applies, where you call the base class __setattr__ to do the actual work. Here’s an example:

class C(object):
    def __setattr__(self, name, value):
        if hasattr(self, name):
            raise AttributeError, “attributes are write-once”
        object.__setattr__(self, name, value)

注意__setattr__,有时属性不是保存在self.__dict__中(例如,当使用__slots__或property时,或使用built-in基类时)。

C++ programmers may find it useful to realize that this form of subtyping in Python is implemented very similarly to single-inheritance subclassing in C++, with __class__ in the role of the vtable.

There’s much more that could be explained (like the __metaclass__ declaration, and the __new__ method), but most of that is pretty esoteric. See below if you’re interested.

还有一些象__metaclass__声明和__new__方法,但它们非常深奥。有兴趣在后面。(的确如此)

I’ll end with a list of caveats:

  • You can use multiple inheritance, but you can’t multiply inherit from different built-in types (for example, you can’t create a type that inherits from both the built-in dict and list types). This is a permanent restriction; it would require too many changes to Python’s object implementation to lift it. However, you can create mix-in classes by inheriting from “object”. This is a new built-in, naming the featureless base type of all built-in types under the new system.

    你可以使用多重继承,但不能从不同的built-in type来多重继承(例如,不能创建一种type,同时从dict和list类型继承而来)。这是永久的限制。不这样限制的话,需要对Python做非常多的改动才可以适应。然而你可以通过从object继承来创造mix-in类。object是一种新的built-in,在新系统下为所有built-in type命名的无特性的基type。(Python已经是单根继承了,要记住。)

  • When using multiple inheritance, you can mix classic classes and built-in types (or types derived from built-in types) in the list of base classes. (This is new in Python 2.2b2; in earlier versions you couldn’t.)

    当使用多重继承,你可以混合classic class和built-in type(或从built-in type继承来的类型)在基类列表中。(在Python 2.2b2中是新的,在更早的版本则不行)(那么结果到底是什么类型呢?是<type ‘type’>类型,你可以试一试。)

  • See also the general bugs in 2.2 list.

我想学习还是精读一些的好。这几天看了不少关于New-Style Class的文章,资料,想到一句话是“侯门深似海”,我现在的感觉是“OO深似海”。许多概念、原理、技术如果只是平时应用的话可能并不是很关心它的细节,只要能保证我正确使用就行了。因为每个人都有一种使用的习惯,在这种习惯下很多东西也许你不是很清楚为什么会这样,但是经验告诉我们,这样就不会有问题。但光会用对我来说是远远不够的,只有深入了解Python的细节,特别是OO的知识,有些东西你才可能明白。从这一段阅读 Python.list 上的邮件来看,有些东西已经很深入地涉及了象 New-Style Class这样的知识。如果你不具备这样的知识和经验,你很难明白别人在说什么。就是象我前面介绍过的Lazy计算,看一看代码,别人也是用的Metaclass来实现的。因此,只有不停地学习才可以。

经过学习,很多东西的确已经慢慢理解,并有了一些感觉。我想现在更需要静下心来,对于某些文章细细地品味,并多做测试才可以更好的理解。那么就以Guido写的 Unifying types and classes 来开始吧。有些内容可能我就照搬或直译了。

Introduction

Python 2.2 introduces the first phase of “type/class unification”. This is a series of changes to Python intended to remove most of the differences between built-in types and user-defined classes. Perhaps the most obvious one is the restriction against using built-in types (such as the type of lists and dictionaries) as a base class in a class statement. Andrew Kuchling’s Python Warts paper devotes one of its longest sections to this language deficiency. Incidentally, Andrew’s paper What’s New in Python 2.2 gives a broad overview of what else is new in Python 2.2.

从Python 2.2开始就第一次引入了“type/class统一”的术语。Python作了一系列的改变以消除大部分built-in type和用户定义的类之间的不同。其中最明显的变化可能就是可以在一个class语句中把built-in type作为基类,如list和dict。

This is one of the biggest changes to Python ever, and yet it can be done with very few backwards incompatibilities. The changes are described in minute detail in a series of PEPs (Python Enhancement Proposals). PEPs are not designed to be tutorials, and the PEPs describing the type/class unification are sometimes hard to read. They also aren’t finished yet. That’s where this paper comes in: it introduces the key elements of the type/class unification for the average Python programmer.

这些改变在一系列的PEPs中有详细的描述。PEP不是教程,而且有时很难读懂。(的确如此,有些PEP不仅写了如何做,还有怎么做,看起来是够复杂的,至今我也没看过几篇。)这篇论文讲述了type/class unification中一些主要的元素,是给普通的Python程序员看的。

A bit of terminology: “classic Python” refers to Python 2.1 (and its patch releases such as 2.1.1) or earlier versions, while “classic classes” refer to classes defined with a class statement that does not have a built-in object amongst its bases: either because it has no bases, or because all of its bases are classic classes themselves – applying the definition recursively.

classic classes 是指在一个class语句定义中,基类不包括built-in对象的类(或者是没有基类,或者所有的基类都是classic class)。这是一种循环定义。

Classic classes are still a special category in Python 2.2. Eventually they will be totally unified with types, but because of additional backwards incompatibilities, this will be done after 2.2 is released (maybe not before Python 3.0). I’ll try to say “type” when I mean a built-in type, and “class” when I’m referring to a classic class or something that could be either; if it wouldn’t be clear from the context which interpretation is meant, I’ll try to be explicit, using “classic class” or “class or type”.

Classic class 在Python 2.2中仍然是一种特别的种类。最后它们终将被type所同化,但由于所带来的向后不兼容,这一过程将可能在2.2发布之后(也许在Python 3.0发布之前)完成。(Guido是在2.2发布时写的这篇论文,要记住。既然如此,建议大家以后编程还是直接用New-Style class好了,早用早好。)以后type是指built-in type,而class是指classic class或诸如此类的东西。如果从上下文很难分清某种解释是什么意思,将试着明确使用”classic class”或”class 或 type”。

那么我们可以理解为:type就是new-style class的称呼,而class已经专指classic class。由于现存两种不同OO机制,看上去的确挺乱的。那么在这篇文章我们可以这样理解。如果大家全部统一到new-style class上来,是用type还是class可能就无所谓了。不过,这篇文章很强调built-in type。为什么呢?因为对于new-style class来说,要么从object继承而来,要么从type继承而来,而且这种继承是在class语句中显示指明的。而object,type就是built-type。因此这一点就成为new-style class与classic class的根本区别。

阅读此Blog

这篇文章讲述了如何使用decorator在 Python 中实现抽象函数(或虚函数)的方法。通常老的作法是在基类存在一个空函数,但它会引发一个异常。比如:

>>> class A(object):
…     def abstract(self):
…         raise “You should implement this method”

 
>>> class B(A):
…     pass

>>> a=B()
>>> a.abstract()

Traceback (most recent call last):
  File “<pyshell#8>”, line 1, in -toplevel-
    a.abstract()
  File “<pyshell#3>”, line 3, in abstract
    raise “You should implement this method”
You should implement this method

可以看出,当在子类的实例上调用未在基类实现的方法时,会引发一个异常。不过,这也有一个问题,那就是如果一直没有调用这个未实现的方法,Python 是不会告诉你哪个方法未在子类中实现的。

那么本文提出的方法是:

  1. 写一个decorator方法,它的作用是给每个指定为abstract的函数加一个属性,用于指明此函数为abstract函数。在文章中是: f.abstract = True
  2. 在所有为abstract函数前加上此decorator描述
  3. 编写一个用于所有包含abstract函数的基类,它的作用是在__init__时,检查类中的所有函数,看一看是否存在函数的属性abstract有为True的,一旦存在,则说明此函数未被覆盖(override),因此引发异常。

不过,使用这种方法要求你在基类中一定要使用super(Class, self).__init__来调用父类的初始化函数,以保证对abstract函数能在初始化时被检查出来。

不过,记得在 python.list 邮件列表中看到过有关在Python中实现纯虚函数的讨论(很有趣)。

更有人谈到,即使我覆盖了父类的abstract函数,但子类中的函数仍然为空,如就一句pass,又如何呢?真是有些较真了。其实abstract的作用是让你一定要在继承时去实现,而不是要保证你实现的一定要有意义,那完全是人的事情。在讨论中Alex还使用metaclass实现了一把,有兴趣大家可以看一看。

阅读线索

PSF是Python Software Foundation的简称。在今年它第一次发起资助项目,今年有60多个提议被发起,最终有三个项目被批准进行资助。它们是:

  1. Brian Zimmer will manage the project Moving Jython Forward
  2. Ilya Etingof will manage the project Implementation of SNMPv3
  3. Greg Wilson will manage the project Software Engineering with Python for Scientist and Engineers

详情请阅读

Jython终于会有机会再发展了,如果再加上Sun的支持,在Java领域将会有一番作为。期待。其它两个还不太了解。

2004年12月30日

前面我介绍过Lazy计算,在此模块的作者Blog上还有一篇文章,阅读此文,关于future–一种特殊的承诺。仔细看一下它的实现,你会发现它是使用线程来实现的。作者的想法是通过线程来实现计算的并行化。它与Lazy不同,并不是当你需要值的时候才计算,而是一上来就计算,但是与主线程并行。如果在主线程需要这个值的时候,它会阻塞住,直到future计算完成。然后继继它的运算。不过,Python解释器一次只可以有一个线程在运行,因此这种技术在Python中现在是无法实现的,作者也说是一种技术上的练习。

再仔细想一想,作者提供的Lazy方法其实只适合于函数调用。因为函数是有返回值的,当你只是把函数返回值保存起来时,这个值并没有什么用。只有与外界发生关系时,如打印,这个值才真正有用。这也是Lazy实现的方法:考察所有与外界的交互,真正需要输出值时就会去调用真正的函数,不需要时则并不进行真正的计算。如果我调用的是一个无返回值的函数(或叫过程),这一方法就无效了。因为,过程没有返回值,因此你很难自动判断何时过程的处理是立刻需要的,何时过程的处理不是立刻需要的。

能否实现过程的Lazy方法呢?这个问题很有趣。

我写过几篇关于如何解析编码的Blog,有兴趣可以查一查,不过,那都是人工判断手工处理。虽然不麻烦,但毕竟不是很好。今天看到在列表里有人问:

Q:如何解析象“=?us-ascii?Q?Re=3A=20=5Bosg=2Duser=5D=20Culling=20problem?=”的邮件头

A:使用email.Header.decode_header即可。代码如下:

>>> import email.Header
>>> email.Header.decode_header(“=?us-ascii?Q?Re=3A=20=5Bosg=2Duser=5D=20Culling=20problem?=”)
[('Re: [osg-user] Culling problem’, ‘us-ascii’)]

可以看出,解析结果为一个列表,每个元素是一个二元组。二元组第一个元素为信息的原始内容,第二个元素为信息的编码。

为什么是个列表呢?因为可以在一个头里有多种编码,如:

>>> import email.Header
>>> email.Header.decode_header(“Re: Python 2.4 Tutorial =?UTF-8?B?566A5L2T5Lit5paH54mI?=”)
[('Re: Python 2.4 Tutorial', None), ('\xe7\xae\x80\xe4\xbd\x93\xe4\xb8\xad\xe6\x96\x87\xe7\x89\x88', 'utf-8')]

这就是有两段信息了。第一段没有编码,那么缺省为 ascii,第二段为utf-8编码。因此为了将其显示出来,你可以将其转为unicode。

>>> import email.Header
>>> header = email.Header.decode_header(“Re: Python 2.4 Tutorial =?UTF-8?B?566A5L2T5Lit5paH54mI?=”)
>>> def my_unicode(s, encoding):
…     if encoding:
…         return unicode(s, encoding)
…     else:
…         return unicode(s)
>>> print ”.join([my_unicode(s, encoding) for s, encoding in header])
Re: Python 2.4 Tutorial简体中文版

如果你再仔细看一下email.Message就知道如何解析一个邮件了。下面我简单举个例子:

>>> import email
>>> message = email.message_from_string(file(‘a.txt’).read())
>>> s = message.get_payload()
>>> message.is_multipart()
False

上面的例子假定邮件文件放在a.txt中。因此首先使用email.message_from_string来解析一个邮件,它将返回一个Message(在email.Message包中)对象。然后使用message的get_payload()得到邮件的内容。当内容只有一部分时,s为一个字符串。如果有多个部分时,s是一个Message对象的列表。通过message的is_multipart()可以判断出邮件是否由多部分组成。如果是多部分,对s中的每个对象分别调用get_payload()即可得到各部分的内容。

同时如果想得到其它的信息,如:Subject, To, Reply-To, Cc等信息,还可以使用message.get(‘Subject’)来得到相应的信息字符串。在我收到的许多邮件中都是中文的,象Subject, From, 之类的信息很可能有中文的标题或中文的用户名。这时可以使用上面解析邮件头的方法来转码。

这只是简单的用法,可以应付大多数标准的邮件。有些邮件很不标准,只有在标题中有相应的编码,但在正文中没有编码说明。这时,可能要连猜带蒙了。大家有兴趣自已学吧。我也是刚看而已。

2004年12月29日

最近在学习 New Style Class,这个东西一直想学,但本身比效抽象,而且还没有做为正式的文档放在 Python 的文档中。Python 从2.2版开始做为一个分水岭。在 2.2 之前的 Python 世界有三种类型的对象:

  • 类型(list, tuple, string等)
  • 类(由我们定义的)
  • 实例(由类生成)

到了 2.2 ,Python 把类型和类统一了。关于这一点 Guido 有一篇论文 Unifying types and classes,这是在 2.2 出来时写的。说实在的,当时看到这篇文章,真是没看懂。现在还是没看完。总之 New Style Class 带来许多新东西,不知道为会影响你什么,但如果想学,你会发现,世界突然变复杂了。

到了2.2,Python 世界可以看成有两种类型的对象:

  • type类型对象
  • 非type类型对象

先讲一下我参考的资料都在 python.org 的 document 下,其中有一个 New Style Class 的分类,访问这里

在学的过程中,我突然发现我对OO(面向对象)的理解真的是不很深刻,其中有部分原因我想是静态语言带给我的,象C++。很早在学校的是候我已经学过C++了,掌握了当时的面向对象的编程(那时有模板,还不叫现在的泛型)。但现在想一想,我对OO的理解是:

类---->实例


子类---->实例

类是一个模板,是生成实例的模板。你可以通过子类化(派生)来生成新的类。在C++中,不是单根继承的,可以多重继承。而Java是单根继承,不可以多重继承。但Interface可以实现多重继承。

从类到子类通过子类化(派生)实现。从类到实例通过实例化(不同的语言不同,象C++, Java用new来创建)实现。

但Python的对象观给我一些不同的感受(把我上面的对于静态语言的OO抛开吧)。

什么是对象?可以保存属性和方法的就叫做对象(也许不对,我先这么定义吧)。在Python中全部都是对象。在2.2后,Python引入了New Style Class概念,所有New Style Class都是单根继承了,从object继承而来。为了区别于2.2之前的不从object继承而来的类,把它们称为Classic Class。但在Python中还有更抽象的对象,那就是元类(metaclass),它是可以生成类的类,它的实例是类。那么三者间的关系是:

元类---->类---->实例

如果你看一看types模块,其中有一种类型叫做TypeType。在New Style Class的对象系统中,所有事物都是从两个基本类生成的:type和object。而object又是所有对象的根。

  • 元类通过子类化(type)可以生成新的元类。通过实例化可以生成类(object)。
  • 类通过子类化(object)可以生成子类。通过实例化可以生成实例。
  • 实例不能子类化,也不能再实例化了。

那么可以把能够实例化的对象叫做type对象,不能实例化的对象叫做non-type 对象。

在Python中可以通过调用issubclass(A,B)来判断A是否是B的子类和isinstance(A,B)来判断A是否是B的实例。比如:

>>> import types
>>> isinstance([], types.ListType)
True

是不是挺习惯呢?其实不用导入types也可以的

>>> isinstance([], list)
True

很有趣吧。在Python有一些内置函数称为factory函数,它们可以生成对应类型的实例对象,对于这种类型可以使用相应的factory函数的名字来判断,象:int, str, long, unicode, tuple, list, dict, bool等都可以这样做。

那么我们简化一下Python的对象系统,可以为:

  • 由type子类化而来的metaclass
  • 由type实例化和object子类化而来的类
  • 由类实例化而来的实例

主要分为这三类。当然这样并不是说python有两个根(type和object),只是说其它的对象从这两个级别上子类化或实例化而来。而且type和object之间的关系很微妙,比如做个试验:

>>> isinstance(type, object)
True
>>> issubclass(type, object)
True
>>> isinstance(object, type)
True
>>> issubclass(object, type)
False

可以看出type是object的子类,而object不是type的子类。但他们互为实例。不用管那么多只要知道分为这三类就行了。

子类化是通过class classname(superclass)语句来实现的。实例化是通过classname()来实现的。简单地说,metaclass的定义就是基类为type,New Style Class的定义就是(object)。不过因为type是object的子类,因此New Style Class其实是包含了metaclass在里面。前面的New Style Class理解为类比较好。

2004年12月28日

大家可以先看一下这篇 Blog ,了解一下什么是 Lazy 计算。阅读此文

简言之,Lazy是一种延迟计算,你创建了一种对结果的承诺,但只有当真正需要时才会去计算。只要你不是真是需要,你得到的并不是真正的结果。这里有一个包,大家可以下载:>>>下载<<<。这个包就是上面 Blog 的作者所写。

先让我们看一个例子:

>>> from LazyEvaluation import *
>>> def anton(a,b):
… return a+b

>>> f = lazy(anton)
>>> type(f)
<type ‘function’>
>>> a=f(5,6)
>>> type(a)
<class ‘LazyEvaluation.Promises.Promise’>
>>> a
<LazyEvaluation.Promises.Promise object at 0×00CAE370>
>>> print a
11

可以看出,a=f(5,6)得到一个Promise的类实例,而不是想象中的11。但如果你要打印它的值,得到的的确是11。这一点与通常的函数不一样,通常的函数返回的是数值。

实现的原理也不是太复杂。看这篇 Blog 可能会简单一些。阅读此Blog。主要的原理就是将函数封装到类中。当函数在调用时,根据当时的情况:如果是真的需要函数结果才进行计算,如果只是赋值或显示函数信息并不计算。如果你再看一看LazyEvaluation 包的实现,它就比上面那篇Blog的方法复杂,主要是使用了metaclass来实现了。并且对类的特殊函数的处理进行了分类,代码片段如下(Promise.py):

__magicmethods__ = ['__nonzero__', '__abs__', '__pos__', '__invert__',
'__neg__']

__magicrmethods__ = [('__radd__', '__add__'), ('__rsub__', '__sub__'),
('__rdiv__', '__div__'), ('__rmul__', '__mul__'),
('__rand__', '__and__'), ('__ror__', '__or__'),
('__rxor__', '__xor__'), ('__rlshift__', '__lshift__'),
('__rrshift__', '__rshift__'), ('__rmod__', '__mod__'),
('__rdivmod__', '__divmod__'), ('__rtruediv__', '__truediv__'),
('__rfloordiv__', '__floordiv__'), ('__rpow__', '__pow__')]

__magicfunctions__ = [('__cmp__', cmp), ('__str__', str),
('__unicode__', unicode), ('__complex__', complex),
('__int__', int), ('__long__', long), ('__float__', float),
('__oct__', oct), ('__hex__', hex), ('__hash__', hash),
('__len__', len), ('__iter__', iter), ('__delattr__', delattr),
('__setitem__', setitem), ('__delitem__', delitem),
('__setslice__', setslice), ('__delslice__', delslice)]

__magicdelayedfunctions__ = [('__getattr__', getattr),
('__getitem__', getitem), ('__call__', apply),
('__getslice__', getslice)]

只有最后一种才会产生delay(延迟计算的较果),其它几种都会立即计算。在后面的__init__中,根据不同的分类调用不同的包裹函数以产生不同的效果。同时我还注意到,需要Lazy的函数的参数也可以是Lazy的。不过在需要立即得到结果时,这个包会有一种机制把每个参数都强制生成结果(这是一种递归处理),然后得到最终的结果。

我想这种处理可能在程序启动时会有帮助,因为那时有些工作如果可以推到后面进行,会加快速度。

我的体会:

  1. 区分是否真正需要一个结果我想就是你是否真的要看到此数据,如显示,打印,保存等。如果只是一个中间状态,就可以认为不需要立即得到结果。
  2. 现在这个包只是对函数的封装。不知道以后会不会有类的封装(也不知道能否实现)。
  3. 这个包使用了元类编程,如果你对元类编程感兴趣不妨一学。不过,其中在调用原来函数时使用了apply方法,似乎不太好。

作者也考虑在2.4中使用decorator可以更好一些。不过,现在的情况是可以支持 Python 2.2.2+ 的版本,如果使用 2.4 可能兼容性要差一些。

同时还有一个链接可能内容差不多,有兴趣可以看一下。阅读这里

Q:如何检测一个程序正在运行

A:这是一个Linux版本。首先将你的程序进行包裹,得到此程序的pid值保存到文件中。

#!/bin/sh
mpg123 “$@” &
echo $! >/tmp/mpg123.pid


或者在 Python 2.4下:

#!/usr/bin/env Python
from subprocess import Popen

p = Popen(‘mpg123′)
pidfile = file(‘/tmp/mpg123.pid’, ‘w’)
pidfile.write(“%d” % p.pid)
pidfile.close()

然后再运行时使用 python 2.4 中的subprocess进行检查。主要是根据pid使用ps命令来检查。代码为:

from subprocess import Popen, PIPE

try:
     pidfile = file(‘/tmp/mpg123.pid’)
except IOError:
     print ‘mpg123 is dead’
else:
     pid = pidfile.read()
     t = Popen(‘ps p %s’ % pid, shell=True, stdout=PIPE).stdout
     if t.readlines() < 2:
         print ‘mpg123 is dead’
         os.remove(‘/tmp/mpg123.pid’)
         return 0
     return 2

不过上面的检查代码是认为在此之前已经运行过程序了,因此开始时的异常处理是需要必须存在pid文件。可以将其改为当pid文件不存在时就自动按第一次运行来处理即可。

注:subprocess是2.4版中新加入的模块。

阅读此线索

2004年12月27日

在网上经常看到一些问题与解答,虽然与已无关,但的确也增长见识。因此想记录下来,以免遗忘,更可以备查。

这些问题在网上的位置我尽可能留下来。

Q:一个程序用来处理用户的输入。想法是:如果用户输入是采用重定向方式执行,则将结果输出即可。如:./myprog.py <code 。如果用户没有采用重定向方式执行,则进入一种交互模式,在每行显示一个显示,提醒用户进行输入。问题是:我如何判断用户的输入是否是重定向的。

A:

>>> if sys.stdin.isatty():
…    print ‘Console’
… else:
…    print ‘Redirected’ 

阅读此线索