面试题---python基础

面试遇到的问题:

滴滴:

1. Python的数据结构

2. list和tuple的区别

3. list中有哪些操作?append和extend的区别?

4. list和dict的却别?dict是有序的吗?

5. 如果a = dict, b =a 其中dict发生了改变,b会改变吗?这是一个浅拷贝还是深拷贝?这是引用吗?

如果把dict换成list呢?

6. 你用用过多线程吗?多线程的作用是什么?

7. 如何实现Singleton?这个Singleton中有什么变量?

8. 静态方法、类方法是什么?类的成员方法能访问实例变量吗?能访问类的变量吗?能访问静态类方法吗?

9. 你用过哪些python的包

美团:

1. python语言的特点

2. Python装饰器(两面都问到)

 
今日头条:

1. Python的数据结构

2. list和set区别

 

**1. Python数据结构

——美团(滴滴),介绍一下Python的数据结构,并说明它们有什么操作

四个基本数据结构:list、tuple、dict、set

 在python中,字符串 tuples, 和数字是不可更改的对象,而list,dict等则是可以修改的对象。

补充:8种数据类型:number数字(整型int、浮点型float、complex复数)、string字符串、list列表、tuple元组、dict字典、set集合、null空值、boolean布尔值。

字符串知识总结:

字符串或串(String)是由数字、字母、下划线组成的一串字符。

https://blog.csdn.net/syd_de_blog/article/details/88775245

https://www.cnblogs.com/lvtaohome/p/11215954.html

1. list

1). list的操作有哪些?——滴滴

可以用可以使用dir()和help()查询

dir()用来查询一个类或者对象所有属性,如dir(list)

help()函数:1.帮助查看类型详细信息,包含类的创建方式、属性、方法,例如:

>>> help(list)

2.帮助查看方法的详细使用信息(使用时要注意输入完整路径,使用模块帮助时,需要先导入模块),;例如:

>>> from selenium.webdriver.common.by import By
>>> help(By)

 

2)比较list.append()和extend(),他们是生成一个新的列表还是原来的列表?

  • append()接收任意数据类型的参数,并且简单的加在list的末尾,当作一个元素。——同一个list
  • extend():——同一个list
    • 可以接收list,将这个list中的每个元素都增加到原来的list中。所以如果增加的list有n个元素,原list有m个元素,则使用extend()扩充以后,list有m+n个元素
    • 也可以接收dic:将每个key值添加到原list中
    • 也可以接收tuple

例如:

复制代码

>>> l1 = [1,2, 3, 4, 5]
>>> l2 = [7, 8, 9]
>>> l3 = ('a', 'b', 'c')
>>> t3 = ('a', 'b', 'c')
>>> d4 = {'a':1, 'b':2, 'c':3}

>>> id(l1)
4330496200
>>> l1.append(l2) 
>>> id(l1) #append()是同一个list
4330496200
>>> l1
[1, 2, 3, 4, 5, [7, 8, 9]] #append()可以接收list,l2当作一个元素加入到l1中
>>> len(l1)
6
>>> l1.append(t3) #append()可以接收tuple,也当作一个元素
>>> l1
[1, 2, 3, 4, 5, [7, 8, 9], ('a', 'b', 'c')]
>>> len(l1)
7
>>> l1.append(d4) #append()可以接收dict,也当作一个元素
>>> l1
[1, 2, 3, 4, 5, [7, 8, 9], ('a', 'b', 'c'), {'a': 1, 'c': 3, 'b': 2}]
>>> len(l1[7])
3

# extend
>>> l1 = [1,2, 3, 4, 5]
>>> id(l1)
4330496200
>>> l1.extend(l2) #[1, 2, 3, 4, 5, 7, 8, 9]
>>> id(l1) # 使用extend()是同一个list
4330496200
>>> l1.extend(t3) #extend()可以接收tuple,将其中所有的元素扩充到list中
>>> l1
[1, 2, 3, 4, 5, 7, 8, 9, 'a', 'b', 'c']
>>> l1.extend(d4) ##extend()可以接收dict,将其中所有的key值扩充到list中
>>> l1
[1, 2, 3, 4, 5, 7, 8, 9, 'a', 'b', 'c', 'a', 'c', 'b']

#insert
》》》list1 = [1,2,3]
     list2=[4,5,6]
》》》list1.insert(1,list2)
》》》print(list1)
[1, [4, 5, 6], 4, 5, 6]

#index  获取列表中的值对应的索引号
》》》list1 = [1,2,3]
》》》print (list1.index(2))
1

#翻转

>>>list1 = [1,2,6,3,4,5,5]
>>>list1.reverse()
>>>print(list1)

[6,5,5,4,3,2,1]

#列表排序

>>>mylist = [1, 2, 5, 4]
>>>mylist.sort() 
>>>print(mylist)
[1,2,4,5]


mylist = ['Google', 'Tencent', 'Microsoft', 'Baidu', 'Alibaba']

# 删除尾部元素
mylist.pop()      # 会返回被删除元素
# 删除指定位置的元素
mylist.pop(1)  # 删除索引为1的元素,并返回删除的元素

mylist.remove('Microsoft') #删除列表中的Microsoft

del mylist[1:3]       #删除列表中索引位置1到位置 3 的数据

#切片
mylist = ['Google', 'Tencent', 'Microsoft', 'Baidu', 'Alibaba','Sina']
#获取索引位置1的数据
mylist[1]        #'Tencent'
#获取索引位置1到5的数据,注意这里只会取到索引位置4,这里叫做取头不取尾
mylist[1:5]   # 'Tencent', 'Microsoft', 'Baidu', 'Alibaba'
#获取从最头到索引位置5的数据
mylist[ :5]   #'Google', 'Tencent', 'Microsoft', 'Baidu', 'Alibaba'

#获取从索引位置2开到最后的数据
mylist[2:]    #'Microsoft', 'Baidu', 'Alibaba','Sina'
#间隔切片
list5=[1,2,3,4,5,6]
print (list5[::2])

#循环
a = [1,2,3,4,5,6]
#在a的数据基础上每个数据乘以10,再生成一个列表b,
b = [i*10 for i in a]
print(a)
print(b)

#运行结果如下:
#    [1, 2, 3, 4, 5, 6]
#    [10, 20, 30, 40, 50, 60]

#生成新的列表
#生成一个从1到20的列表

a = [x for x in range(1,20)]

#把a中所有偶数生成一个新的列表b

b = [m for m in a if m % 2 == 0]

print(b)


#运行结果如下:
#        [2, 4, 6, 8, 10, 12, 14, 16, 18]

#生成一个列表a
a = [i for i in range(1,4)]
print(a)

#生成一个列表b
b = [i for i in range(100,400) if i % 100 == 0]
print(b)

# 嵌套式 
c = [m+n for m in a for n in b]
print(c)


#运行结果:
#    [1, 2, 3]
#    [100, 200, 300]
#    [101, 201, 301, 102, 202, 302, 103, 203, 303]

#list将序列创建为列表
>>> list("Hello world")
['H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']

复制代码

 

3)合并list:——两个方法

# 方法一:+ 一个新的list
l3 = l1 + l2

# 方法二:extend() #还是原来的list
l1.extend(l2)

 

4)list使用切片操作,会生成新的list吗?——正向、负向、浅拷贝

会。因为切片操作相当于浅拷贝,生成了一个新的list对象。

复制代码

list = [1, 2, 3, 4, 5]
# list是有序的,所以可以根据索引来查找值
# 1. 正向
list[1:4, 2] #[1, 3]
# 2. 负向
list[-1:-4:-2] #[5, 3]

#切面相当于浅拷贝——新的list
>>> id(list)  #4330445000
>>> id(list[-1:-5])  #4330479176

复制代码

备注:深拷贝与浅拷贝的区别:

1)浅拷贝:浅拷贝只会拷贝内存中的第一层数据;;在浅拷贝中 当改变拷贝对象的值 被拷贝对象的值也会随之改变

2)深拷贝:当不想改变被拷贝的值时 应该使用深拷贝

https://www.cnblogs.com/lixiaoliuer/p/6094698.html

2. tuple

1)list和tuple的区别——滴滴

 listtuple
创建list = [1, 2, 3, 4, 5]t1 =(1,) # 如果只有一个元素,必须用“,”隔开,否则默认为‘int’类型
t2 = (1, 2, 3)
t3 = (1, 2, 3, ['a', 'b']) # tuple中可以嵌套list

元素是否可更改

(最大的不同)

可更改:
list.append()

list.extend()
list.pop()

list.remove()

不可更改,所以不具备增、删、改。

如果删除:del t #删除整个元素

对于t3,tuple中嵌套有list,则可以更改那个list的值:

t3[3].append('c') #t3 = (1, 2, 3, ['a', 'b', 'c'])

切片操作

相当于浅拷贝(新的list、tuple)

正向、负向

是否有序有序(按定义的次序进行排序)
优势 

1. tuple操作比list更快。
2. 访问的安全性。数据不可更改
2. 隐含的assert语句,说明这一数据是常量

转换

1. tuple(l)
2. list(t)

     相互转换的代码:

num_list = [1,2,3]
num_tuple = (4,2,3)
num1 = tuple(num_list)
num2 = list(num_tuple)

print(num1)
print(num2)

》》》(1, 2, 3)
          [4, 2, 3]

2)基本操作:

 

代码实现:

>>> tuple('Hello,world!')
('H', 'e', 'l', 'l', 'o', ',', 'w', 'o', 'r', 'l', 'd', '!')

3. dict:

0)dict的显著特征:


 

  • 字典中的数据必须以键值对的形式出现,即k,v: 

      key:必须是可哈希的值,比如intmstring,float,tuple,但是,list,set,dict不行 

      value:任何值

  • 键不可重复,值可重复

      键若重复字典中只会记该键对应的最后一个值

  • 字典中键(key)是不可变的,何为不可变对象,不能进行修改;而值(value)是可以修改的,可以是任何对象。

      在dict中是根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。

1)dict有哪些操作?

 另外:dict推导式:

d = {key: value for (key, value) in iterable}

补充:dict的删除方法也可以用pop,例如:dic={"name":"zs","age":18},删除字典中的"name"字段
dic.pop('name')
print(dic)
》》{'age': 18}

 

2)dict是有序的吗?

——不是,list、tuple是有序的

 

3)如果a = dictR, b=a, dictR修改了,a、b会变化吗?这属于引用吗?这是浅拷贝还是深拷贝?

这是赋值引用。如果更改a的值,b会随之变化。如果更改b的值,a会随之变化。

如果改成了list:同样。

如果改成了tuple,或者一个非容器类型(例如:数字、字符以及其他原子类型),没有拷贝一说,只能是引用,a、b发生变化都不会影响彼此的值。但是如果tuple中含有嵌套的list,改变这个list中的值,会互相影响。

(详情见:下面赋值、浅拷贝、深拷贝)

4)list和dict的区别

Dict是字典,以键值对的形式存储,查找插入速度快但内存占用大。 List是列表,查找插入速度较慢但内存占用小。 Dict的内部实现是哈希表,用哈希函数给字典的键分配位置。

5)区分可变/不可变对象、有序/无序

  • 可变对象和不可变对象是指内容是否可以被改变
  • 不可变对象包括:number,string,tuple,
  • 可变对象包括:list,set,dict
  •  
  • 有序和无序的判断:可不可以索引
  • 有序对象:str 、tuple、list
  • 无序对象:set,dict

6)代码

----字典生成创建方式 

复制代码

#创建空字典1
    
d = {}
print(d)

#创建空字典2
d = dict()

#直接赋值方式

d = {"one":1,"two":2,"three":3,"four":4}


#常规字典生成式

dd = {k:v for k,v in d.items()}
print(dd)

#加限制条件的字典生成方式

ddd = {k:v for k,v in d.items() if v % 2 ==0}
print(ddd)

复制代码

 

----字典的常见操作_____访问、删除、变更字典里面的内容

复制代码

#访问字典中的数据
d = {"one":1,"two":2,"three":3,"four":4}
print(d["one"])
#变更字典里面的数据
d["one"] = "eins"
print(d)

#删除一个数据,使用del
del d["one"]
print(d)

#运行结果如下:
1
{'one': 'eins', 'two': 2, 'three': 3, 'four': 4}
{'two': 2, 'three': 3, 'four': 4}

复制代码

----字典中成员检测----------通过key进行检索的

复制代码

d = {"one":1,"two":2,"three":3,"four":4}

if 2 in d:
    print("value")-------------执行不出来
    
if "two" in d:
    print("key")
    
if ("two",2) in d:
    print("kv")---------执行不出来

复制代码

----使用for循环访问字典

复制代码

d = {"one":1,"two":2,"three":3,"four":4}
#使用for循环,直接按key值访问

for k in d:
    print(k,d[k])
    
#上述代码也可以写成如下
    
for k in d.keys():
    print(k,d[k])

#只访问字典的值

for v in d.values():
    print(v)

#以下是特殊用法

for k,v in d.items():
    print(k,'--->',v)

复制代码

----字典相关函数

通用函数:len,max,min,dict

d = {"one":1,"two":2,"three":3,"four":4}
print(max(d))
print(min(d))
print(len(d))

dict() 函数的使用方法:

复制代码

dict0 = dict()  # 传一个空字典
print('dict0:', dict0)
 
dict1 = dict({'three': 3, 'four': 4})  # 传一个字典
print('dict1:', dict1)
 
dict2 = dict(five=5, six=6)  # 传关键字
print('dict2:', dict2)
 
dict3 = dict([('seven', 7), ('eight', 8)])  # 传一个包含一个或多个元祖的列表
print('dict3:', dict3)
 
dict5 = dict(zip(['eleven', 'twelve'], [11, 12]))  # 传一个zip()函数
print('dict5:', dict5)

复制代码

str(字典):返回字典的字符串格式

d = {"one":1,"two":2,"three":3,"four":4}

print(str(d))

clear:清空字典

items:返回字典的键值对组成的元组格式

复制代码

d = {"one":1,"two":2,"three":3,"four":4}

i = d.items()
print(type(i))
print(i)

d.clear()
print(d)

复制代码

keys:返回字典的键组成的一个结构

d = {"one":1,"two":2,"three":3,"four":4}
k = d.keys()
print(type(k))
print(k)

values:返回字典的值组成的一个结构

d = {"one":1,"two":2,"three":3,"four":4}
v = d.values()
print(type(v))
print(v)

get:根据制定键返回相应的值,好处是可以设置默认值

复制代码

d = {"one":1,"two":2,"three":3,"four":4}

print(d.get("one333"))

#get默认值是None,可以设置
print(d.get("one",100))
print(d.get("one222",100))

复制代码

fromkeys:使用指定的序列作为键,使用一个值作为字典的所有的键的值

p = ["one","two","three","four",]
#注意fromkeys两个参数的类型
#注意fromkeys的调用主体
d = dict.fromkeys(p,"222")
print(d)

 

4. set

1)集合的定义与特点

  • 集合更接近数学上集合的概念。集合中每个元素都是无序的、不重复的任意对象。
  • 可以通过集合去判断数据的从属关系,也可以通过集合把数据结构中重复的元素减掉。集合可做集合运算,可添加和删除元素。
  • 集合内数据无序,即无法使用索引和分片
  • 集合内部数据元素具有唯一性,可以用来排除重复数据
  • 集合内的数据:str,int,float,tuple,冰冻集合等,即内部只能放置可哈希数据

2)哈希数据科普

什么是可哈希(hashable)?

  • 简要的说可哈希的数据类型,即不可变的数据结构(字符串str、元组tuple、对象集objects)。

  哈希有啥作用?

  • 它是一个将大体量数据转化为很小数据的过程,甚至可以仅仅是一个数字,以便我们可以用在固定的时间复杂度下查询它,所以,哈希对高效的算法和数据结构很重要。

 

什么是不可哈希(unhashable)?

  • 同理,不可哈希的数据类型,即可变的数据结构 (字典dict,列表list,集合set)

 

3)代码实现

set集合的介绍

  集合是无序的,不可重复的数据集合,集合里面的每个元素必须是可哈希的(不可变的数据类型),但是集合是不可哈希的,所以集合做不了字典的键

  集合的创建方式:

    ①:通过set()创建集合,格式 :集合名字=set(对象),该对象可以是任意的数据类型,但是对象里面的每个元素必须是不可变的数据类型,是可以hash的

    ②:通过{}创建  

复制代码

set1=set([1,2,3,4,5,6,7])
print(set1)  #{1, 2, 3, 4, 5, 6, 7}

set2=set('helloworld')
print(set2) # {'r', 'h', 'w', 'd', 'l', 'o', 'e'}

set3={1,2,3,4,5,6,7,8}
print(set3) # {1, 2, 3, 4, 5, 6, 7, 8}

复制代码

----集合的定义

复制代码

#集合的定义,set()
s = set()
print(type(s))
print(s)

#也可以像下面这样做,大括号内一定要有值,否则定义出的将是一个dict
s = {1,2,3,4,5,6,7}
print(s)

复制代码

创建集合时需要用list作为输入集合,可通过add()方法增加元素,remove()方法删除元素

s = set([1,2,3])
s.add(6)
s.remove(2)

集合的内涵

普通集合内涵
--------以下集合会在初始化后自动过滤掉重复元素

s = {33,1,33,6,9,126,8,6,3,77,88,99,126}
print(s)

普通循环集合内涵

s = {33,1,33,6,9,126,8,6,3,77,88,99,126}
ss = {i for i in s}
print(ss)

带条件的集合内涵

s = {33,1,33,6,9,126,8,6,3,77,88,99,126}
sss = {i for i in s if i % 2 ==0}
print(sss)

多循环集合的内涵

s1 = {1,2,3,4}
s2 = {"nice","to","meet","you"}

s = {m*n for m in s2 for n in s1}
print(s)

**2.  Python中赋值引用、浅拷贝与深拷贝

 Python中关于对象复制有三种类型的使用方式,赋值、浅拷贝与深拷贝。

1. 赋值(函数的参数/返回值)——对象引用

在Python中赋值就是对象的引用。不会开辟新的空间,只是复制了对象的引用。

包括两种形式:

  1. b = a
  2. 对象作为函数参数或返回值

分为了两种情况

  1. 不可更改对象:tuple、数字、字符等原子类型——a、b的改变互不影响(相当于指向了另一个对象的内存空间)
  2. 可更改的对象:list、dict、set——a、b改变都会互相影响(相当于内存中的对象改变)

复制代码

#一、不可更改对象
# 1. 数字
>>> a = 5
>>> b = a #b = 5
>>> a = 3 # b = 5 :a改变,b不会改变
>>> b = 2 # a = 3:b改变,a不会改变

# 2. 字符串
>>> a = 'sbw'
>>> b = a   #b = 'sbw'
>>> a = 'lf'  #b = 'sbw':a改变,b不会改变
>>> b = 'wf' #a = 'lf' :b改变,a不会改变

# 3. tuple
>>> a = (1, 2, 3)
>>> b = a   #b = (1, 2, 3)
>>> a = ('a', 'b', 'c')  #b = (1, 2, 3):a变化,b不会改变
>>> b = ('d', 'e') # a = ('a', 'b', 'c'):b改变,a不会改变

复制代码

复制代码

# 二、可更改的对象
# 1. list
>>> l1 = [1, 2 ,3, ['a', 'b']]
>>> l2 =l1 #l2 = [1, 2, 3, ['a', 'b']]
>>> l1.append('new_l1') #l2 = [1, 2, 3, ['a', 'b'], 'new_l1'] :b随a变化
>>> l2.append('new_l2') #l1 = [1, 2, 3, ['a', 'b'], 'new_l1', 'new_l2']:a随b变化

# 同理:dict和set

复制代码

 

2. 浅拷贝

浅拷贝会创建新对象(两个对象的id不同),但是其内容是原对象的引用。

一般情况下,一方改变不会影响另一方,但是如果存在对嵌套对象改变,则双方会互相影响。

三种方式

  1. 切片操作:l2 = l1[:]
  2. 工厂函数:l2 = list(l1)
  3. copy函数:l2 = copy.copy(l1)

复制代码

>>> l1 = [1, 2, 3, ['a', 'b']]
>>> l2 = copy.copy(l1)  #l2 = [1, 2, 3, ['a', 'b']]

>>> id(l1)
4330497160
>>> id(l2)
4330557320  # l1、l2指向的是不同的地址

>>> [id(x) for x in l1] #l2内容是原对象l1的引用(引用的元素地址相同)
[4297554880, 4297554912, 4297554944, 4330499912]
>>> [id(x) for x in l2]
[4297554880, 4297554912, 4297554944, 4330499912]

# 一般情况下,l1和l2的改变互不影响
# 但是如果其中一个对里面的嵌套list进行修改,另一个会发生变化
>>> l2.append('new_l2') #l2 = [1, 2, 3, ['a', 'b'], 'new_l2']
>>> l1 #l1不发生改变
[1, 2, 3, ['a', 'b']]

>>> l1.append('new_l1') #l1发生改变,l2不变
>>> l1  #[1, 2, 3, ['a', 'b'], 'new_l1']
>>> l2  #[1, 2, 3, ['a', 'b'], 'new_l2']

>>> l1[3].append('c') #l1更改里面的嵌套list,l2改变
>>> l1    #[1, 2, 3, ['a', 'b', 'c'], 'new_l1']
>>> l2    #[1, 2, 3, ['a', 'b', 'c'], 'new_l2']

复制代码

 

3. 深拷贝

方式:copy.deepcopy()

和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因而,它的时间和空间开销要高。

两个对象完全独立,互不影响。

 

另外注意:

1、对于非容器类型,如数字,字符,以及其它“原子”类型,没有拷贝一说。产生的都是原对象的引用。

2、如果元组变量值包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。

 

**3. Python的特点——美团

1. 动态语言。

  • 动态类型语言:也就是可以不用事先声明变量,就可以对它赋值。在编译的时候,Python不会进行类型检查,而是直至运行时,自动判断变量类型并对变量赋值,所以可能抛出类型错误。
  • 静态类型语言:变量在赋值前必须声明数据类型。这样的好处是在编译器就可以发现类型错误。例如:C++/C、Java

2. 解释型语言

  • 编译型语言,例如C++,源文件需要通过编译器使用一系列标志和选项转换成计算机可以识别的二进制格式;在运行程序时,链接器/载入器软件将程序从硬盘复制到内存,然后开始运行。
  • 对于解释型语言,例如Python,在计算机内部,Python解释器把源代码转换成称为字节码的中间形式,然后再把它翻译成计算机使用的机器语言并运行。

3. 支持面向对象编程和面向过程的编程。

  • 面向对象:围绕着数据和功能
  • 面向过程:程序围绕着过程或者函数(可重复使用的程序片段)构建

Python具有非常强大但是过于简洁的执行面向对象编程的方式,特别是相对于C++或者Java这种大型语言来说

4. 高层语言

  • Python在编写程序的时候,无需考虑如何管理你的程序使用内存一类的底层细节。
  • C++语言:需要手动分配、销毁对象的内存。

5. 速度快

Python 的底层是用 C 语言写的,很多标准库和第三方库也都是用 C 写的,运行速度非常快

6. 使用强制缩进来规范代码,使得程序有极佳的可读性。

7. 可移植性。基于其开源代码的特性,Python已被移植到很多平台。

8.可扩展性。

如果需要一段运行很快的代码,或者是不愿开放的代码,可以使用C/C++编写,再再Python中调用

9. 扩展库强大。特别是在数据处理方面

 

4. Python如何进行内存管理的?

从三个方面来说:1. 引用计数;2. 垃圾回收; 3.内存池机制;

1. 引用计数

Python是动态语言,即在使用前不需要声明,对内存地址的分配是在程序运行时自动判断变量类型并赋值。

对每一个对象,都维护着一个指向改对象的引用的计数,obj_refcnt

当变量引用一个对象,则obj_refcnt+1,如果引用它的变量被删除,则obj_refcnt-1,系统会自动维护这个计数器,当其变成0时,被回收。

2. 内存池机制

Python内存机制分为6层,内存池用于管理对小块内存的申请和释放。

  • -1,-2层:主要由操作系统进行操作
  • 0层:是C中的malloc,free等内存分配和释放函数进行操作
  • 1,2层:内存池,由Python的接口函数PyMem_Malloc函数实现,当对象<256字节的时候,由该层直接分配内存。虽然,对应的也会使用malloc直接分配256字节的内存快——解决C中malloc分配的内存碎片问题。
  • 3层:最上层,我们对Python对象的操作

内存池与C不同的是,:

  • 如果请求分配的内存在1~256字节之间就使用自己的内存管理系统,否则直接使用 malloc。
  • 这里还是会调用 malloc 分配内存,但每次会分配一块大小为256k的大块内存。
  • 经由内存池登记的内存,最后被回收到内存池,而不会调用C的free释放掉,以便下次使用。
  • 对于Python对象,如数值、字符串、元组,都有其独立的私有内存池,对象间不共享他们的内存池。例如:如果分配又释放了一个大的整数,那么这块内存不能再被分配给浮点数。

3. Python的垃圾回收机制。

PythonGC主要是通过引用计数来跟踪和回收垃圾,并使用“标记-清理”技术来解决容器对象可能产生的循环引用问题,使用“分代回收”提高回收的效率。

1. 引用计数:在Python中存储每个对象的引用计数obj_refcnt。当这个对象有新的引用,则计数+1,当引用它的对象被删除,则计数-1,当技术为0时,则删除该对象。

  • 优点:简单、实时性
  • 缺点:维护引用奇数消耗资源、无法解决循环引用的问题。

2. 标记-清理:基本思路是按需分配。等到没有空闲时,从寄存器和程序栈中的引用出发,遍历以对象为节点,引用为边所构成的图,把所有可以访问到对象都打上标记,然后清扫一遍内存空间,把没有标记的对象释放。

3. 分代收集

总体的思想:Python将系统中所有的内存块根据对象存活时间划分为不同的代(默认是三代),垃圾收集频率随着“代”的存活时间的增大而减小。

举例:

我们分为新生代、老年代、永久代。

在对对象进行分配时,首先分配到新生代。大部分的GC也是在新生代发生。

如果新生代的某内存M进过了N次GC以后还存在,则进入老年代,老年代的GC频率远低于新生代。

对于永久代,GC频率特别低。

 

5. 单下划线、双下划线

  • __name__:一种约定,Python内部的名字,用来与用户自定义的名字区分开,防止冲突
  • _name:一种约定,用来指定变量私有
    • import *情况下,解释器会对单下划线开头的名称做处理
    • from module/package import *,任何单下划线开头的名称不会被导入,除非模块/包的__all__列表明确包含了这些名称
  • __name
    • 解释器用_classname__name来代替这个名字用以区别和其他类相同的命名
    • 现在创建一个A的子类B,B就不会轻易的覆盖掉A中的__method_name了,相当于:Java中的final

 

**6. 类变量和实例变量

类变量就是供类使用的变量,实例变量就是供实例使用的

作为函数的参数/返回值——赋值引用——1. 对于不可更改对象,互不影响;2. 可更改对象:会互相影响。

复制代码

class Person:
    name="aaa"  #类变量

p1=Person()
p2=Person()
p1.name="bbb" #实例p1调用类变量name='aaa',在实例作用域里将类变量的引用改变了,变成了实例变量。self.name指向实例变量。
print p1.name  # bbb
print p2.name  # aaa
print Person.name  # aaa

复制代码

 

如果将类变量name =[]换成数组,则是不可变——相当于在内存中更改了对象的值

复制代码

class Person:
    name=[]

p1=Person()
p2=Person()
p1.name.append(1) #类变量
p1.name  # [1]
p2.name  # [1]
Person.name  # [1]

复制代码

 

**7. @staticmethod和@classmethod

他们都是装饰器。装饰器是一种特殊的函数,要么接受函数作为参数,并返回一个函数;要么接受一个类作为参数,返回一个类。他们的作用是对运行中的程序动态的加入特定的功能而不改变函数的本身。@标记是语法糖,可以让你简单易读的方式获得方法的装饰目标对象。

 实例/成员方法类方法静态方法
标记
例如:def __init__(self, name)
@classmethod@staticmethod
参数实例self类实例cls无,不需要对类或者实例进行绑定
调用a.foo(x)只可用实例调用,
不可以用类调用
A.class_foo(x)、a.class_foo(x)
类、实例可调用类方法
A.static_foo(x)、a.static_foo(x)
类、实例可调用类方法
访问类变量

可以访问。若在成员方法内对:

1. 不可更改的类变量做成更改,
  则实例中的类变量发生变化,但是对于类对象中的类变量没有改变。

2. 对可更改的类变量做成更改,实例中和类对象中的类变量都改变。

可以访问,直接影响
(无论是可更改还是不可更改)
 
可以访问。
访问实例变量能访问
每一个实例的实例变量互不影响
(无论是不可更改对象还是可更改对象)
不可访问 不可访问
访问静态变量不可以不可访问可以

 

一、实例方法/成员函数。

定义:实例所使用的方法。

1. 类的成员函数能访问类变量吗?——滴滴

 

复制代码

#coding=utf-8
class foo(object):
    class_var = 0 # 类变量,所有实例对象所共有
    class_var_dict = []

    def __init__(self):
        self.instance_var = 1 #实例/成员变量:针对每个实例对象自身持有
        self.instance_var_dict = []

        # 在成员方法中。对可变类变量class_var_dict进行修改——都发生变化
        self.class_var_dict.append('a') 
    
       # 在成员方法中,对不可变类变量class_var进行修改
        #不影响foo.class_var = 0
       # 两个实例变量发生变化:foo1.class_var = foo2.class_var = 1 
        self.class_var += 1

# 创建两个实例
foo1 = foo()
foo2 = foo()    

复制代码

在成员方法中,

  • 对不可更改的类变量(数字、字符串、tuple)做成更改,则实例中的类变量发生变化,但是对于类对象中的类变量没有改变。
  • 可更改的类变量(list、set、dict)做成更改,则实例中和类对象中的类变量都改变。

复制代码

# 查看类对象和两个实例的变量:自己的命名空间__dict__(各不相同)
print('foo1的命名空间:', id(foo1.__dict__), (foo1.__dict__)) 
print('foo2的命名空间:',id(foo2.__dict__), (foo2.__dict__))
# foo1的命名空间: 4300908680 {'instance_var': 1, 'class_var': 1}
# foo2的命名空间: 4300909064 {'instance_var': 1, 'class_var': 1}
# foo类对象的命名空间: 4328743560 {'__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'foo' objects>, 'class_var_dict': ['a', 'a'], '__init__': <function foo.__init__ at 0x102074950>, 'class_var': 0, '__dict__': <attribute '__dict__' of 'foo' objects>}

# 在实例中,更改不可更改的类变量——互不影响
foo1.class_var = '2'
print('foo2.class_var:', foo2.class_var) #foo2.class_var: 1
print('foo类对象的类变量class_var:', foo.class_var) #foo.class_var: 0

# 在实例中,更改可更改的类变量——都会更改
foo1.class_var_dict.append('new_for_foo1')
print('foo2.class_var_dict:',foo2.class_var_dict) #foo2.class_var_dict: ['a', 'a', 'new_for_foo1']
print('foo类对象的类变量class_var_dict:', foo.class_var_dict)  # foo.class_var_dict: ['a', 'a', 'new_for_foo1']

复制代码

2. 类的成员函数能够访问实例变量吗?

每个实例的实例变量都完全独立,互不影响。

在成员函数中调用实例变量:self.instance_var

在实例中调用实例变量:实例名.instance_var

复制代码

# 更改foo1中实例变量——即使是可更改对象dict,都互不影响
print('foo1中实例变量的ID:', id(foo1.instance_var_dict))#4321671176
print('foo2中实例变量的ID:', id(foo2.instance_var_dict))#4321669192

foo1.instance_var_dict.append('new_for_foo1')
print('foo1.instance_var_dict:', foo1.instance_var_dict)
#foo1.instance_var_dict: ['new_for_foo1']
print('foo2.instance_var_dict:', foo2.instance_var_dict)#foo2.instance_var_dict: []

class A():
     m = 1
     print (m)
     def a(self):
         print ('====')
         n=2
         self.m = self.m + 1
         print (self.m)


q = A()
print ('+++')
q.m = 4
print ('****')
q.a()
》》》
1
+++
****
====
5


class A():
     m = 1
     print (m)
     def a(self):
         print ('====')
         n=2
         self.m = self.m + 1
         print (self.m)


q = A()
print ('+++')
# q.m = 4
print ('****')
q.a()
》》》

1
+++
****
====
2
对比可以看出,代码的执行顺序,方法中self.m是首先调用实例变量的

复制代码

 

二、静态方法

静态方法是一种普通函数,就位于类定义的命名空间中,它不会对任何实例类型进行操作。使用装饰器@staticmethod定义静态方法。类对象和实例都可以调用静态方法

1. 静态方法能访问成员变量吗?

不能够访问成员变量,能访问并更改类变量。

复制代码

   @staticmethod
    def static_foo():
        # 更改类属性:
        foo.class_var = 10
        foo.class_var_dict.append('new_static_methoda')

        print('foo.class_var', foo.class_var)
        print('foo.class_var_dict', foo.class_var_dict)

复制代码

2. 静态方法能访问类变量吗?

可以。

三、类方法

类方法是将类本身作为对象进行操作的方法。类方法使用@classmethod装饰器定义,其第一个参数是类,约定写为cls。类对象和实例都可以调用类方法

1. 类方法能访问类成员变量吗?

能够。并且直接影响,无论是可更改还是不可更改属性。 

复制代码

#coding=utf-8
class foo(object):
    class_var = 0 # 类变量,所有实例对象所共有
    class_var_dict = []

    def __init__(self):
        self.instance_var = 1
        self.instance_var_dict = []

        self.class_var_dict.append('a')
        self.class_var += 1

    @classmethod
    def class_foo(cls):
        # 类方法中调用实例变量——报错!'foo' has no attribute 'instance_var'
        print('cls.isinstance:', cls.instance_var)
        print('cls.isinstance_dict:', cls.instance_var_dict)

        # 在类方法中调用类变量——直接影响
        cls.class_var_dict.append('new_class_foo')
        print('cls.class_var_dict:', cls.class_var_dict)
        print('foo.class_var_dict:', foo.class_var_dict)

        cls.class_var = 2
        print('cls.class_var:', cls.class_var)
        print('foo.class_var:', foo.class_var)


foo1 = foo()
foo2 = foo()

复制代码

最后结果:

cls.class_var_dict: ['a', 'a', 'new_class_foo']
foo.class_var_dict: ['a', 'a', 'new_class_foo']
cls.class_var: 2
foo.class_var: 2

2. 类方法能访问实例变量吗?

不能。

报错:AttributeError: type object 'foo' has no attribute 'instance_var'

 

四、super方法

super()是调用父类的方法,在子类中,并不会主动调用父类的__init__方法。

例如:

复制代码

class Foo(object):
    def __init__(self):
        self.val = 1
class Foo2(Foo):
    def __init__(self):
        print(self.val)

if __name__ == '__main__':
    foo2 = Foo2()

复制代码

运行时,报错

有两种方法:

一、调用实例方法

复制代码

class Foo(object):
    def __init__(self):
        self.val = 1
class Foo2(Foo):
    def __init__(self):
        Foo.__init__(self)   #类调用实例方法时,需要传入self指代的实例
        print(self.val)

if __name__ == '__main__':
    foo2 = Foo2()

复制代码

二、使用super()

复制代码

class Foo(object):
    def __init__(self):
        self.val = 1
class Foo2(Foo):
    def __init__(self):
        super(Foo2, self).__init__()  
        print(self.val)

if __name__ == '__main__':
    foo2 = Foo2()

复制代码

 

 

**8. 多线程

1. 滴滴问题:你有用过多线程吗?多线程是用来干什么的?

2. 多线程作用:可以实现代码的并发性,优化处理能力。同时更小的功能划分可以使代码的重用性更好。

3. 实现:GIL(全局解释器锁),用thread、threading和Queue模块可以用来实现多线程编程

4. 解决方法:多进程和协程。协程是进程和线程的升级版。进程和线程都面临着内核态和用户态的切换问题。

协程可以自己控制切换的时机,不再需要陷入系统的内核态。

yield:协程的思想

1)GIL锁原理

Python代码的执行是由Python解释器来控制,在同一时刻只能由一个线程在解释器中运行。那么对Python解释器的访问就通过GIL来实现。

GIL能够保证在同一时刻只有一个线程运行。

在多线程的环境中,Python解释器按以下方式执行

  1. 设置GIL
  2. 切换到一个线程去运行
  3. 当运行了指定数量的字节码指令,或线程主动让出控制(可以调用time.sleep(0)),则把该线程设置为睡眠状态,解锁GIL;
  4. 当一个线程结束计算,它就退出了。
    • 可以调用thread.exit()之类的退出函数(但是不建议使用thread,因为主线程退出后,所有的子线程强制退出,且不会清理资源)
    • 也可以使用Python退出进程的标准方法,如sys.exit()或抛出一个SystemExit异常等。
    • 不过,不可以直接“杀掉”("kill")一个线程。
  5. 当调用外部代码(如C/C++扩展函数)时,GIL会被锁定,直到这个函数执行完成。(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

2)比较三个模块:thread、threading、queue

首先,thread和threading都允许程序员创建和管理线程。

  1. thread:提供了基本的线程和锁的支持。当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清理工作。
  2. threading:能确保重要的子线程退出后进程才退出。
  3. Queue模块:允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。

对于threading模块

复制代码

import threading
from time import sleep, ctime

nsecs = [2, 4]

def loop(nloop, nsec):
    print("Start", nloop, "at", ctime())
    sleep(nsec)
    print("End", nloop, "at", ctime())

def main():
    print("All start at", ctime())
    nloops = range(len(nsecs))
    nthreads = [] #线程组

    # 创建线程
    for i in nloops:
        t = threading.Thread(target=loop, args=(i, nsecs[i]))
        nthreads.append(t) #添加到线程组

    # 执行线程
    for i in nloops:
        nthreads[i].start()

    # 等待所有线程执行完成
    for i in nloops:
        nthreads[i].join()

    print("All done at ", ctime())

if __name__ == "__main__":
    main()

复制代码

对于thread模块

复制代码

import _thread
from time import sleep, ctime

loops = [4, 2]

def loop(nloop, nsec, lock):
    print("Start", nloop, "at", ctime())
    sleep(nsec)
    print("End", nloop, "at", ctime())
    lock.release()

def main():
    print("All start at ", ctime())
    nlocks = []
    nloops = range(len(loops))
    nsecs = loops

    for i in nloops:
        # 分配锁
        lock = _thread.allocate_lock()
        # 锁定锁
        lock.acquire()
        # 追加到locks[]数组中
        nlocks.append(lock)

   # 对每一个线程调用loop函数
    for i in nloops:
        _thread.start_new_thread(loop, (nloops[i], nsecs[i], nlocks[i]))

    # 判断所有的nloop线程都执行完以后结束
    for i in nloops:
        while nlocks[i].locked:
            pass

    print("All end at ", ctime())

if __name__ == "__main__":
    main()

复制代码

 

9. Python自省

自省就是面向对象的语言所写的程序在运行时,能知道对象的类型。

type()、dir():列出制定对象或类的属性、getattr()、hasattr()、isinstance()

 

10. @property装饰器

作用:把一个方法变成属性调用。在一个类中:

  1. getter方法变成属性,只需要加上:@property
  2. setter方法变成属性:需要创建另一个装饰器@属性名.setter
  3. 如果只有getter方法,没有setter方法,则表明只读,不可修改

复制代码

class Student(object):

    @property #将score方法当作属性调用(相当于score 的getter方法)
    def score(self):
        return self._score

    @score.setter #score的setter方法,其中包括了验证score
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('Score must be an integer')
        if value < 0 or value > 100:
            raise ValueError('Score must between 0~100')
        self._score = value

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, str):
        if not isinstance(str, str):
            raise ValueError('Name must be a string')
        self._name = str

s = Student()
s.score = -11
s.score

复制代码

运行结果:

Traceback (most recent call last):
  File "/Users/lesley/PycharmProjects/sort/Student.py", line 26, in <module>
    s.score = -11
  File "/Users/lesley/PycharmProjects/sort/Student.py", line 12, in score
    raise ValueError('Score must between 0~100')
ValueError: Score must between 0~100

 

11. Python中的pass语句作用是什么?

pass语句不会执行任何操作,一般作为占位符或者创建占位程序。

 

12. Python如何进行类型转换?

Python提供了将变量或值从一个类型转换成另一个类型的内置方法。

1. 数值类:int(x  [,base]), long(x [,base]), float(x), complex(real [, image])

2. 字符串:str(x)、chr(x)

3. 其他:list(s), tuple(s)

 

 

13. 字符串格式化:% 和 .format

.format()就是用{}:来代替%

1). %无法同时传递一个变量和元组

复制代码

>>>name='sbw'
>>>"hi, there is %" %name and #输出正确

>>>name=(1, 2, 3) #元组
>>>"hi there is %" %name #出错
# 必须更改为:
>>>"hi, there is %" %(name, ) #提供一个单元素的元组

# 使用.format()
>>>"hi, there is {}".format(name) #'hi,there is (1, 2, 3)'

复制代码

2). 可重用参数——通过位置映射

>>> '{name},{age}'.format(age=19,name='sbw')
'sbw,19'

 3). 格式限定符——通过{}:

它有着丰富的的“格式限定符”(语法是{}中带:号),比如:

  • 填充与对齐

填充常跟对齐一起使用^、<、>分别是居中、左对齐、右对齐,后面带宽、填充的字符,只能是一个字符,不指定的话默认是用空格填充

复制代码

# 右对齐,填充字符为8个
>>> '{:>8}'.format('189')
 '     189'

# 用0填充8个字符位、右对齐
>>>'{:0>8}'.format('189')
 '00000189'

# 用字符a填充8个字符位、右对齐
>>>'{:a>8}'.format('189')
 'aaaaa189'

复制代码

  • 精度与类型f

精度常跟类型f一起使用

>>>'{:.2f}'.format(321.33345)
'321.33'

# .2表示:长度为2的精度;f表示:float

 

  • 其他类型

主要就是进制了,b、d、o、x分别是二进制、十进制、八进制、十六进制。

复制代码

# 二进制:'{:b}
>>>'{:b}'.format(17)
 '10001'

# 十进制 {:d}
>>>'{:d}'.format(17)
 '17'

# 八进制 {:o}
>>>'{:o}'.format(17)
'21'

#十六进制 {:x}
>>>'{:x}'.format(17)
'11'

复制代码

用,号还能用来做金额的千位分隔符。

# {:,}:金额的分隔符
>>> '{:,}'.format(1234567890)
 '1,234,567,890'

 

14. 迭代器(iteration)和生成器(generator)

14.1 迭代——可以通过for...in来遍历——创建[for...in]

只要作用于一个可迭代对象,for都可以正常运行,不关心对象类型。

1. 可迭代对象——Iterable

可以直接作用于for循环的对象,称为可迭代对象,有以下几种:

  • 一类是集合数据类型,如listtupledictsetstr等;
  • 一类是generator,包括生成器和带yield的generator function。

可以使用isinstance()判断一个对象是否是Iterable对象:

复制代码

#判断一个对象是否可以迭代——collection.Iterable
>>> from collections import Iterable
>>> isinstance('abd', Iterable) #str可迭代
True
>>> isinstance([1, 2, 3], Iterable) #数组可迭代
True
>>> isinstance(123, Iterable)#数字不可迭代
False
>>> 

复制代码

2. 迭代器——可以被next()函数调用并不断返回下一个值的对象(Iterator)——计算是惰性的

复制代码

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

复制代码

生成器都是可迭代对象,但是str、dict、list虽然Iterable,但是不是Iterator。

listdictstrIterable变成Iterator可以使用iter()函数:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

为什么listdictstr等数据类型不是Iterator

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

  • 对dict迭代

复制代码

# 1. 对dict迭代
>>>dict = {'a':1, 'b':2, 'c':3}

#对key(默认;因为存储不按照list,所以迭代结果顺序可能不同)
>>> for key in dict:
...     print(key)
... 
c
b
a

# 对value进行迭代
>>> for value in dict.values():
...     print(value)
... 
3
2
1

# 同时对values和key迭代:
>>> for key,item in dict.items():
...     print((key, item))
... 
('c', 3)
('b', 2)
('a', 1)

复制代码

  • 对字符串

复制代码

>>> ch = 'ABC'
>>> for i in ch:
...     print(i)
... 
A
B
C
>>> 

复制代码

  • Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
>>> for i in enumerate(['A', 'B', 'C']):
...         print(i, value)
... 
(0, 'A') 3
(1, 'B') 3
(2, 'C') 3

 

14.2 列表生成器/字典生成器

  1. 列表生成器

>>> L1 = ['Hello', 'World', 18, 'Apple', None]

>>> L2=[s.lower() for s in L1 if isinstance(s,str)==True]
>>> L2
['hello', 'world', 'apple']

例如:列出当前目录下的所有文件和目录名

>>> import os
>>> [dir for dir in os.listdir('.')]
['.bash_history', '.bash_profile', ...]

 

  2. 字典生成器

>>>dict = {'a':1, 'b':2, 'c':3}
>>> dict2 = {key:value for key,value in dict.items()}
>>> dict2
{'c': 3, 'b': 2, 'a': 1}

 

6.3 迭代器和生成器

引入生成器:列表容量大时,会占用很大的存储空间——生成器可以一边循环一边计算

生成器 ——1. 创建:(x*x for x in range(3));2.边循环边计算(只迭代一次)

是迭代器的一种,但是你只能迭代它们一次。因为它们不是全部存在内存里,它们只在要调用的时候在内存里生成:

复制代码

# 创建方法:()
>>> generator= (x*x for x in range(3))
>>> generator
<generator object <genexpr> at 0x1022ba990>

# 不能在生成器中用for i in mygenerator第二次调用生成器:
# 首先计算0,然后会在内存里丢掉0去计算1,直到计算完4.
# 只能调用一次
>>> for I in generator:
...     print(i)
... 
0
1
4
>>> for i in generator:  
...     print(i)
... 

复制代码

 

14.4 yeild关键字

yeild 可以理解为返回一个生成器。对于一个生成器的函数,

  • 当for语句第一次调用函数里返回的生成器对象,代码才运行,遇到yield语句就返回本次循环中的第一个返回值。
  • 下一次调用时,从上次返回的yield语句处继续执行。
  • 一旦函数运行并没有碰到yeild语句就认为生成器已经为空了

例如:狄波拉数列

复制代码

>>> def fib(max):
...     n, a, b = 0, 0, 1
...     while n < max:
...         yield b
...         a, b = b, a + b
...         n = n + 1
...     return 'done'
... 
>>> fib(10)
<generator object fib at 0x1022bae60>


# 输出
>>> f =fib(10)
>>> for i in f:
...     print(i)
... 
1
1
2
3
5
8
13
21
34
55

# 可以用next()来获取生成器的值(只能一次)>>> next(f)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

复制代码

例如:杨辉三角

复制代码

>>> def triangles():
...     L = [1]
...     while True:
...         yield L
...         L.append(0)
...         L = [L[i - 1] + L[i] for i in range(len(L))]
... 
>>> for t in triangles():
...         print(t)
...         n = n + 1
...         if n == 10:
...             break
... 
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

复制代码

 

15. *args 和**kwargs

*args**kwargs只是为了方便并没有强制使用它们。

  • *args:可变的位置参数:不确定函数里传递参数的个数(可传递任意数量)

复制代码

>>> def print_everything(*args):
...     for count, thing in enumerate(args):
...             print('{0}, {1}'.format(count, thing))
... 
>>> print_everything(['a', 'b', 'c'])
0, ['a', 'b', 'c']
>>> print_everything('a', 'b', 'c')
0, a
1, b
2, c

复制代码

  • **kwargs:可变的关键字参数:允许你使用没有事先定义的参数名

复制代码

>>> def table_things(**kwargs):
...     for name, value in kwargs.items():
...         print '{0} = {1}'.format(name, value)
...
>>> table_things(apple = 'fruit', cabbage = 'vegetable')
cabbage = vegetable
apple = fruit

复制代码

*args**kwargs可以同时在函数的定义中,但是*args必须在**kwargs前面。

另外,调用函数时,也可以使用*、**

复制代码

>>> def print_three_things(a, b, c):
...     print 'a = {0}, b = {1}, c = {2}'.format(a,b,c)
...
>>> mylist = ['aardvark', 'baboon', 'cat']
>>> print_three_things(*mylist)

a = aardvark, b = baboon, c = cat

复制代码

 

16. 面向切面编程AOP和装饰器——考的也非常多!!

1)AOP定义:在程序运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想。

  ——通过装饰器实现

  • 切入到指定类/方法的代码:切面
  • 切入到哪些类,哪些方法:切入点

2)用处:较为经典的有插入日志、性能测试、事务处理等,通过声明的方式配置好。

——为什么不用公共类?如果我们只是提取了公共方法到一个独立的类和独立的方法中,然后再在其他类中调用,那么会在类与上面提到的独立的类之间产生耦合,一旦这个独立的类产生改变,那么会影响到调用他的类。

 

**17. 装饰器

装饰器是高阶函数,要么接受函数作为参数,并返回一个函数;要么接受一个类作为参数,返回一个类。他们的作用是对运行中的程序动态的加入特定的功能而不改变函数的本身。@标记是语法糖,可以让你简单易读的方式获得方法的装饰目标对象。

1. 基本的Decorator函数:

本质上来说:decorator是一个接受函数作为参数,并且返回函数的高阶函数。

举例:在login()函数中输出调试的信息

复制代码

def printDebug(func):
    def wrapper(*args, **kwargs):
        print('Start %s()' % func.__name__)
        func()
        print('End %s()' % func.__name__)
    return wrapper

@printDebug #语法糖
def login():
    print('Login in')

login() # 执行顺行:printDebug(login)()

复制代码

输出结果为:

Start login()
Login in
End login()

注意:

>>> login.__name__
'wrapper'

我们发现在decorator装饰过后的now()函数的名字__name__变成了:wrapper。为了防止有些依赖函数签名的代码出错,所以需要把原始函数的__name__等属性复制到wrapper函数中:通过Python内置的functools.wraps实现:

复制代码

import functools
def printDebug(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('Start %s()' % func.__name__)
        func()
        print('End %s()' % func.__name__)
    return wrapper

@printDebug
def login():
    print('Login in')

复制代码

2. login带参数:login(user)

复制代码

import functools
def printDebug(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('Start %s()' % func.__name__)
        func(*args, **kwargs)
        print('End %s()' % func.__name__)
    return wrapper

@printDebug
def login(user): # 函数带有参数
    print('Login in '+ user)

login('sbw')

复制代码

执行的顺序:[decprated]login('sbw')——>printDebug(login)('sbw')——>wrapper('sbw')——>[real]login('sbw')

3. 装饰器本身有参数——三层

我们在定义渣装饰器的时候,也可以加入参数,比如我们传入一个参数来指定Bug的级别

复制代码

import functools
# 装饰器带有参数:Bug级别:level
def print_debug_level(level):
    def print_debug(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('Start login with level:' + str(level))
            func(*args, **kwargs)
            print('End %s()' % func.__name__)
        return wrapper
    return print_debug

@print_debug_level(level=5)
def login(user): # 函数带有参数
    print('Login in '+ user)

login('sbw')

复制代码

输出结果:

Start login with level:5
Login in sbw
End login()

 

执行过程:[decorated]login('sbw')——>print_debug_level(5)——>print_debug[with closure value 5](login)('sbw')——>wrapper('sbw')[use value 5] ——>[real]login('sbw')

4. 装饰器带有返回值的函数

例如:login返回值message用来判断login是否成功

复制代码

import functools
# 装饰器带有参数:Bug级别:level
def print_debug_level(level):
    def print_debug(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('Start login with level:' + str(level))
            result = func(*args, **kwargs)
            print('The Login result is:',result)
            return result
        return wrapper
    return print_debug

@print_debug_level(level=5)
def login(user): # 函数带有参数
    print('Login in '+ user)
    msg='success' if user == 'sbw' else 'fail'
    return msg

login('sbw')

复制代码

5. 改进

在上面这个例子中,因为装饰器只能用于函数的整体,不能用于一部分,所以我们可以将验证validation的代码提取出来。同时因为validation()过程耗时,所以我们可以添加缓存cache。

复制代码

#coding=utf-8
import time
import functools
dict_cache={} # 存放所有验证过的用户和验证的结果

def cache(func):
    @functools.wraps(func)
    def wrapper(user):
        now = time.time()
        if user in dict_cache: # 判断这个用户是否曾经验证过
            print('曾经验证过')
            user, cache_time = dict_cache[user]
            if now - cache_time > 30: # 如果上一次验证的时间在30s之前,则重新进行验证
                print(u'超过了缓存时间')
                result = func(user)
                dict_cache[user] = (result, now)
            else: # 还在缓存时间内
                print('还在缓存时间内')

        else:
            print('第一次验证')
            result = func(user)
            dict_cache[user] = (result, now)

        return result
    return wrapper

def login(user):
    print('in login:'+user)
    msg = validation(user)
    print(msg)

@cache
def validation(user):
    time.sleep(1)
    msg = 'success' if user == 'sbw' else 'fail'
    return msg

login('sbw')
login('lf')
login('lf')
login('sbw')
login('sbw')

复制代码

6. 应用多个装饰器

复制代码

def print_debug(func):
    def wrapper():
        print('enter the login')
        func()
        print('exit the login')

    return wrapper


def others(func):  # define a other decorator
    def wrapper():
        print('***other decorator***')
        func()

    return wrapper


@others  # apply two of decorator
@print_debug
def login():
    print('in login:')


@print_debug  # switch decorator order
@others
def logout():
    print('in logout:')


login()
print('---------------------------')
logout()

复制代码

运行结果:

复制代码

***other decorator***
enter the login
in login:
exit the login
---------------------------
enter the login
***other decorator***
in logout:
exit the login

复制代码

对于logout()函数应用于装饰器可以看成:

复制代码

@print_debug    #switch decorator order
(
    @others
    (
        def logout():
            print('in logout:')
    )
)

复制代码

[print_debug decorated]logout() ——>print_debug.wrapper[call [others decorated]logout() ] ——>print_debug.wrapper.other.wrapper[call real logout]

总结:

无论是两层还是三层装饰器,decoration和wrapper的传参个数,与要装饰的方法有关,例如add(self,a,b)包含三个参数,此时,他们的传参中也必须要包含三个参数,不然会报错TypeError: decoration() takes 1 positional argument but 3 were given
class fn():
    # -----装饰器
    def decorator_add(*args,**kwargs):
        def decoration(func,*args,**kwargs):
            def wrapper(self,*args,**kwargs):
                try:
                    func()
                    print('装饰器')
                except Exception as msg:
                    print(msg)
            return wrapper
        return decoration

    @decorator_add
    def add(self,a,b):
        a = 0
        b=1
        sum = a+b
        print(sum)
        return sum

 

18. 鸭子类型

简单来说,如果一个东西,走路像鸭子,叫起来像鸭子,游泳起来像鸭子,我们就认为她是一只鸭子。——只关心行为。

例如,有两个类,一个是人类,一个是鸭子类,他们都会吃饭和走路,我们不管他们这两个方法中返回的值是否相同,只要她们能够吃饭和走路,我们统统当作鸭子,传递给一个函数当参数。

例如:list.extend(obj)方法中,只要obj可以迭代,那么我们不管他是list/dict/tuple/strs我们都可以传递进去当参数。

 

19. Python不支持函数重载

1)什么是函数重载?

函数重载,主要是为了解决两个问题:

  • 可变参数类型
  • 可变参数个数

另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

2)Python调用函数机制:使用*agrs:可传入任意数量的参数;**wkagrs:可传入任意类型的阐述(事先未声明的)

 

20. 新式类和旧式类

MBR问题:(新式类是广度优先,旧式类是深度优先)

http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html

 

21. __new__ 和 __init__

  1. __new__是一个静态方法,而__init__是一个实例方法.
  2. __new__方法会返回一个创建的实例,而__init__什么都不返回.
  3. 只有在__new__返回一个cls的实例时后面的__init__才能被调用.
  4. 当创建一个新实例时调用__new__,初始化一个实例时用__init__.

假如cls是一个类对象,当你调用C(*args, **kwargs)来创建一个类C的实例,python的内部机制:

  1. 通过:C.__new__(C, *args, **wkargs)创建这个类实例,并返回这个实例c
  2. 确认c是C的实例
  3. 调用C.__init(c, *args, **kwargs)初始化实例c
c = C.__new__(C, *arg, **kwargs)
if(is instance(c, C)):
    c.__init__(c, 23) #__init__第一个参数要为实例对象

 

22. Python命名空间

1)定义

在Python中,使用命名空间来记录变量的轨迹,本质是一个字典,key是变量名,value是那些变量的值。命名空间分为:

  • Local(局部命名空间):函数的命名空间。记录了:函数的参数、局部变量。可用locals()查看
  • Enclosing(内嵌函数命名空间):def、lambda
  • Global(模块的命名空间)。记录了:函数、类、其他导入模块、模块级的变量和常量。可用globals()查看
  • Built-in(内置命名空间):(Python标准库中的函数)模块,任何模块均可访问它,它存放着内置的函数和异常。

2)命名空间查找顺序——Python函数作用域的LEGB顺序

L:local 局部命名空间(当前函数)
E:enclosing 内嵌函数(def;lambda;父函数)
G:global 模块命名空间
B:build-in 内置命名空间

如果找不到:NameError: name 'aa' is not defined。

3)命名空间的生命周期

  • 内置命名空间在python解释器启动时创建,一直保留、不删除
  • 模块的全局命名空间在模块定义时被读入创建,一直保留到解释器退出
  • 当函数被调用时,创建一个局部命名空间。当函数返回结果或抛出异常时,被删除。

4)locals() & globals()

locals():返回的是局部命名空间的拷贝。——只读

globals():返回的是全局命名空间。——可改动

5)from module import  & import module

  • 使用 import module,模块自身被导入,但是它保持着自已的名字空间——>需要使用模块名来访问它的函数或属性(module.function)
  • 使用 from module import,实际上是从另一个模块中将指定的函数和属性导入到你自己的名字空间,——>可以直接访问它们却不需要引用它们所来源的模块的原因。

 

23. 闭包(closure)

闭包(closure)是函数式编程的重要的语法结构,也是一种组织代码的结构。它提高了代码的可重用行。

创建一个闭包的三个条件

  1. 必须有内嵌函数
  2. 内嵌函数必须要引用外部函数中的变量
  3. 外部函数返回值必须是内嵌函数

重点是函数运行后并不会被撤销,像单例模型中instance字典一样,当函数运行完后,instance并不被销毁,而是继续留在内存空间里。这个功能类似类里的类变量,只不过迁移到了函数上。

闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样。

 

24. 面向函数式编程

什么是面向函数式编程?——关注我们要干什么?而不是怎么干

1. lambda匿名函数

>>> sum = lambda x,y:x*y
>>> sum(1,2)
2

匿名函数的命名规则,用lamdba 关键字标识,冒号(:)左侧表示函数接收的参数(a,b) ,冒号(:)右侧表示函数的返回值(a+b)。

2. map()

有两个参数:函数名,作用对象(eg:list)。

r = map(func, seq)
  • 函数func作用于seq中每个元素
  • 输出必须转为list()
>>> m = map(lambda x:x*x, range(3))
>>> list(m) # 输出必须转化成list()
[0, 1, 4]
>>> m
<map object at 0x1022be828>

3. reduce()

  • 需要引入reduce()函数:from fuctools import reduce
  • 类似于map,但是作用于seq中每两个元素
  • 直接输出

例如:阶乘的实现:

>>> from functools import reduce
>>> result = reduce(lambda x,y:x*y, range(1, 6))
>>> result
120

4. filter()

# 定义:通过函数func过滤掉seq中不满足条件的元素
# func:返回值必须为boolean

filter(func, seq)

需要注意:

  • 必须要用list()才能返回结果
>>> numbers = [1, 2, -3, 8, -2, 4, 7]
>>> even_number = list(filter(lambda x:x>0 and x%2==0, numbers))
>>> even_number
[2, 8, 4]

 

25、Python中的设计模式

2.1  单例模式(Singleton)——必考!!绝对要记住1-2个方法

1. 定义

  • 单例模式:保证系统中一个类只有一个实例。
  • 目的:通过单例模式,可以保证系统中一个类只有一个实例,而且该实例易于外界访问。
  • 例如:一台计算机中可以有若干个打印机,但是只能有一个printer spooler(打印机后台处理服务),避免两个打印作业同时输出到打印机中。
  • 实现的要点:
    • 只提供私有的构造函数
    • 只含有一个该类的静态私有对象
    • 提供了一个静态的共用函数用于创建和获取他本身的静态私有对象

2. 四种实现方法:

1)使用__new__方法——将类变量绑定到_isinstance

复制代码

# 方法一:用__new__实现,将类实例绑定到类变量_instance上面
# 如果cls._instance为None说明该类还没有实例化过,实例化该类,并返回  
# 如果cls._instance不为None,直接返回cls._instance  
class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not (hasattr(cls, '_instance')):
            orig = super(Singleton, cls) 
            cls._instance = orig.__new__(cls, *args, **kwargs)
        return cls._instance

复制代码

实例:

复制代码

class MyClass(Singleton):  
    a = 1  

one = MyClass()  
two = MyClass()  
  
two.a = 3  
one.a  #3  
#one和two完全相同,可以用id(), ==, is检测  
id(one)  #29097904  
id(two)  #29097904  
one == two  #True  
one is two  #True  

复制代码

2)Borg——指向同一个__dict__

__dict__是一个字典,键是属性名,值为属性值

思想:共享属性。所谓单例模式就是所有引用(实例、对象)拥有相同的属性和方法;因为方法天然相同,所以主要保证属性一致。——>将实例的__dict__属性指向(引用)同一个字典

复制代码

class Borg(object):  
    _state = {}  
    def __new__(cls, *args, **kw):  
        ob = super(Borg, cls).__new__(cls, *args, **kw)  
        ob.__dict__ = cls._state  
        return ob  
  
class MyClass2(Borg):  
    a = 1  
  
one = MyClass2()  
two = MyClass2()  
  
#one和two是两个不同的对象,id, ==, is对比结果可看出  
two.a = 3  
one.a   #3  
id(one)  #28873680  
id(two)  #28873712  

one == two   #False  
one is two  #False  

#但是one和two具有相同的(同一个__dict__属性),见:  
id(one.__dict__)  #30104000  
id(two.__dict__)  #30104000  

复制代码

3) import——天然的的单例模式

因为在Python中,模块只会出初始化一次,所有变量都归属于某个模块,import机制是线程安全的

例如,我们创建一个函数,获取汇率。在一般情况下,我们只需要第一次获得这个汇率,以后对它进行操作就行了。

my_singleton.py:

class My_Singleton(object):
    def func(self):
        val = 1
 my_singleton = My_Singleton()
    

to use

from my_singleton import my_singleton
my_singleton.func()

4). 装饰器 

复制代码

def singleton(cls, *args, **kwargs):  
    instances = {}  
    def _singleton():  
        if cls not in instances:  
            instances[cls] = cls(*args, **kwargs)  
        return instances[cls]  
    return _singleton  
 
@singleton  
class MyClass4(object):  
    a = 1  
    def __init__(self, x=0):  
        self.x = x  
  
one = MyClass4()  
two = MyClass4()  
  
two.a = 3  
one.a  #3  
id(one)  #29660784  
id(two)  #29660784  
one == two  #True  
one is two  #True  

one.x = 1  
one.x  #1  
two.x  #1  

复制代码

 

 2.2 工厂模式

例如,一个婚介所,存放了男男女女的基本信息、要求。你想要找一个男朋友,只需要提出你的要求:高富帅,这个婚介所就只通过这些信息自动给你匹配。

复制代码

class Shape:
    pass

class Circle(Shape):
    pass

class Square(Shape):
    pass

for name in ['Circle', 'Sqaure']:
    cls = globals[name] # cls是一个类,它的类型是type
    obj = cls()  # 可以通过type()动态的创建类

复制代码

 结果:

cls    #<class '__main__.Square'>
obj.  # <__main__.Square object at 0x1012c3390>

type(obj)  #<class '__main__.Square'> obj是一个实例,它的类型就是 class Square
type(cls).  # <class 'type'> cls是一个类,它的类型就是type 

 

**26. os库和sys库的区别——腾讯有考!

1. os模块:——提供了一系列对操作系统进行操作的接口

复制代码

 1 os.getcwd() #获取当前工作目录,即当前python脚本工作的目录路径
 2 os.chdir("dirname")  #改变当前脚本工作目录;相当于shell下cd
 3 os.curdir  #返回当前目录: ('.')
 4 os.pardir  #获取当前目录的父目录字符串名:('..')
 5 os.makedirs('dirname1/dirname2')    #可生成多层递归目录
 6 os.removedirs('dirname1')    #若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
 7 os.mkdir('dirname')    #生成单级目录;相当于shell中mkdir dirname
 8 os.rmdir('dirname')    #删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
 9 os.listdir('dirname')    #列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
10 os.remove()  #删除一个文件
11 os.rename("oldname","newname")  #重命名文件/目录
12 os.stat('path/filename')  #获取文件/目录信息
13 os.sep    #输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
14 os.linesep    #输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
15 os.pathsep    #输出用于分割文件路径的字符串
16 os.name    #输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
17 os.system("bash command")  #运行shell命令,直接显示
18 os.environ  #获取系统环境变量
19 os.path.abspath(path)  #返回path规范化的绝对路径
20 os.path.split(path)  #将path分割成目录和文件名二元组返回
21 os.path.dirname(path)  #返回path的目录。其实就是os.path.split(path)的第一个元素
22 os.path.basename(path)  #返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
23 os.path.exists(path)  #如果path存在,返回True;如果path不存在,返回False
24 os.path.isabs(path)  #如果path是绝对路径,返回True
25 os.path.isfile(path)  #如果path是一个存在的文件,返回True。否则返回False
26 os.path.isdir(path)  #如果path是一个存在的目录,则返回True。否则返回False
27 os.path.join(path1[, path2[, ...]])  #将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
28 os.path.getatime(path)  #返回path所指向的文件或者目录的最后存取时间
29 os.path.getmtime(path)  #返回path所指向的文件或者目录的最后修改时间

复制代码

 例如:如何判断一个文件是否存在,如果存在统计她的行数;不存在则建立这个文件并输入“hello”

复制代码

>>> import os 
>>> if not(os.path.exists('test.txt')):
...     with open('test.txt', 'wt') as file:
...             file.write('hello\n')
... else:
...     lines = readlines()
...     print('the len of file is %d' %len(lines))
... 

复制代码

在Linux下实现:

songbowendeMacBook-Pro:~ lesley$ ls *.txt|grep 'test'|wc
       2       2      20

 注意:

  • read 读取整个文件
  • readline 读取下一行,使用生成器方法
  • readlines 读取整个文件到一个迭代器以供我们遍历

2. sys:来处理Python运行时配置以及资源,从而可以与当前程序之外的系统环境交互

例如:与解释器交互

我们可以用dir(sys)来查看sys模块下面的built-in函数:

常见的命令有:

复制代码

1 sys.argv           #命令行参数List,第一个元素是程序本身路径
2 sys.exit(n)        #退出程序,正常退出时exit(0)
3 sys.version        #获取Python解释程序的版本信息
4 sys.maxint         #最大的Int值
5 sys.path           #返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
6 sys.platform       #返回操作系统平台名称
7 sys.stdout.write('please:')
8 val = sys.stdin.readline()[:-1]

复制代码

补充:

3. Shutil模块:高级的文件、文件夹、压缩包处理模块

复制代码

 1 shutil.copyfileobj(force, fdst[, lenght]). #将文件内容拷贝到另一个文件中,可以部分内容
 2 shutil.copyfile(src, dst).                 # 拷贝文件
 3 shutil.copymode(src, dst)                  # 仅拷贝权限。内容、组、用户都不变
 4 shutil.copystat(src, dst)                  # 拷贝状态信息,包括:包括:mode bits, atime, mtime, flags
 5 
 6 shutil.copy(src, dst)                       # 拷贝文件和权限
 7 shutil.copy2(src, dst)                      # 拷贝文件和状态信息
 8 
 9 shutil.copytree(src, dst, symlinks=False, ignore=None) #递归的拷贝文件
10 shutil.rmtree(path[, ignore_errors[, onerror]])        # 递归的删除文件
11 shutil.move(src, dst)                                  # 递归的移动文件
12 
13 shutil.make_archive(base_name, format,...)  # 创建压缩包并且返回路径

复制代码

 

shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的,详细:

复制代码

import tarfile

# 压缩
tar = tarfile.open('your.tar','w')
tar.add('/Users/wupeiqi/PycharmProjects/bbs2.zip', arcname='bbs2.zip')
tar.add('/Users/wupeiqi/PycharmProjects/cmdb.zip', arcname='cmdb.zip')
tar.close()

# 解压
tar = tarfile.open('your.tar','r')
tar.extractall()  # 可设置解压地址
tar.close()

zipfile 压缩解压

复制代码

复制代码

import tarfile

# 压缩
tar = tarfile.open('your.tar','w')
tar.add('/Users/wupeiqi/PycharmProjects/bbs2.zip', arcname='bbs2.zip')
tar.add('/Users/wupeiqi/PycharmProjects/cmdb.zip', arcname='cmdb.zip')
tar.close()

# 解压
tar = tarfile.open('your.tar','r')
tar.extractall()  # 可设置解压地址
tar.close()

tarfile 压缩解压

复制代码

 

27. read,readline和readlines

  • read 读取整个文件
  • readline 读取下一行,使用生成器方法
  • readlines 读取整个文件到一个迭代器以供我们遍历

也可参考:https://www.cnblogs.com/pychina/p/10219772.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值