python3 is not compatible with lower version of python e.g. python2.x. Listed are some python basics and differences between python2 and python3.

来源自廖雪峰大神的博客

Triple Quotes Link to heading

three single or double quotes. The syntax for triple quotes consists.

None Link to heading

  • None 和 0 是不同的. None 是一个特殊的空值, 而0是有意义的.

python 动态语言 Link to heading

  • python 为动态语言, 变量本身类型不固定, 比如 a = 0,也可以给 a 赋值为 a = ‘abc’; 相对应的存在静态语言, 变量类型是固定的, 如 Java. 与静态语言相比, 动态语言更灵活.

return None Link to heading

  • def func() 的返回值是由 return 决定的. 如果没有 return 语句, 返回 None. return None 可以简写为 return.

空函数 Link to heading

  • 空函数. 想定义一个什么事情也不做的函数, 可以用 pass 语句. 这种函数通常为了程序能跑起来, 先把函数名写上, 然后慢慢想.
def xxx():
	pass

返回多值,函数 Link to heading

  • python 返回多个值的函数可以用 tuple 来实现.

python 允许给多个变量赋值 Link to heading

Python允许你同时为多个变量赋值。例如:

a = b = c = 1

以上实例,创建一个整型对象,值为1,三个变量被分配到相同的内存空间上。

您也可以为多个对象指定多个变量。例如:

a, b, c = 1, 2, "john"

以上实例,两个整型对象1和2的分配给变量a和b,字符串对象"john"分配给变量c。

参数类型 Link to heading

  • 参数

    • 函数的默认参数. 如 power(x, n=2). 设置默认参数时, 一, 默认参数必须在后, 否则会报错; 二, 默认参数必须指向不变对象, 如 str, None.

    • 可变参数(positional expansion, meaning unpacking arguments lists). 先看一下例子

    def calc(*numbers):
    	sum = 0
    	for n in numbers:
    		sum = sum + n * n
    	return sum
    

    在函数内部, 参数 numbers 接收到一个 tuple, 所以调用改函数时, 可以传入任意个参数. 如

    calc(1, 2)
    calc()
    

    函数会把接收到的参数先组成一个 tuple, 然后根据这个 tuple 进行运算.

    如果我现在手上有一个 list 或者 tuple, 如 nums = [1,2,3], 我怎么调用可变参数的函数呢?

    calc(*nums) 即可解决.

    *nums 表示把 nums这个 list 所有元素作为可变参数传进去.

    • 关键字参数(keyword expansion, meaning keyword arguments unpacking). 和可变参数的原理类似, 关键字参数会先把所接受的参数组装出一个 dict, 然后把 dict 传进去.
    def person(name, age, **kw):
    	print('name: ', name, 'age: ', age, 'other: ', kw)
    
    >>>extra = {'city': 'Beijing'}
    >>>person('Bob', 35, city = 'Beijing')
    name: Bob age: 35 other: {'city': 'Beijing'}
    >>>person('Bob', 35, **extra)
    name: Bob age: 35 other: {'city': 'Beijing'}
    

    **extra表示把 extra 这个 dict 所有 key-value 用关键字 **kw 传入. kw 将要获得一个 dict, 这个 dict 是 extra 的拷贝, 对 kw 的改动不会改变 extra.

    • 命名关键字参数. 关键字参数调用者可以传入任意不受限制的参数, 然而命名关键字参数可以限制关键字参数的名字. 如
    def person(name, age, *, city, job):
    	print (name, age, city, job)
    

    命名关键字参数需要一个特殊分隔符*, *后面的参数被视为命名关键字参数.

    注意: 如果函数中有可变参数在前面, 不需要*作为分隔符, 如:

    def person(name, age, *args, city, job):
    	print(name, age, args, city, job)
    

    还有如果使用命名关键字参数必须传入参数名, 否则会报错:

    >>> person('Jack', 24, 'Beijing', 'Engineer')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: person() takes 2 positional arguments but 4 were given
    
    • 参数组合. 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

    对于任意函数,都可以通过类似 func(*args, **kw) 的形式调用它,无论它的参数是如何定义的

    def f1(a, b, c=0, *args, **kw):
        print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
    
    def f2(a, b, c=0, *, d, **kw):
        print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
    
    >>> args = (1, 2, 3, 4)
    >>> kw = {'d': 99, 'x': '#'}
    >>> f1(*args, **kw)
    a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
    >>> args = (1, 2, 3)
    >>> kw = {'d': 88, 'x': '#'}
    >>> f2(*args, **kw)
    a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
    
    • 小结

    Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。

    默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!

    要注意定义可变参数和关键字参数的语法:

    *args是可变参数,args接收的是一个tuple;

    **kw是关键字参数,kw接收的是一个dict。

    以及调用函数时如何传入可变参数和关键字参数的语法:

    可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过args传入:func((1, 2, 3));

    关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过kw传入:func({‘a’: 1, ‘b’: 2})。

    使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

    命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。

    定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

generator and tuple Link to heading

  • Python3 generator and tuple.

They are different but look the same.

Tuple is like this, t = (1, 2, 3)

Generator is like this, g = (x *x for x in list)

Generator will not create the whole list, but will create one by one. Using g.next(), you will get next element.

变量指向函数 Link to heading

  • 变量可以指向函数

如: f = abs()

又如:

def add(x, y, f):
	return f(x)+f(y)

内建函数 Link to heading

  • 几大内建函数

    • map and reduce

    map() 函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

    reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

    reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
    
    • filter

    filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。 filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

闭包 closure Link to heading

  • 函数可以作为返回值, 闭包 closure

函数中可以定义函数, 并且返回这个函数, 返回的结果类似于

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>

调用f()时, 才能得到求和结果.

注意到返回的函数在其定义内部引用了局部变量,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用

再有, 返回的函数并没有立刻执行,而是直到调用了f()才执行。

例子:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。

你可能认为调用f1()f2()f3()结果应该是149,但实际结果是:

>>> f1()
9
>>> f2()
9
>>> f3()
9

全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f(). 返回 f 返回的是函数, 不马上被执行; 返回 f(i)返回的是函数的值, 马上被执行 f 这个函数, 得出值.
    return fs

lambda Link to heading

  • lambda 匿名函数

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

lambda x: x*x

偏函数 Link to heading

  • 偏函数

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

装饰器(常见于 framework, 如 flask 里面的@route) Link to heading

  • 装饰器 decorator

装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数. 以下是个例子:


class Coordinate(object):
     def __init__(self, x, y):
         self.x = x
         self.y = y
     def __repr__(self):
         return "Coord: " + str(self.__dict__)
def add(a, b):
     return Coordinate(a.x + b.x, a.y + b.y)
def sub(a, b):
     return Coordinate(a.x - b.x, a.y - b.y)
one = Coordinate(100, 200)
two = Coordinate(300, 200)
add(one, two)
Coord: {'y': 400, 'x': 400}

def wrapper(func):
     def checker(a, b): # 1
         if a.x < 0 or a.y < 0:
             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
         if b.x < 0 or b.y < 0:
             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
         ret = func(a, b)
         if ret.x < 0 or ret.y < 0:
             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
         return ret
     return checker
add = wrapper(add)
sub = wrapper(sub)
sub(one, two)
Coord: {'y': 0, 'x': 0}
add(one, three)
Coord: {'y': 200, 'x': 100}

_ and __ Link to heading

  • _ and __

表示定义的变量或者函数为 private. private 不应该被直接调用. 在 class 中, private 变量不可以被直接调用, 如 __name. 另一种和 private 变量长得很像的是 __name__, 这种是特殊变量, 特殊变量是可以被直接访问的, 所以不能用这种变量名.

最后注意下面的这种错误写法:

>>> bart = Student('Bart Simpson', 98)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'

表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。不信试试:

>>> bart.get_name() # get_name()内部返回self.__name
'Bart Simpson'

__init__ Link to heading

  • __init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

  • oop 任意绑定 attribute

可以任意绑定数据, 对于两个实例变量, 虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:

>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'

polymophism Link to heading

  • Polymorphism 多态

开闭原则: 调用方只管调用,不管细节.

对扩展开放:允许新增一个 class 的子类

对修改封闭:不需要修改依赖 class 类型的已经存在的函数。

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写(override)(override is a type of function which occurs in a class which inherits from another class)。

动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

  • isinstance使用方法

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

  • dir()的使用

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法.

  • MethodType

给实例绑定方法

s.set_age = MethodType(set_age, s) # 给实例绑定一个方法

只给这一个实例绑定方法, 对其他实例无效

  • __slots__

定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

如果硬绑定, 会得到AttributeError错误

@property Link to heading

  • @property

把’@property’加到一个函数上让这个函数变成属性函数时, 它会本身把这个函数变成只读(getter 方法), (如果此函数叫做 def fee), 那么另外可以设置一个 setter 函数,只是要加上’@fee.setter’

例如:

class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth

以上 birth 是一个 getter, 第二个 birth 是一个 setter.

mixin Link to heading

  • MixIn 继承

    如果让Ostrich除了继承Bird 外还继承Runnable, 那么就需要多重继承. 也叫做 MixIn 继承.

    MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

定制类 Link to heading

  • 定制类

    • __str__() and __repr__()

    如果想要能够被打印出来, 就必须实现这个方法

    如定义Student

    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...     def __str__(self):
    ...         return 'Student object (name: %s)' % self.name
    

    __str__() and __repr__()在大部分时间一样, 只是 str 打印字符串, 而 repr 打印程序调试字符串

    • __iter__()

    想要被用于for ... in循环,类似listtuple那样,就必须实现一个__iter__()方法

    • __getitem__(), __setitem__() and __delitem__()

    • __call__()

    任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用.

    class Student(object):
    def __init__(self, name):
        self.name = name
    
    def __call__(self):
        print('My name is %s.' % self.name)
    

    调用方法如下:

    >>> s = Student('Michael')
    >>> s() # self参数不要传入
    My name is Michael.
    
  • 元类 metaclass

    控制类的创建行为, 使用 metaclass; 运动时动态创建, 用 type();

  • logging

    import logging
    
    ...
    
    logging.exception(e)
    
    ...
    
    logging.basicConfig(level = logging.INFO)
    

    logging 有 debug, info, warning 和 error 等级别,这样可以指定不同级别的信息.

pdb, 调试器 Link to heading

  • pdb

    python 的调试器 pdb, 让程序以单步方式运行, 可以随时查看运行状态

    启动方式python3 -m pdb err.py

    n 可以单步执行

    任何时候都可以输入命令p 变量名来查看变量:

    (Pdb) p s
    '0'
    (Pdb) p n
    0
    

    输入命令q结束调试,退出程序.

    pdb.set_trace()

    这个方法也是用pdb,但是不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点

    c可以继续运行

  • Test-Driven Development(TDD)

with 关键字 Link to heading

  • with 的用法

    有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。

    with controlled_execution() as thing:
             some code
    

    when ‘with’ statement is executed, Python evaluates the expression, calls the __enter__() on the resulting value and assigns whatever __enter__() returns to the variable given by as. Python will then execute the code body, and no matter what happens in that code, call the guard object’s __exit__()

    __exit__() will look at the exceptions.

    with statement as a means of ensuring finalization of an object in the event of an exception being thrown. This usually looks like the following:

    
    with open("x.txt") as f:
        data = f.read()
        do something with data
    

    上面的 code 是说, 打开 x.txt 文件, 并把这个返回的东西赋值给 f, 之后执行下面的 code.

    有的 code 会省略 as 后面的代码, 但是通常只是with...as...的变体

    如以下:

    def test_keyerror(self):
            d = Dict()
            with self.assertRaises(KeyError):
                value = d['empty']
    

    这段代码 d 可以写在 as 后面, 但是得确定 d 是 dict().

读取文件写法 Link to heading

  • 读取文件

    with open('/path/to/file', 'r') as f:
        for line in f.readlines():
        	print(line.strip()) # 把末尾的'\n'删掉
    

    模式: r, rb, w, wb

python 获取路径 Link to heading

  • python 文件,系统相关

    import os
    
    os.environ
    os.environ.get('PATH')
    

    操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中,这一点要注意一下。查看、创建和删除目录可以这么调用:

    # 查看当前目录的绝对路径:
    >>> os.path.abspath('.')
    '/Users/michael'
    # 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
    >>> os.path.join('/Users/michael', 'testdir')
    '/Users/michael/testdir'
    # 然后创建一个目录:
    >>> os.mkdir('/Users/michael/testdir')
    # 删掉一个目录:
    >>> os.rmdir('/Users/michael/testdir')
    

    但是复制文件的函数居然在os模块中不存在, shutil模块提供了copyfile()的函数

  • python print formatted

    "Art: {a:5d},  Price: {p:8.2f}".format(a=453, p=59.058)
    

pickling and unpickling Link to heading

  • pickling unpickling 序列化 反序列化

    我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

    >>> import pickle
    >>> d = dict(name='Bob', age=20, score=88)
    >>> pickle.dumps(d)
    b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
    
    #pickling
    >>> f = open('dump.txt', 'wb')
    >>> pickle.dump(d, f)
    >>> f.close()
    
    #unpickling
    >>> f = open('dump.txt', 'rb')
    >>> d = pickle.load(f)
    >>> f.close()
    >>> d
    {'age': 20, 'score': 88, 'name': 'Bob'}
    
    #json pickling
    print(json.dumps(s, default=lambda obj: obj.__dict__))
    
    #json unpickling
    def dict2student(d):
        return Student(d['name'], d['age'], d['score'])
    
    >>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
    >>> print(json.loads(json_str, object_hook=dict2student))
    <__main__.Student object at 0x10cd3c190>
    
  • fork() 返回两个返回值的详细解释

    一段代码, if else 两个分支各代表父进程和子进程

    首先要明白的就是fork函数的作用,它是用来创建一个子进程,和父进程一样的子进程,就是父进程的一个副本。

    子进程将会有自己的地址空间,并且会获得父进程的数据段的副本以及堆栈的副本,所获得的副本都是精确拷贝。所谓副本就是一模一样的,包括变量,堆栈的结构。

    另外有一点就是子进程和父进程共享代码段。

    fork函数如何返回两次值那?这就要说到子进程的创建了。

    其实现过程大概是这样:

    首先在父进程中调用fork函数,在fork函数中开始的代码中首先创建一个子进程空间,获得一个进程ID,然后逐步将数据段以及堆栈都拷贝过去,因为子进程的数据段以及堆栈都和父进程一样,而且创建完成后就会和父进程共享代码段,共同执行代码,所以fork创建完子进程后面的代码在子进程中也会执行,并且堆栈中也有fork函数等待返回,这样就可以在fork下面的代码中进行实现返回值了,在父进程中执行的时候就可以判断当前进程的PID,如果等于刚刚新建进程的PID则返回0,但是因为当前进程为父进程所以它的PID不会等于刚刚新建进程的PID,再往下执行如果不等于则返回刚刚新建进程的PID,这样父进程得到的fork返回值就是新建子进程的PID。而在子进程中同样会执行fork剩下的代码,也会判断当前进程的PID是否等于刚刚新建进程的PID,结果是等于的,因为是在子进程中,其本身就是刚刚新建的进程,所以既然等于则会返回0,这样子进程中得到的返回值就是0,然后下面的代码段在父进程和子进程中执行时就可以根据返回值的不同而执行不同的代码了。 其实在linux中fork返回值的处理交给了一个do_fork的函数,这个函数根据传入的值,然后会分别在子进程和父进程中执行,从而返回两次不同的值,具体过程和上面所说的实现思想差不多。

  • doctest 文档测试

    当在

    '''
    '''
    

    中写入代码, 可以自动执行写在注释中的这些代码. doctest模块可以直接提取注释中的代码并执行测试

    eg

    
    ...
    
    if __name__=='__main__':
        import doctest
        doctest.testmod()