brizz
8/14/2013 - 12:57 AM

python_intro.md


Python 简介

@su27 and @menghan

What is Python?

Python: 优雅而健壮的编程语言

  • 高级

  • 易学易读易维护

  • 兼顾解释性和编译性的优点

  • 面向对象

  • 一些函数化编程的结构

  • 高效快速,扩展库众多


What is Python?

  • 动态语言
>>> 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
 |

Data Structures


Data Structures

数值

  • int: 100 , 0x3e

  • long

    • 并不是C或者其他编译类型语言的长整型
>>> import sys

>>> sys.maxint
>>> 9223372036854775807

>>> sys.maxint+1
>>> 9223372036854775808L

>>> 999999 ** 9
>>> 999991000035999916000125999874000083999964000008999999L
  • float: 1.1

  • complex: (9+3j)


Data Structures

数值运算

  • 除法
>>> 5 / 2
2
>>> 5.0 / 2
2.5

>>> from __future__ import division
>>> 5 / 2
2.5
>>> 5 // 2
2
  • 其他运算

Data Structures

String

  • 字符串是不可变的
>>> 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

Data Structures

String

  • 字符串的连接与格式化
>>> 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'

Data Structures

String

  • 字符串的连接与格式化
# 不好:

s = ''
for i in seq:
    s += chr(i)

# 好:

''.join(chr(i) for i in seq)

Data Structures

String

  • 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'

Data Structures

字符串的方法

find, index, rfind, rindex, count

startswith, endswith, isalpha, isdigit, isalnum, islower, isupper, isspace

encode, decode, lower, upper, strip, lstrip, rstrip, replace

split, rsplit, splitlines

join


Data Structures

容器

tuple: (1000001, 'ahbei') , (1,)

list: [1, 2, 3, 4, 5]

dict: {'CEO': 'ahbei', 'Team Members': ['brant', 'hongqn', ...]}

set: set([1, 2, 3])


Data Structures

list

  • 切片
  • 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'

Data Structures

list

  • 改变一个可变对象的方法,通常没有返回值
>>> li = ['n', 'b', 'a']
>>> li.sort()
>>> li.reverse()
>>>

# 与不可变对象比较:
>>> 'This is it.\n'.strip().upper()
'THIS IS IT.'

# 如果想要返回:
>>> sorted(li)
['a', 'b', 'n']

Data Structures

FP tools for list

filter

>>> def f(x): return x % 2 != 0 and x % 3 != 0
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]

map

>>> seq = range(8)
>>> def add(x, y): return x+y
>>> map(add, seq, seq)
[0, 2, 4, 6, 8, 10, 12, 14]

Data Structures

列表解析

A.

>>> squares = []
>>> for x in range(10):
...     squares.append(x ** 2)
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

B.

>>> squares = map(lambda x: x ** 2, range(10))

C. (list Comprehension)

>>> squares = [x ** 2 for x in range(10)]

Data Structures

列表解析: 多变量及过滤条件

exam A: flatten a list

>>> 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]

exam B:

>>> 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]

Data Structures

dict

>>> 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}
  • set: d['name'] = 'Jim', update, setdefault
  • delete: del d['CEO'], pop, popitem, clear
  • get: get, has_key (deprecated), keys, values, items
  • iter: 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']}

Data Structures

set

set的修改:

add, discard, remove, clear, copy, update

set的运算:

union(|), difference(-), intersection(*), symmetric_difference(^)

子集的判断:

issubset, issuperset, &gt;, &lt;

Data Structures

tuple

  • 元组是不可变类型
# 不可赋值
>>> 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
  • mutable: list, dict, set, 类实例
  • immutable: 数值类型, 字符串, tuple

Data Structures

tuple

  • 某种程度的可变
>>> 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',)

Data Structures

tuple

  • Unpacking
>>> result, = (1024,)
>>> result
1024

>>> a, b = b, a

Data Structures

collections

Specialized container datatypes, providing alternatives to Python’s general purpose built-in containers, dict, list, set, and tuple.

Counter

>>> 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)]

Data Structures

collections

  • OrderedDict
>>> d = OrderedDict([('first', 1),
...                  ('second', 2),
...                  ('third', 3)])
>>> d.items()
[('first', 1), ('second', 2), ('third', 3)]
  • deque
  • defaultdict
  • namedtuple

Control Flow Tools


Control Flow Tools

分支

  • if...elif...else
  • 悬挂问题

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!'

Control Flow Tools

分支

  • 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'

Control Flow Tools

循环

  • while
  • for i in ...
  • break, continue, pass, ...
  • while和for都可以有else
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."

Control Flow Tools

循环

  • Example A
# 不好
for i in range(len(seq)):
    foo(seq[i], i)
# 好:
for i, item in enumerate(seq):
    foo(item, i)
  • Example B
# 不好:
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)

Control Flow Tools

异常

  • 所有异常都是Exception的子类
    • 除了KeyboardInterruptedSystemExit
    • ValueError, KeyError, etc...
try:
    do_something()
except KeyError:
    handle_key_error()
except Exception, e:
    import traceback
    traceback.print_exc()
finally:
    release_resources()
  • raise

Control Flow Tools

异常

  • 不要做这样的事情
try:
    do_something()
except:
    pass

Modules


Modules

A module is a file containing Python definitions and statements.

# 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 modules to use
>>> 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'

Modules

名称空间与作用域

Local, Global, 与 Built-ins 名称空间

自由区隔的名字空间

def foo():
    pass
foo.__doc__ = '呀,刚才忘了加上doc字符串'
foo.version = 0.2

import即是把名字导入当前的名称空间


Modules

import的方式

>>> 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

Note: import的时候, 被import的模块代码将被执行


Iterators


Iterators

迭代器

  • 迭代器用起来是什么感觉?
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
  • xrangerange
  • iterkeys, iteritems

Iterators

迭代器

  • for在这里做了什么?
>>> 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

Iterators

  • 一个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

Iterators

Generators

生成器

  • 生成器是一种简单创建迭代器的特殊函数。生成器执行后返回的是一个迭代器。
  • 执行到yield的时候,其运行状态会被挂起,下次调用next()时恢复执行。
def reverse(data):
    for char in data[::-1]:
        yield char

>>> for char in reverse('AI'):
...     print char
I
A

Iterators

Generators

  • 生成器可以有一个不带参数的return,表示数据流的结束,跟执行到底效果一样。
  • 生成器可以接受传入值。
  • 不同于一般函数,生成器可以从多个不同位置开始运行、结束运行和挂起。
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]]

Iterators

Generators

Generator Expressions

生成器表达式

  • 把列表解析的方括号换成圆括号,就是生成器表达式,它返回一个迭代器。
  • 迭代器按需计算数据,而列表解析需要一次性把所有数据实体化。 处理无限长度或海量数据时,生成器表达式更佳。
>>> 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'])

Iterators

tools for iterable

any / all

  • any([0, 1, 0]) => True
  • all([1, 1, 0]) => False

itertools

  • itertools.count() => 0, 1, 2, ...
  • itertools.cycle([1, 2]) => 1, 2, 1, 2, ...
  • itertools.chain([1, 2], ('a', 'b')) => 1, 2, 'a', 'b'
  • map的iter版:itertools.imap
  • filter的iter版:itertools.ifilter
  • zip的iter版:itertools.izip
  • more...

Functions


Functions

python中的函数

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

Functions

  • 可变对象不能做参数默认值
>>> 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'

Functions

哪些调用是错的?

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')

Functions

哪些调用是错的?

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

Functions

哪些调用是错的?

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)

Functions

可变长度的参数

  • 用一个元组接受可变长默认参数
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])

Functions

可变长度的参数

>>> 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}

Functions

匿名函数与lambda

  • lambda [arg1[, arg2, ..., argN]]: expression
>>> 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))

Functions

Decorator

  • Case A
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)

Functions

Decorator

  • Case B
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]

Functions

Decorator

  • How to write a decorator(case A)
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)
  • decorator做了什么
@f
def func(): pass
def func(): pass
func = f(func)

Functions

Decorator

  • How to write a decorator(case B)
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]
  • 带参数的decorator做了什么
@f(arg)
def func(): pass
def func(): pass
func = f(arg)(func)

Functions

Decorator

  • Another way(callable object)
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

Programming Python Part II


Menghan


Outline

  1. Object oriented programming

    • Basic
    • Descriptor
    • Attribute lookup
    • MRO and super
  2. Zen of Python

  3. Practices


Object oriented programming

  • Basic
  • Descriptor
  • Attribute lookup
  • MRO and super

Basic

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'

Basic

reflection

>>> isinstance(r1, Rectangle)
True
>>> hasattr(r1, 'area')
True
>>> callable(r1.x)
False
>>> r1.__doc__
'doc for Rectangle'

Basic

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'>)

Basic

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

Basic

def __init__(self):
    self.__x = 1

>>> r1.__x
?
>>> r1.__y = 1
?
>>> print r1.__y
?

Basic

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()
?

Basic

>>> Rectangle.__private_attr2 = 1
>>> Rectangle.__private_method2 = lambda cls: 'hehe'
>>> Rectangle.__private_attr2
?
>>> Rectangle.__private_method2()
??

Descriptor

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

Descriptor

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

Descriptor examples

staticmethod, classmethod, Method, property are implemented by descriptor


Staticmethod

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

Staticmethod by descriptor

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

Classmethod

class C(object):

    @classmethod
    def name(cls):
        return cls.__name__

>>> C.name()
'C'
>>> C().name()
'C'

Classmethod by descriptor

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

Method


Method

.middle[ 还需要举例吗?


Method by descriptor

    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()

Property

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

Property by descriptor

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)

Attribute lookup

Lookup class attribute

class C(object): pass
getattr(C, 'v')

Controlled by __getattribute__

  1. lookup C.__dict__ for v
    1. if v is descriptor: return v.__get__
    2. else: return v
  2. lookup C's base classes
  3. return C.__metaclass__.__getattr__, default implement: raise AttributeError

Attribute lookup

Lookup instance attribute

class C(object): pass
c = C()
getattr(c, 'v')

Controlled by __getattribute__

  1. lookup into C or C's base classes for overriding(data) descriptor
  2. lookup c.__dict__ for v
  3. lookup C.__dict__ for v
    1. if v is descriptor: return v.__get__
    2. else: return v
  4. lookup C's base classes
  5. return C.__metaclass__.__getattr__, default implement: raise AttributeError

Attribute lookup

setattr

?


MRO and super

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'>)

MRO and super

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()  # ???

MRO and super

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()  # ???

MRO and super

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?


MRO and super

Classic style class, new style class and "C3" Algorithm in python2.3

rules:

  1. keep base class order
  2. inheritor first

MRO and super

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!!

MRO and super

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

MRO and super

Pros and cons

Advices


Zen of Python

>>> 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!

Practices


DONE


vim: set softtabstop=4 shiftwidth=4 tabstop=4 expandtab: