Python-语言

离散性地记录学习 py 过程中的要点

博文封面

Python 是什么,和其他编程语言对比有哪些优势和劣势?

  • Python 是解释型语言。代码运行前不需要编译,不像 C , Java。
  • Python 语言是动态类型的,不需要提前声明变量的类型;但是并不代表 Python 没有数据类型的概念,数据类型贯穿整个 Python,函数对象,类对象,类的实例,字符串,整形类型等等。 函数或方法的签名形式灵活,位置参数,默认参数,关键字参数,命名关键字参数。
  • Python 世界中函数是一等公民,意思是函数可以被当作值赋值给变量,也可以作为参数传递给函数,函数也可以返回一个函数。
  • Python 语言语法优雅简介,编写 Python 代码很快。有各种现有的轮子,生态库和框架用于快速开发,但是 Python 运行速度较那些编译型语言慢.
  • Python 语言可应用于多领域,Web 开发,数据抓取和爬虫,数据分析,大数据,自动化测试,人工智能等等。

Python 中的单下划线双下划线

良好的约定、协议和规范能促进事物良性高效地发展

  • _foo 对于私有概念的一种约定,变量,函数,方法,类,模块,包等等 python 编码元素都可以通过单一的下划线前缀表名私有性,不想被用户直接访问
  • __foo__ ,这种常被称为 dunder (double under) 双下划线,表明这是一个用于特殊用途的元素(变量,方法等)。
  • __foo 这种双下划线前缀会被 python 转为 _classname__foo,表明元素更加私有(more private),同时也更能避免撞衫其他同名元素。
  • foo_ 一种表示区别于 python 标准库或者三方的库的同名元素的约定,某些时候给变量去名字的时候刚好和已存在的库重名了, 但是还是想坚持用这个名字, 比如内置的 dict 类,比如你可以定义一个名为 dict_ 的变量来和 dict 自带类区分,这样 IDE lint 也不会发出警告。

list, queue, deque 三者的区别和应用场景

  • list 底层实现其实是可变长数组, 本质还是数组,只不过在容量达到初始容量后会自动扩容,具体扩容量网上说是原来容量的 2 倍,没去真实考察过。 和 java 中的 ArrayList 一样的性质,所以这种数据结构对于读取和更新元素来说很快,通过下标立马完事,但是如果频繁插入(除了从尾部插入),删除 操作,效率比较慢,因为数值中元素需要为这些元素的修改一个个移位,这种情况使用链表更为合适。
  • queue 是线程安全单向链表结构,常用于多线程/多进程间消息通信模型。get,put 方法用于从队列中读写元素,也可控制是否阻塞形式操作队列。队列也可 指定最大的容量,如果元素达到最大容量,put 操作可以阻塞形式地等待队列腾出空间,也可同时指定超时时间,达到超时时间队列如果还没有空间会抛出 Full 异常,如果非阻塞形式 put ,队列没有满则操作成功,否则直接抛出 Full 异常,get 操作相似。qsize, full,empty 这些用于获取当前队列 大小和状态信息的方法是线程不安全的,比如 empty 返回 False,在下一步 get 操作之前另一个线程 get 了这个元素,此时队列是空的, 该次 get 操作会被挂起。
  • deque 是线程安全的双向链表结构,用于控制有限历史元素,也可用于多任务调度场景。

解压序列赋值多个变量

这个操作并不是元组独有,可迭代的对象都可以,序列类型(列表,元组, 字符串,range),迭代器,生成器。赋值时只要保证变量个数和元素个数一致就行。
丢弃或者忽略某些值,可以用任意变量名去占位,比如常用的 _ , 也可以用 *_ 占位任意个数的值。

1
2
3
4
5
record = ['Andy', '20', 'male']
name, age,  _ = record
a, b, c = range(3)
record = ('Andy', '20', 13198789087, 18566754567)
name, age, *phone_numbers = record

__new__ 和 __init__ 区别

  • __new__ 创建类实例,__init__ 初始化类。
  • __new__ 方法返回类实例或者也可以返回其他类的实例,__init__ 方法什么都不会返回,也就是返回 None。
  • __new__ 是静态方法,第一个参数为类类型 cls,也就是类的 type 实例 ,__init__ 是实例方法,第一个参数为 self。
  • __new__ 只有在返回该类(cls)的实例的情况下(不管是不是相同实例)后面才会去调用该类的实例的 __init__ 方法。
  • __new__ 一般用来自定义不可修改类型的子类的实例的创建。

实现单例模式的几种方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from typing import Any


class Singleton:
    _ins = None

    def __new__(cls) -> Any:
        if cls._ins is None:
            print('new {!r} {}'.format(cls, id(cls)))
            cls._ins = super().__new__(cls)
        return cls._ins


class A(Singleton):
    """ 通过常规继承来实现单例模式 """
    def __init__(self) -> None:
        print('init A {}'.format(id(self)))


def singleton(cls):
    _ins = {}

    def wrapper(*args, **kwargs):
        if id(cls) not in _ins:
            _ins[id(cls)] = cls(*args, **kwargs)
        return _ins[id(cls)]

    return wrapper


@singleton
class B:
    """ 通过装饰器来实现单例模式 """
    def __new__(cls) -> Any:
        print('new B')
        return super().__new__(cls)

    def __init__(self) -> None:
        print('init B {}'.format(id(self)))


class M(type):
    _ins = None

    def __call__(cls):
        """ type 的实例被 call, 相当于创建类的实例
            
            M()() 相当于 C()
            
        """
        if cls._ins is None:
            cls._ins = super().__call__()
        return cls._ins


class C(object, metaclass=M):
    """ 通过元类来实现单例模式 """
    def __new__(cls) -> Any:
        print('NEW C')
        return super().__new__(cls)

    def __init__(self):
        print('INIT C')

class D:
    pass
# 模块级变量,天然的单例模式,因为模块只会被导入一次(除了循环导入问题的情况)
d = D()

if __name__ == '__main__':
    a1 = A()
    a2 = A()

    print('a1 {0} a2 {1}'.format(id(a1), id(a2))) # a1 1926784248520 a2 1926784248520

    print('-' * 30)

    b1 = B()
    b2 = B()
    print('b1 {0} b2 {1}'.format(id(b1), id(b2))) # b1 1926784248464 b2 1926784248464

    print('-' * 30)

    c1 = C()
    c2 = C()
    print('c1 {0} c2 {1}'.format(id(c1), id(c2))) # c1 1926784297448 c2 1926784297448
    
# new <class '__main__.A'> 1926784746024
# init A 1926784248520
# init A 1926784248520
# a1 1926784248520 a2 1926784248520
# ------------------------------
# new B
# init B 1926784248464
# b1 1926784248464 b2 1926784248464
# ------------------------------
# NEW C
# INIT C
# c1 1926784297448 c2 1926784297448

继承方式实现单例模式时,即使返回相同实例,也会调用 __init__ 方法,而装饰器和元类方式不会再次调用__init__ 方法,
因为__init__ 方法的调用前必须调用 __new__ 方法,装饰器和元类直接返回了相同实例,也就是没有调用 __new__ 方法,
而常规的继承 Singleton 父类,创建第二个实例的时候明显是先要走 __new__ 方法的。装饰器版本和元类版本的实现更加纯粹。

闭包的概念

闭包是函数式编程的特点之一,能够实现代码的组织和逻辑重用。
单一个嵌套函数引用外部函数的变量时,并且返回这个嵌套函数,那么这个函数就是闭包函数。
闭包函数有以下特点:

  • 函数有个嵌套函数
  • 嵌套函数引用了外部函数的变量
  • 被嵌套函数返回嵌套函数

每次调用了闭包函数返回的嵌套函数,嵌套函数如果引用了被嵌套函数的本地可变类型的变量时,总是会引用同一个变量所指的对象。
比如之前举的装饰器版本的单例模式实现的例子可以得出,每次实例化同一个类时,归功于闭包函数声明的本地用于保存类的实例的字典。
实例字典的 key 是需要实现单例的类的 id(内存地址),value 就是类实例,这样每次创建类之前python会先调用装饰器返回的嵌套函数,
嵌套函数内部根据参数 cls(被单例的类)的 id 从本地的 _cls 字典查询是否存在该类的实例,如存在就直接返回先前创建的该类的实例。
个人认为这就是闭包函数的诠释之一, 这个字的理解,有点像轻量级的类的概念,妙哉!

实例方法 和 @classmethod 和 @staticmethod 区别

  • 实例方法第一个参数必须是 self,表示这是一个绑定到类的实例的方法。而不是普通函数。
  • @classmethod 方法第一个参数为类 cls,表示这是一个和类绑定的方法,常用于根据额外传递的参数创建类的实例,这是为了提供一种可选的构造器来实例化类。
  • @staticmethod 方法不需要强制绑定任何参数,其实就是一个函数,和普通函数(模块命名空间函数)的区别就是它是定义在类里的静态方法, 为了将一些和类有关的逻辑(助手功能)绑定在一起,而不是全部都在模块命名空间中定义这些函数。有利于组织代码的结构。
1
2
3
4
5
6
7
8
9
class A(object):
    def foo(self, x):
        print ("executing foo(%s, %s)" % (self, x))
    @classmethod
    def class_foo(cls, x):
        print ("executing class_foo(%s, %s)" % (cls, x))
    @staticmethod
    def static_foo(x):
        print ("executing static_foo(%s)" % x)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
print(A().foo)
<bound method A.foo of <A object at 0x000001B70EE93AC8>>
print(A.class_foo)
<bound method A.class_foo of <class 'A'>>
print(A.static_foo)
<function A.static_foo at 0x000001B70EEA7378>
a = A()
a.foo(1)
executing foo(<A object at 0x000001B70EB8FFD0>, 1)
A.class_foo(2)
executing class_foo(<class 'A'>, 2)
a.class_foo(2)
executing class_foo(<class 'A'>, 2)
A.static_foo(3)
executing static_foo(3)
a.static_foo(3)
executing static_foo(3)
A.foo(4)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: foo() missing 1 required positional argument: 'x'
A.foo(4, 4)
executing foo(4, 4)
A.foo(A(), 4)
executing foo(<A object at 0x000001B70EE93E48>, 4)

可以看出,A.foo 是一个绑定于 A 实例的实例方法,A.class_foo 是一个绑定于类 A 的类方法
而 A.static_foo 只是一个定义在类 A 的函数,仅此而已,有点像孤儿,但是这个静态方法又和类 A 存在功能逻辑上的关联。
这里 A 的实例可以调用类和静态方法,学过 java 等静态语言对于这个特性感到很不可思议,实例竟然可以调用类方法和静态方法。
这就是动态语言的灵活的地方。调用 a.class_foo(x), 其实是 python 帮我们传递了 a.__class__ 而已,调用实例方法
a.foo(x) 其实是 A.foo(a, x)。java 里只有普通实例方法和静态方法,类方法说的就是静态方法。

实例类型\方法类型 实例方法 类方法 静态方法
a(A 的类实例) a.foo(x) a.class_foo(x) a.static_foo(x)
A(A 的 type 实例) A.foo(a,x) A.class_foo(x) A.static_foo(x)

参考:
https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod

类变量和实例变量

  • 类变量被类的所有实例共享
  • 实例变量属于某个特定的实例,实例可以访问类属性,但是如果同时存在同名的类和实例属性,那么通过实例访问这个同名属性时访问的是这个实例的属性, 通过类访问这个同名属性访问的是这个类的属性。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class User:
    name = 'andy'
    phone_numbers = ['245', '678', '980']
    
    def __init__(self, name = None):
        if name is not None:
            self.name = name
    
usr = User()
print(User.name) # andy
print(usr.name) # andy
print(User.phone_numbers) # ['245', '678', '980']
print(usr.phone_numbers) # ['245', '678', '980']
usr.phone_numbers.append('666')
print(User.phone_numbers) # ['245', '678', '980', '666']
print(usr.phone_numbers) # ['245', '678', '980', '666']
jack = User(name = 'jack')
print(User.name) # andy
print(jack.name) # jack
jack.phone_numbers = ['888']
print(User.phone_numbers) # ['245', '678', '980', '666']
print(jack.phone_numbers) # ['888']

python 中的 lambada 表达式

lambada 表达式是个匿名函数,一种轻量级简洁的函数定义方式。
python 中的 lambada 函数只能存在一个表达式,防止被滥用。

函数式编程

map, reduce,filter,sorted 这些内置的高阶函数都是函数式编程的典范。
高阶函数就是那种能够接受函数作为参数的函数。

python 浅拷贝和深拷贝

内置的 copy 模块中有用于这两种拷贝的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import copy
foo = [1, 2, 3, 4, ['a', 'b', 'c']]

foo_ref = foo
foo_copy = copy.copy(foo)
foo_deep_copy = copy.deepcopy(foo)

foo.append(5)
foo[4].append('d')

print(foo) # [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print(foo_ref) # [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print(foo_copy) # [1, 2, 3, 4, ['a', 'b', 'c', 'd']]
print(foo_deep_copy) # [1, 2, 3, 4, ['a', 'b', 'c']]

对于浅拷贝,在拷贝可变表象元素的时候其实是拷贝的引用,而深拷贝拷贝的是可变元素的整个内容,不是同一个实例

函数定义时的默认参数

函数的默认参数的初始化是在函数定义的时候确定的,不是在调用时确定的。函数默认值不要使用可变对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def extendlist(item, container=[]):
    container.append(item)
    return container

list1 = extendlist(10)
list2 = extendlist('a', [])
list3 = extendlist(300)

print('list1 = %s' % list1)
print('list2 = %s' % list2)
print('list3 = %s' % list3)

输出:

list1 = [10, 300]
list2 = ['a']
list3 = [10, 300]

解决方案:
将默认参数初始为 None,在函数里去判断处理

1
2
3
4
5
def extendlist(item, container=None):
    if container is None:
        container = []
    container.append(item)
    return container

pass 语句问题

当语法上需要一个语句,但程序需要什么动作也不做时可以使用 pass 语句用于占位作用。

但是 pass 语句不是 return , breakcontinue, 后几者用于中断程序的执行流程

1
2
3
def test_pass():
    pass
    print('after pass excuted')

输出:
after pass excuted

How are arguments passed – by reference or by value?

Python 中参数是 通过赋值 传递的,通过两个因素解释:
- 传递的参数确实是对象的引用(但是引用是通过值传递的)
- 参数是否是可变类型

When you pass by reference you're actually passing by value the reference... Define by reference

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
a=1
def func(a):
    print ('in_func_got', a , id(a))  # 1, 140716526392352
    a=2
    print ('in_func_set_to', a, id(a)) # 2, 140716526392384

print('out_func_before', a, id(a)) #  1,  140716526392352
func(1)
print('out_func_after', a, id(a))# 1, 140716526392352

list = []
def func2(list):
    print ('in_func2_got', list) # []
    list.append(1)
    print ('in_func2_set_to', list) #  [1]

print ('out_func2_before', list) # []
func2(list)
print ('out_func2_after', list) # [1]

list2 = []
def func3(list):
    print ('in_func3_got', list) # []
    list=[1]
    print ('in_func3_set_to', list) #  [1]

print ('out_func3_before', list2) # []
func3(list2)
print ('out_func3_after', list2) # []
  • 可变对象,此时参数和外部变量都指向同一个对象,对这个对象的修改会影响到外部作用域;但是如果在函数内重新对参数赋值新的对象,那么对外部作用域来说毫无影响,外部变量还是指向原来的对象的引用,并且对象也没被修改。
  • 不可变对象,无论对参数赋不值赋新值,都不会改变外部变量的引用,更不会修改对象内容。

Do you know what list and dict comprehensions are? Can you give an example?

列表和字典推导是一种简洁快速地一次性生成预期内容的列表或字典。

1
2
3
l = [i**2 for i in range(5)] # [0, 1, 4, 9, 16]
items = [('a', 1), ('b', 2), ('c', 3)]
d = {k:v for k, v in items if v>=2} # {'b': 2, 'c': 2}

列表/字典 推导由于是一次性生成所有的数据,如果数据量大,会消耗较大内存,如果只用其中少数数据,反而造成了没必要的内存资源的浪费;可以通过 generator 生成器按需生成元素。

What is PEP 8? for example?

首先回答 PEP 是什么?
PEP 的英文全称是 Python Enhancement Proposal ,Python 增强提议,PEP 是一份文档,描述了为 Python 提出的新特性提议,编码分格和设计指导。
PEP 8 是 Python 编码规范指南,但不是强制标准,目的是为了写出可读性更强和一致性的 pythenic 分格代码。能让你和你周围的 Pythener 一起更加高效地合作编(搞)码(基)。

为什么代码可读性这么重要?

Code is read much more often than it is written.
-Guido van Rossum

阅读代码的频率远远比写代码要高。

如何编写符合 PEP-8 的代码?

命名约定

命名风格

包,模块,变量,常量,参数,函数,类,方法
如何命名

代码布局

空行,最大行字符,换行

缩进

换行缩进

注释

块注释,内联注释

文档字符串
表达式和语句中的空格

Do you use virtual environments?

虚拟环境是为了隔离项目间的开发环境,避免造成项目间的依赖和环境冲突。
比如 A 项目依赖 1.0 版本的 D,B 项目依赖 2.0 版本的 D,1.0 和 2.0 版本的 D 之间存在兼容性问题,如果不使用虚拟环境隔离,使用哪一个版本都会造成另一方项目依赖问题。或者比如老旧的 A 项目采用 Python 2 开发,新项目 B 是在 Python 3 下开发的情况下,虚拟环境依然能够解决多项目开发环境问题。
虚拟环境会生成项目特定版本的 Python 安装和三方包依赖目录。

使用 Python 内置的 venv 模块创建虚拟环境:

python -m venv my-venv

my-venv 是虚拟环境根目录名称,可以修改。

如果有多个python版本, 比如 python3 下创建虚拟环境:

1
python3 -m venv my-venv

激活虚拟环境:

1
2
3
4
*nix:
$ . my-venv/bin/activate
Windows :
> my-venv\Scripts\activate

退出虚拟环境:

1
2
3
4
*nix:
$ . my-venv/bin/deactivate
Windows :
> my-venv\Scripts\deactivate

Can you sum all of the elements in the list, how about to multiply them and get the result?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# sum
sum(range(11)) # 55

# 迭代
value = 0
for i in range(11):
    value += i
print(value) # 55

# reduce
reduce(lambda x, y: x + y, range(11) ) # 55
# 相当于:
def reduce(func, iterable, init=None):
    it = iter(iterable)
    if init is None:
        value = next(it)
    else:
        value = init
    for i in it:
        value = func(value, i)
    return value

# 获取列表元素相乘结果,同样可以通过迭代或者 reduce 方法

Do you know what is the difference between lists and tuples? Can you give me an example for their usage?

列表和元组的区别:

  • - 列表和元组都是序列类型,按照存放的顺序存储一系列元素。
  • 列表可以修改,元组不行
  • 两者都可以存放不同类型的元素;但是列表一般存放相同类型元素,元组存放不同类型元素;

元组不仅仅是不可变列表

元组通常表示一种异构数据结构,每个元素都可以有特定的含义。
比如一条数据库查询记录可以用 tuple 表示 ('jack',20,'male') 表示一个名为 jack 的20岁的男性。多条记录用元组列表表示。[('jack',20,'male'), ('mary', 19,' female')]

具名元组

既然元组可以表示有意义的数据结构,具名元组是一种轻量级用类的概念去使用元组的方式

1
2
3
4
5
6
7
8
# 表示坐标的具名元组
Point = namedtuple('Point', ['x', 'y']) # 工厂方法创建具名 tuple
p = Point(11, y=22)
p[0] + p[1] # 33
x, y = p # 元组解包
x, y # (11, 22) 创建新元组
p.x + p.y # 33,也可以通过名称访问字段
p #  __repr__ 输出 Point(x=11, y=22)

Do you know the difference between range and xrange?

range 和 xrange 都是用于生成连续的整数
python 2.x :
range() 是一次性生成所有的元素并返回一个list,而 xrange() 生成一个按需获取元素的 xrange 对象。
python 3.x :
range() 返回一个 range 序列对象,xrange() 被移除。range 的实现就是 2.x 中的 xrange

Tell me a few differences between Python 2.x and 3.x

  • print 在 2.x 中表现为语句,3.x 中为函数调用
  • 字符串的格式化,2.x 使用 % 号,3.x 可以使用 format 字符串方法
  • 文本字符串编码,2.x 默认 ASCII,u 前缀用来声明字符串为 Unicode 编码,3.x Unicode
  • 整数相除,2.x 中两数都是整数情况下相除会舍掉余数返回最接近的整数,3.x 者不是,例如 5/2 , 2.x 返回 2,3.x 返回 2.5。要想 2.x 返回 2.5,使用 5/2.0, 要想 3.x 中返回 2 ,使用 5//2
  • 全局命名空间泄漏问题 python i = 10 [i for i in range(5)] print(i) # 2.x => 4 , 3.x => 10

What are decorators and what is their usage?

装饰器用于在不修改原有代码实现情况下增强函数的功能。这是AOP(面向切片编程)思想
常用于权限校验,性能测试。日志记录方面等。
python 语言自带这种特性,不像 java 等静态语言实现 AOP 比较复杂繁琐。
参考:
python 装饰器

The with statement and its usage.

上下文管理器,with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、多线程环境中线程锁的自动获取和释放等。
参考:
浅谈 Python 的 with 语句

说说你对 zen of python 的理解,你有什么办法看到它

Python 之禅,Python 编码 19 条指导原则.
大佬说的话都对。

  • Beautiful is better than ugly. 优美胜于丑陋(Python 以编写优美的代码为目标)
  • 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. 不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)
  • 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. 虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )
  • 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! 命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)

参考:
维基百科
蛇宗三字经(The Zen of Python)

github上都fork过哪些python库,列举一下你经常使用的,每个库用一句话描述下其功能

你调试python代码的方法有哪些

什么是 GIL

全局解释器锁,为了防止在python解释器下同时执行多线程,一次只能存在一个线程能够执行,每个线程运行前必须先获得解释器锁,意味着多线程 python 应用其实是单线程运行的(针对 CPU 密集计算,非 IO 密集计算),GIL 锁阻止多个线程同时访问 python 对象, 即使多线程被分配到不同的 CPU 也会被 GIL。
对于 IO 密集的场景,比如网络/文件 IO ,GIL 没什么影响,在处理 IO 操作前会自动释放 GIL,因此其他线程能够快速拿到 GIL 去处理其他的 IO 操作;
但是对于 CPU 密集型应用,就会受 GIL 有影响,而且由于多线程反而额外增加了线程切换的开销,比单线程都慢,可以采用多进程+协程的方式提高运行效率。多进程下,每个进程都有独立的解释器,每个进程运行一个协程,不过要考虑进程间的通信(IPC),multiprocessing 内部采用 pickle 方式处理 IPC
参考:
Has the Python GIL been slain?
What is the Python Global Interpreter Lock (GIL)?

什么是元类(meta_class)

元类用于创建类,在类被创建的时候可以自定义类的创建行为, 元类继承自 type , 重写 type.__new__ 实现自定义类的创建行为
元类的应用案例有很多,比如 ORM 框架中模型类的创建会使用模型元类来解析模型类并自动添加一些类属性。

对比一下dict中 items 与 iteritems

优化问题,python 2.x 和 3.x
2.x 下 items 直接返回了(k,v) tuple list, 这样一次性生成返回方式在数据量大情况下影响性能,所以出现 iteritems 采用 generator 方式按需返回字典元素,同时为了保持向后兼容。
3.x 下使用 items 替代 2.x 下的 iteritems,iteritems 被移除,返回的是一个可迭代的视图(view)对象。
参考:
Dictionary view objects

是否遇到过python的模块间循环引用的问题,如何避免它

循环引用/导入问题在 python 中尤为常见,这是代码设计问题。
举个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 目录结构
# demo
#   circularimport
#       __init__.py
#       a.py
#       b.py
#       c.py

# 直接循环导入
# a.py
import sys

print("a in")
print("b imported: %s" % ('circularimport.b' in sys.modules,))
from circularimport import b # 导入 b 模块

print("a out")
print('b.b = {}'.format(b.b)) #  AttributeError: module 'circularimport.b' has no attribute 'b'
# b.py
import sys

print("b in")
print("a imported: %s" % ('circularimport.a' in sys.modules,))
from circularimport import a # 导入 a 模块

print("b out")
b = 3

# 输出
a in
b imported: False
b in
a imported: False
a in
b imported: True
a out
AttributeError: module 'circularimport.b' has no attribute 'b' # b 模块没有完全初始化成功

# 间接循环导入
# a.py
import sys

print("a in")
print("b imported: %s" % ('circularimport.b' in sys.modules,))
print("c imported: %s" % ('circularimport.c' in sys.modules,))
from circularimport import b

print("a out")
print('b.b = {}'.format(b.b)) #  AttributeError: module 'circularimport.b'

# b.py
import sys

print("b in")
print("c imported: %s" % ('circularimport.c' in sys.modules,))
from circularimport import c

print("b out")
print('c.c = {}'.format(c.c))
b = 3

# c.py
import sys

print("c in")
print("a imported: %s" % ('circularimport.a' in sys.modules,))
from circularimport import a

print("c out")
c = 6

# 输出:
a in
b imported: False
c imported: False
b in
c imported: False
c in
a imported: False
a in
b imported: True
c imported: True
a out
AttributeError: module 'circularimport.b' has no attribute 'b' # b 模块没有完全初始化成功

直接循环导入例子中,在 a 模块导入 b 模块的同时,b 模块又去导入 a 模块,产生死循环导入问题,python 针对这样的情况的行为是中断了 b 的二次导入,第二次 导入 a 模块时,其实 b 模块已经处于导入状态,只不过没有被完全初始化,在 b 模块中 from circularimport import a 之后的语句是没有被执行的,因此 二次 a 模块导入会成功导入 b 模块,然后访问 b 模块中未被初始化的 b 属性就会抛出 AttributeError: module 'circularimport.b' has no attribute 'b' 异常。
间接导入也是一样道理。

不管是直接还是间接循环导入,都会出现模块没有被完全导入的情况,所有访问未被完全导入的模块的成员(属性,函数,类...) 会抛出找不到相关成员异常。

解决方案:
惰性导入,a 模块在需要 b 模块的时候导入,比如可以放在一个函数中

1
2
3
4
# a.py
def access_b():
    from circularimport import b
    print(b.b)

现实世界遇到的相关问题:
在使用 SQLAlchemy 的 alembic 进行数据库迁移时,需要首先导入所有定义的 Model 才能生成迁移。model 包的初始化模块 __init__.py 定义了全局 db 实例,model 包下定义了各个模型类模块,这些模型模块会导入 model 包下的 db 属性,如果 model 包模块同时去导入 所有的模型模块的话就会出现循环导入问题。

其实 SQLAlchemy ORM 声明 Model 的关系模型的时候也是鼓励去使用model名称,而不是导入模型类的方式,这样也就规避了循环导入的问题。

inspect 模块有什么用

inspect 模块主要提供了四种用处:

  • 类型检查
  • 获取源码
  • 检查类和函数
  • 解析堆栈

现实例子:
Django auth app 中的 authenticate 函数中使用 inspect.getcallargs 检测身份验证后端是否接收传递给 authenticate 的凭据参数来找到符合的身份验证后端去验证登录凭据。

python 自省

自省就是运行时动态获取 模块,类等元素的运行时信息。
type(), isinstance(), issubclass(), getattr(), setattr(), inspect 模块 等等

已知列表和数值,找出列表中所有的两数相加等于数值的索引对

1
2
3
4
5
6
7
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        result = []
        for i, n in enumerate(nums):
            if i not in result and (target - n) in nums:
                result.extend((i, nums.index(target-n)))
        return [(result[i], result[i+1]) for i in range(0, len(result), 2)]