高级
易学易读易维护
兼顾解释性和编译性的优点
面向对象
一些函数化编程的结构
高效快速,扩展库众多
>>> a = 1
>>> a = 'asdf'
>>> a = '1'
>>> a + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects
>>> (1).__class__
int
python
ipython
help()
docstring
>>> help(list)
Help on class list in module __builtin__:
class list(object)
| list() -> new empty list
| list(iterable) -> new list initialized from iterable's items
|
| Methods defined here:
|
| __add__(...)
| x.__add__(y) <==> x+y
|
| __contains__(...)
| x.__contains__(y) <==> y in x
|
int: 100
, 0x3e
long
>>> import sys
>>> sys.maxint
>>> 9223372036854775807
>>> sys.maxint+1
>>> 9223372036854775808L
>>> 999999 ** 9
>>> 999991000035999916000125999874000083999964000008999999L
float: 1.1
complex: (9+3j)
>>> 5 / 2
2
>>> 5.0 / 2
2.5
>>> from __future__ import division
>>> 5 / 2
2.5
>>> 5 // 2
2
>>> s = 'python'
>>> s[0]
'p'
>>> s[0] = 'c' # TypeError
>>> s = 'hello douban'
>>> s[1:5]
'ello'
>>> s[:5]
'hello'
>>> s[-6:]
'douban'
>>> 'dou' in s
True
>>> 'yah' in s
False
>>> print '哈' * 5
哈哈哈哈哈
>>> '<span class="' + 'red' + '">' + 'button' + '</span>'
'<span class="red">button</span>'
>>> '<span class="%s">%s</span>' % ('red', 'button')
'<span class="red">button</span>'
>>> '{2}-{0}-{1}'.format('1', '4', '2013')
'2013-1-4'
>>> '{}:{}:{day}'.format(2009, 4, day='Sunday') # python 2.7
'2009:4:Sunday'
>>> coord = (3, 5)
>>> 'X: {0[0]}; Y: {0[1]}'.format(coord)
'X: 3; Y: 5'
>>> coord = {'latitude': '37.24N', 'longitude': '-115.81W'}
>>> 'Target: {latitude}, {longitude}'.format(**coord)
'Target: 37.24N, -115.81W'
# 不好:
s = ''
for i in seq:
s += chr(i)
# 好:
''.join(chr(i) for i in seq)
str:
'douban.com'
'\xe8\xb1\x86\xe7\x93\xa3'
unicode
u'\u8c46\u74e3'
转换
>>> '豆瓣'
'\xe8\xb1\x86\xe7\x93\xa3'
>>> '豆瓣'.decode('utf8')
u'\u8c46\u74e3'
>>> u'\u8c46\u74e3'.encode('utf8')
'\xe8\xb1\x86\xe7\x93\xa3'
(1000001, 'ahbei')
, (1,)
[1, 2, 3, 4, 5]
{'CEO': 'ahbei', 'Team Members': ['brant', 'hongqn', ...]}
set([1, 2, 3])
append
, insert
, pop
, remove
, reverse
, sort
index
, count
(没有find)list
模拟栈操作>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
list.insert
模拟队列不如 collections.deque
>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry") # Terry arrives
>>> queue.popleft() # The first to arrive now leaves
'Eric'
>>> li = ['n', 'b', 'a']
>>> li.sort()
>>> li.reverse()
>>>
# 与不可变对象比较:
>>> 'This is it.\n'.strip().upper()
'THIS IS IT.'
# 如果想要返回:
>>> sorted(li)
['a', 'b', 'n']
>>> def f(x): return x % 2 != 0 and x % 3 != 0
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]
>>> seq = range(8)
>>> def add(x, y): return x+y
>>> map(add, seq, seq)
[0, 2, 4, 6, 8, 10, 12, 14]
>>> squares = []
>>> for x in range(10):
... squares.append(x ** 2)
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> squares = map(lambda x: x ** 2, range(10))
>>> squares = [x ** 2 for x in range(10)]
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> combs = []
>>> for x in [1,2,3]:
... for y in [3,1,4]:
... if x != y:
... combs.append((x, y))
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
# 可以这样写:
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}
# Dict Comprehensions
>>> {x: x ** 2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
d['name'] = 'Jim'
, update
, setdefault
del d['CEO']
, pop
, popitem
, clear
get
, has_key
(deprecated), keys
, values
, items
iterkeys
, itervalues
, iteritems
copy
, deepcopy
>>> Kid = {'h': '165', 'like': {'laptop': 'mac', 'book': []}}
>>> Kim = Kid.copy()
>>> Kim['like']['book'].append('LOTR')
>>> Kid['like']
{'laptop': 'mac', 'book': ['LOTR']}
add
, discard
, remove
, clear
, copy
, update
union
(|), difference
(-), intersection
(*), symmetric_difference
(^)
issubset
, issuperset
, >
, <
# 不可赋值
>>> t = (1, 2)
>>> t[0] += 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
# 可做dict的key
>>> d = {}
>>> d[['a']] = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> d[(1,2)] = 1
>>> d = (1, ['A', 'B'])
>>> d[1].append('C')
>>> d
(1, ['A', 'B', 'C'])
>>> type((1))
<type 'int'>
>>> type((1,))
<type 'tuple'>
>>> word = 'hello',
>>> len(word)
1
>>> word
('hello',)
>>> result, = (1024,)
>>> result
1024
>>> a, b = b, a
Specialized container datatypes, providing
alternatives to Python’s general purpose built-in containers,
dict
, list
, set
, and tuple
.
>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> d = Counter(a=1, b=2, c=3, d=4)
>>> list(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
>>> c.subtract(d)
>>> c
Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})
# An example:
>>> words = re.findall(r'\w+', open('hamlet.txt').read().lower())
>>> Counter(words).most_common(10)
[('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631),
('you', 554), ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)]
>>> d = OrderedDict([('first', 1),
... ('second', 2),
... ('third', 3)])
>>> d.items()
[('first', 1), ('second', 2), ('third', 3)]
C:
if (x > 0)
if (y > 0)
printf('both available!\n');
else
printf('x not available!\n');
python:
if x > 0:
if y > 0:
print 'both available!'
else:
print 'x not available!'
if(not)
通过计算bool()来判断,因此可以直接利用对象的bool()值toys = []
# if len(toys) == 0: 或者 if toys != [] 不好
if not toys:
print "boring..."
x if condition else y
answer = 'yeah' if toys else 'no'
while
for i in ...
break
, continue
, pass
, ...def find_cat(cat, boxes):
for box in boxes:
if box.isempty():
continue
elif cat in box:
print "The cat is in", box
break
else:
print "We have lost the cat."
# 不好
for i in range(len(seq)):
foo(seq[i], i)
# 好:
for i, item in enumerate(seq):
foo(item, i)
# 不好:
for i in xrange(len(seq1)):
foo(seq1[i], seq2[i])
# 好:
for i, j in zip(seq1, seq2):
foo(i, j)
for i, j in itertools.izip(seq1, seq2):
foo(i, j)
Exception
的子类
KeyboardInterrupted
和SystemExit
ValueError
, KeyError
, etc...try:
do_something()
except KeyError:
handle_key_error()
except Exception, e:
import traceback
traceback.print_exc()
finally:
release_resources()
raise
try:
do_something()
except:
pass
# fibo.py
author = 'su27'
_author_age = 27
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while b < n:
print b,
a, b = b, a+b
>>> import fibo
>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.author
'su27'
>>> fibo.__name__
'fibo'
def foo():
pass
foo.__doc__ = '呀,刚才忘了加上doc字符串'
foo.version = 0.2
>>> import fibo
>>> from fibo import fib, author
>>> from fibo import * # 绝大多数情况下要避免这样用
>>> author
'su27'
>>> _author_age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '_author_age' is not defined
for element in [1, 2, 3]:
print element
for key in {'one':1, 'two':2}:
print key
for key, value in {'one':1, 'two':2}.items():
print key, value
for char in "123":
print char
for line in open("myfile.txt"):
print line
xrange
和range
iterkeys
, iteritems
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
it.next()
StopIteration
iterator
描述了一个数据流。iterator
支持next()
方法,返回其描述的数据流的下一个元素。iterator
,就说这个对象能被迭代(iterable
)。class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data [self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... print char
def reverse(data):
for char in data[::-1]:
yield char
>>> for char in reverse('AI'):
... print char
I
A
def gcomb(x, k): # 生成元素列表x中选k个的所有可能组合
if k == 0:
yield []
elif k <= len(x):
first, rest = x[0], x[1:]
# 第一个元素要么选,要么不选
for c in gcomb(rest, k-1):
c.insert(0, first)
yield c
for c in gcomb(rest, k):
yield c
>>> list(gcomb([1, 2, 3, 4], 2))
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
>>> sum(x * y for x,y in zip(xvec, yvec)) # dot product
260
>>> page = ('this is a big dog', 'this is a small cat')
# List comprehension to set
>>> words = set([word for line in page for word in line.split()])
# Set comprehension
>>> words = {word for line in page for word in line.split()}
# Generator expression to set
>>> words = set(word for line in page for word in line.split())
>>> words
set(['a', 'this', 'big', 'is', 'dog', 'cat', 'small'])
def foo(value):
return value, value % 2
def net_conn(host, port=80):
print "connect to %s:%s" % (host, port)
>>> net_conn('douban', 8080)
>>> net_conn(port=8080, host='douban')
>>> net_conn(host='douban', 8080)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
>>> def hero_string(heroes=[]):
....: heroes.append("me")
....: return ', '.join(heroes)
>>> hero_string(['batman'])
'batman, me'
>>> hero_string(['batman', 'superman'])
'batman, superman, me'
>>> hero_string()
'me'
>>> hero_string()
'me, me'
>>> hero_string()
'me, me, me'
def net_conn(scheme, host='douban', port=80):
print "connect to %s://%s:%s" % (scheme, host, port)
>>> net_conn('http', 'douban', host='8080')
>>> net_conn('douban', scheme='http', port=8080)
>>> net_conn(port=8080, host='douban', 'http')
>>> net_conn(port=8080, host='douban', scheme='http')
>>> net_conn(scheme='http', 'douban', port='8080')
>>> net_conn('http', port='8080')
>>> net_conn('http', 'douban')
>>> net_conn('http', 'douban', 8080, 'tcp')
def net_conn(scheme, host='douban', port=80):
print "connect to %s://%s:%s" % (scheme, host, port)
>>> net_conn('http', 'douban', host='8080')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: net_conn() got multiple values for keyword argument 'host'
>>> net_conn('douban', scheme='http', port=8080)
File "<stdin>", line 1
TypeError: net_conn() got multiple values for keyword argument 'scheme'
>>> net_conn(port=8080, host='douban', 'http')
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
>>> net_conn(port=8080, host='douban', scheme='http')
connect to http://douban:8080
def net_conn(scheme, host='douban', port=80):
print "connect to %s://%s:%s" % (scheme, host, port)
>>> net_conn(scheme='http', 'douban', port='8080')
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
>>> net_conn('http', port='8080')
connect to http://douban:8080
>>> net_conn('http', 'douban')
connect to http://douban:80
>>> net_conn('http', 'douban', 8080, 'tcp')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: net_conn() takes at most 3 arguments (4 given)
def func(arg1, arg2, *rest):
print 'arg1:', arg1
print 'arg2:', arg2
for arg in rest:
print 'extra arg:', arg
def func(arg1, arg2, **rest):
print 'arg1:', arg1
print 'arg2:', arg2
for arg in rest:
print 'extra arg: %s=%s' % (arg, rest[arg])
>>> def f(x, *args, **kwargs):
... print 'x:', x, 'args:', args, 'kwargs:', kwargs
...
>>> f(1)
x: 1 args: () kwargs: {}
>>> f(1, 2, 3)
x: 1 args: (2, 3) kwargs: {}
>>> f(1, 2, n=3)
x: 1 args: (2,) kwargs: {'n': 3}
>>> sorted([('Bo', 24), ('Yi', 23), ('Si', 31)], key=lambda p: p[1])
[('Yi', 23), ('Bo', 24), ('Si', 31)]
>>> map((lambda x: x+' me'), ['love', 'hate', 'kill'])
['love me', 'hate me', 'kill me']
>>> reduce((lambda x,y: x+y), range(10))
45
# but this is faster:
>>> from operator import add
>>> reduce(add, range(10))
def my_songs(request):
if request.user:
songs = request.user.songs
return render_songs_list(songs=songs)
else:
raise NotLoginError()
@require_login
def my_songs(request):
songs = request.user.songs
return render_songs_list(songs=songs)
MCKEY_SONGS = 'songs:%s'
def get_songs(user_id):
songs = mc.get(MCKEY_SONGS % user_id)
if songs is None: # "if not songs"?
rows = store.execute('select id from user_song '
'where user_id=%s', user_id)
songs = [id for id, in rows]
mc.set(MCKEY_SONGS, songs, ONE_DAY)
return songs
@cache(MCKEY_SONGS, ONE_DAY)
def get_songs(user_id):
rows = store.execute('select id from user_song '
'where user_id=%s', user_id)
return [id for id, in rows]
def require_login(func):
def _(request, *args, **kwargs):
if request.user:
return func(request, *args, **kwargs)
raise NotLoginError()
return _
@require_login
def my_songs(request):
return render_songs_list(request.user.songs)
@f
def func(): pass
def func(): pass
func = f(func)
def cache(key, expire=0):
def cached_func(original_func):
def _(*args, **kw):
mckey = key % args
result = mc.get(mckey)
if result is None:
result = original_func(*args, **kw)
mc.set(mckey, result, expire)
return result
return _
return cached_func
@cache(MCKEY_SONGS, ONE_DAY)
def get_songs(user_id):
rows = store.execute('select id from user_song '
'where user_id=%s', user_id)
return [id for id, in rows]
@f(arg)
def func(): pass
def func(): pass
func = f(arg)(func)
class cache(object):
def __init__(self, key, expire=0):
self.key = key
self.expire = expire
def __call__(self, original_func):
def cached_func(*args, **kw):
mckey = self.key % args
result = mc.get(mckey)
if result is None:
result = original_func(*args, **kw)
mc.set(mckey, result, expire)
return result
return cached_func
Menghan
Object oriented programming
Zen of Python
Practices
define
class Rectangle(object):
'''doc for Rectangle'''
def __init__(self, x, y): self.x = x; self.y = y
def area(self): return x * y
@classmethod
def return_doc(cls): return cls.__doc__
>>> r1 = Rectangle(10, 20)
>>> print r1.area()
200
>>> r1.x = 15
>>> print r1.area()
300
>>> del r1.x
>>> print r1.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Rectangle' object has no attribute 'x'
reflection
>>> isinstance(r1, Rectangle)
True
>>> hasattr(r1, 'area')
True
>>> callable(r1.x)
False
>>> r1.__doc__
'doc for Rectangle'
python defined attributes
>>> r1.__doc__
'doc for Rectangle'
>>> str(r1)
'<__main__.Rectangle object at 0x2a9430>'
def __str__(self): return '<Rectangle %sx%s>' % (self.x, self.y)
>>> str(r1)
'<Rectangle 15x20>'
>>> d = r1.__dict__
>>> d['x']
15
>>> d['z'] = 30
>>> r1.z
30
>>> r1.__class__
<class '__main__.Rectangle'>
>>> Rectangle.__name__
'Rectangle'
>>> Rectangle.__bases__
(<type 'object'>,)
>>> Rectangle.__mro__
(<class '__main__.Rectangle'>, <type 'object'>)
private attributes and methods
def method(self): print 'can call'
def __method(self): print 'cannot call'
def public_method(self): self.__method()
>>> r1.method()
can call
>>> r1.__method()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Rectangle' object has no attribute '__method'
>>> r1.public_method():
cannot call
>>> r1._Rectangle__method():
cannot call
def __init__(self):
self.__x = 1
>>> r1.__x
?
>>> r1.__y = 1
?
>>> print r1.__y
?
class private varables and methods
class Rectangle(object):
public_attr = 1
__private_attr = 2
@classmethod
def public_method(cls): print 'can call'
@classmethod
def __private_method(cls): print 'cannot call'
>>> Rectangle.public_attr
?
>>> Rectangle.__private_attr
?
>>> Rectangle.public_method()
?
>>> Rectangle.__private_method()
?
>>> Rectangle.__private_attr2 = 1
>>> Rectangle.__private_method2 = lambda cls: 'hehe'
>>> Rectangle.__private_attr2
?
>>> Rectangle.__private_method2()
??
class Area(object):
def __get__(self, obj, type):
return obj.x * obj.y
def __set__(self, obj, val):
obj.x = 2
def __delete__(self, obj):
print 'hi'
class Rectangle(object):
area = Area()
>>> r1 = Rectangle(); r1.x = 1; r1.y = 2;
>>> r1.area
2
>>> r1.area = 'a'
>>> r1.x
2
>>> del r1.area
hi
In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. (only for new style objects)
descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
staticmethod, classmethod, Method, property are implemented by descriptor
IMO: class's namespace
class C(object):
@staticmethod
def plus(x, y):
return x + y
>>> C.plus(2, 3)
5
>>> C().plus(2, 3)
5
class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
class C(object):
@classmethod
def name(cls):
return cls.__name__
>>> C.name()
'C'
>>> C().name()
'C'
class ClassMethod(object):
"Emulate PyClassMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc
.middle[ 还需要举例吗?
class Method(object):
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
class C(object): pass
x = C()
x.f1 = lambda : 'hi, f1'
x.f2 = types.MethodType(lambda self: 'hi, f2', x, C)
print x.f1()
print x.f2()
class(C):
def __init__(self):
self._value = 0
def _get_value(self):
return self._value
def _set_value(self, value):
if value < 0:
value = 0
self._value = value
value = property(_get_value, _set_value)
>>> c = C()
>>> c.value
0
>>> c.value = 1
>>> c.value
1
>>> c.value = -1
>>> c.value
0
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, "unreadable attribute"
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError, "can't set attribute"
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError, "can't delete attribute"
self.fdel(obj)
Lookup class attribute
class C(object): pass
getattr(C, 'v')
Controlled by __getattribute__
C.__dict__
for v
v.__get__
C.__metaclass__.__getattr__
, default implement: raise AttributeErrorLookup instance attribute
class C(object): pass
c = C()
getattr(c, 'v')
Controlled by __getattribute__
c.__dict__
for vC.__dict__
for v
v.__get__
C.__metaclass__.__getattr__
, default implement: raise AttributeErrorsetattr
?
What is MRO?
MRO: Method resolution order
Why is there an MRO?
The way to look up method in multiple inheritance
class Base(object):
pass
class C(Base):
pass
>>> print C.__mro__
(<class '__main__.C'>, <class '__main__.Base'>, <type 'object'>)
Why do we need MRO?
class Base(object): pass
class A(Base):
def save(self): pass
class B(A): pass
class C(A):
def save(self): pass
class D(B, C): pass
>>> d = D()
>>> d.save() # ???
Why do we need MRO?
class Base(object): pass
class A(Base):
def save(self): pass
class B(A): pass
class C(object):
def save(self): pass
class D(B, C): pass
>>> d = D()
>>> d.save() # ???
Why do we need MRO?
O = object
class X(O): pass
class Y(O): pass
class A(X, Y): pass
class B(Y, X): pass
class C(A, B): pass
What's C's MRO?
Classic style class, new style class and "C3" Algorithm in python2.3
rules:
Refer to the base class explicitly
class A(object):
def save(self):
print 'save A'
class B(A):
def save(self):
A.save(self)
print 'save B'
class C(A):
def save(self):
A.save(self)
print 'save C'
class D(B, C):
def save(self):
B.save(self)
C.save(self)
print 'save D'
d = D()
d.save() # call A.save() twice!!
solution:
class A(object):
def save(self): print 'save A'
class B(A):
def save(self):
super(B, self).save()
print 'save B'
class C(A):
def save(self):
super(C, self).save()
print 'save C'
class D(B, C):
def save(self):
super(D, self).save()
print 'save D'
>>> d = D()
>>> d.save()
save A
save C
save B
save D
Pros and cons
Advices
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
DONE
vim: set softtabstop=4 shiftwidth=4 tabstop=4 expandtab: