python学习

本文详细介绍了Python编程的基础知识,包括语言特点、数据结构(元组、列表、字典、集合)以及列表解析。深入探讨了多线程、函数参数传递、*args和**kwargs、装饰器的概念和用法。同时讲解了类方法、静态方法和实例方法的差异,类变量和实例变量的使用,以及Python的自省机制。文章还涵盖了单下划线和双下划线的使用规则,字符串格式化,迭代器和生成器的概念及yield的运用。此外,讨论了__new__和__init__的区别,单例模式的实现,继承中的super函数,作用域规则,以及Python的垃圾回收机制。最后,提到了闭包、引用、copy与deepcopy的差异,以及“猴子补丁”(monkey patching)的概念和其在测试中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.python语言的特点

  • Python是一种解释型语言。Python代码在运行之前不需要编译。
  • Python是动态类型语言,指的是在声明变量时,不需要说明变量的类型。
  • Python中没有访问说明符(类似C++中的public和private)。
  • 在Python语言中,函数是第一类对象(first-class objects)。这指的是它们可以被指定给变量,函数既能返回函数类型,也可以接受函数作为输入。类(class)也是第一类对象。
  • Python代码编写快,但是运行速度比编译语言通常要慢。

2.元组(tuple)、列表(list)、字典(dict)、集合(set)

元组(tuple)

  • 元组常用小括号表示,即:(),元素加逗号,是元组的标识。
  • 元组也是一种有序列表,但是元组一旦初始化之后就不可以改变,无法再添加或修改元组的元素。所以元组和字符串都是不可变的序列。
  • 定义只有一个元素的Tuple的时候,需要这样:tuple1 = (123,)
    后面要加上一个逗号,这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义。
    tuple2 = (123) #如果你这样定义你定义的将是123这个元素,而不是一个元组。
  • 元组中的元素值是不允许修改的,但我们可以对元组进行连接组合:
>>> t1 = ('wuyanl','luoting','aimeiyu')
>>> t2 = (1,2,3,4,5,6,7,8,9,0)
>>> t1
('wuyanl', 'luoting', 'aimeiyu')
>>> t2
(1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
>>> t3 = t1 + t2
>>> t3
('wuyanl', 'luoting', 'aimeiyu', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
#使用for循环遍历元组
>>> for each in t1:
...     print each
... 
wuyanl
luoting
aimeiyu
  • 元组中的元素值是不允许删除的,但可以使用del语句来删除整个元组。

元组的内置函数:
1.比较两个元组元素:cmp(tuple1,tuple2)相等的话返回0,不相等返回1;
2.计算元组的长度:len(tuple)
3.返回元组中的最大值最小值:max(tuple),min(tuple);
4.将列表转换成元组:Tuple = tuple(list)。

列表(list)

  • 列表常用方括号表示,即:[];
  • 创建一个列表,只要把用逗号分隔的不同的数据项使用方括号括起来即可。
  • list是一种有序的数据集合,可以随意的添加和删除其中的数据。Python中是划分位置的,此为有序,不是说list中的元素自己自动排好了序。
>>> list1 = ['1','2','3']
>>> list2 = ['2','3','1']
>>> list1 == list2
False
>>> list1 is list2
False
>>> list3 = ['1','2','3']
>>> list1 == list3
True
>>> list1 is list3
False
>>>

列表的一些基本操作:
1.可以使用len()函数获得列表的长度(字典中的元素的个数);
2.可以使用索引来引用列表中的元素,注意:列表中的索引是从0开始的,并且在列表中还支持负索引;
3.list是一个可以变的有序列表,因此可以往列表中添加和删除元素:在末尾添加元素用的是append()方法,在指定的位置插入元素使用的是insert()方法;
4.在列表中删除元素:删除末尾的元素使用的是pop()方法,删除指定位置的元素使用pop(i),其中i是索引下标;
5.若想替换list中的某个元素,可以直接把该元素赋值给对应的元素下标即可;
6.在一个list中可以有不同的数据类型,可以有字符串类型,整型,或者bool等,并且list的元素中也可以有另外一个list,就相当于一个循环的嵌套一样。
list的方法:

L.append(var)          #追加元素
L.insert(index,var)
L.pop(var)               #返回最后一个元素,并从list中删除之
L.remove(var)            #删除第一次出现的该元素
L.count(var)             #该元素在列表中出现的个数
L.index(var)             #该元素的位置,无则抛异常
L.extend(list6)         #追加list6,即合并list到L上,这里注意,使用extend函数可以一次在一个列表中插入任意多个值,而不必须每次只使用append()一次一值的插入
L.sort()        #排序
L.reverse()     #倒序
del L[1]        #删除指定下标的元素
del L[1:3]      #删除指定下标范围的元素

#复制list:
L1 = L      #L1为L的别名,用C来说就是指针地址相同,对L1操作即对L操作。
L1 = L[:]   #L1为L的克隆,即另一个拷贝。

示例:

>>> list1 = ['you', 'me', 'her', 'he', 1000, 100000.123]
>>> print list1
['you', 'me', 'her', 'he', 1000, 100000.123]
>>> print len(list1)
6
>>> print list1[1],list1[-1]
me 100000.123
>>> list1.append('it')
>>> list1.insert(3,'30')
>>> print list1
['you', 'me', 'her', '30', 'he', 1000, 100000.123, 'it']
>>> list1.pop()
'it'
>>> list1.pop(1)
'me'
>>> print list1
['you', 'her', '30', 'he', 1000, 100000.123]
>>> list1[2] = 'hello'
>>> print list1
['you', 'her', 'hello', 'he', 1000, 100000.123]
>>> list2 = ['wang','wu','luo',['lang','luo','zhang'],'kua']
>>> print list2
['wang', 'wu', 'luo', ['lang', 'luo', 'zhang'], 'kua']
>>> print list2[3][1]
luo
>>> list1.extend(list2)
>>> print list1
['you', 'her', 'hello', 'he', 1000, 100000.123, 'wang', 'wu', 'luo', ['lang', 'luo', 'zhang'], 'kua']

字典(dict)

  • 字典是由花括号{}来包含其数据的,花括号内包含键(key)和其对应的值(value),一对键和值成为一个项,键和值用冒号:隔开,项和项之间用逗号,隔开。
  • 字典定义了键和值之间一对一的关系,但它们是以无序的方式储存的。字典中的“值”通过键来引用。
  • 字典是可变的,可以包含任何其他类型。键是一个不可变的数据类型。
  • 空字典就是不包含任何项的字典,也可理解为空字典就是花括号内不包含任何内容,直接使用花括号{}表示。

声明:
M={k1:v1,k2:v2}
访问 M[k1]将得到v1

常用字典操作:
dic.clear()清空字典内容
dic.keys()获得键的列表
dic.values()获得值的列表
dic.copy()复制字典
dic.pop(k)删除键k的值
dic.get(k)获得键k的值
dic.update()更新成员,若成员不存在,相当于加入
dic.items()获得由键和值组成的列表

#字典操作
>>> m = {'a':1,'b':2,'c':3}  
>>> print(m)
{'a': 1, 'c': 3, 'b': 2}
#读取某一个,通过key
>>> print(m['a'])  
1
#读取某一个,通过get方法 
>>> print(m.get('b'))
2
#复制字典
>>> m2 = m.copy() 
>>> print(m2)
{'a': 1, 'c': 3, 'b': 2}
#获取所有键的列表
>>> print( m.keys() ) 
['a', 'c', 'b']
#获取所有值的列表
>>> print( m.values() ) 
[1, 3, 2]
#获取所有键值对元组组成的列表 
>>> print( m.items() ) 
[('a', 1), ('c', 3), ('b', 2)]
#更新成员,当对应键值不存在时,相当于加入
>>> m.update({'d':4})  
>>> print(m)
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
#删除某个成员,会把对应的值输出后再删除
>>> m.pop('a')
1
>>> print(m)
{'c': 3, 'b': 2, 'd': 4}
#遍历
>>> for key in m.keys():
...     if(key=='d'):
...         del m[key]   #删除键对应的值
...     print(m)
... 
{'c': 3, 'b': 2, 'd': 4}
{'c': 3, 'b': 2, 'd': 4}
{'c': 3, 'b': 2}
#清空字典内容
>>> m.clear()
>>> print(m)
{}
#删除字典
>>> del m
>>> print(m)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'm' is not defined

集合(set)
集合(set)是一个无序不重复元素集, 基本功能包括关系测试和消除重复元素。
方法(所有的集合方法):
s.issubset(t) #如果s是t的子集,返回True,否则返回False
s.issuperset(t) #如果s是t的超集,返回True,否则返回False
s.union(t) #返回一个新集合, 该集合是s和t的并集
s.intersection(t) #返回一个新集合, 该集合是s和t的交集
s.difference(t) #返回一个新集合, 该集合是s的成员, 但不是t的成员, 即返回s不同于t的元素
s.symmetric_defference(t) #返回所有s和t独有的(非共同拥有)元素集合
s.copy() #返回一个s的浅拷贝, 效率比工厂要好
方法(仅适用于可变集合):以下方法参数必须是可哈希的
s.update(t) #用t中的元素 修改s,即s现在包含s或t的成员
s.intersection_update(t) #s中的成员是共同属于s和t的元素
s.difference_update(t) #s中的成员是属于s但不包含在t中的元素
s.symmetric_difference_update(t) #s中的成员更新为那些包含在s或t中,但不是s和t共有的元素
s.symmetric_difference() #返回一个新集合,包含所有只在其中一个集合中出现的元素。
s.add(obj) #在集合s中添加对象obj
s.remove(obj) #从集合s中删除对象obj,如果obj不是集合s中的元素(obj not in s),将引发keyError错误
s.discard(obj) #如果obj是集合s中的元素,从集合s中删除对象obj
s.pop() #删除集合s中得任意一个对象,并返回它
s.clear() #删除集合s中的所有元素
集合有并集,交集,求差操作
并集:intersection() 方法返回一个新集合,包含在两个集合中同时出现的所有元素。
交集:union() 方法返回一个新集合,包含在两个 集合中出现的元素。
差集:difference() 方法返回的新集合中,包含所有在 集合A出现但未在集合B中的元素。

#定义一个集合
>>> set1 = {1, 2, 3, 4, 5}
# 或者使用 set 函数
>>> list1 = [6, 7, 7, 8, 8, 9]
>>> set2 = set(list1)
>>> print(set1)
set([1, 2, 3, 4, 5])
>>> print(set2)
set([8, 9, 6, 7])  #去掉重复内容,而且是无序的
# 添加新元素
>>> set2.add(10)
>>> print(set2)
set([8, 9, 10, 6, 7])
>>> set3 = frozenset(list1)  #固定集合
>>> set3.add(10)     # 固定集合不能添加元素
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
>>> set1.issubset(set2)
False
>>> set1.issuperset(set2)
False
>>> set4 = set1.union(set2)
>>> print(set4)
set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
>>> print(set1)
set([1, 2, 3, 4, 5])
#删除元素
>>> print(set2)
set([8, 9, 10, 6, 7])
>>> set2.discard(5)
>>>                     # 当元素不存在时,不会引发异常
>>> set2.discard(6)
>>> print(set2)
set([8, 9, 10, 7])
# 与discard的区别在于,如果没有要删除的元素,remove会引发一个异常
>>> set2.remove(6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 6
# 因为set是无序的,所以pop会随机的从set中删除一个元素
>>> set2.pop()
8
>>> print(set2)
set([9, 10, 7])

3. 列表解析

阅读下面的代码,写出A0,A1至An的最终值。

A0 = dict(zip(('a','b','c','d','e'),(1,2,3,4,5)))
A1 = range(10)
A2 = [i for i in A1 if i in A0]
A3 = [A0[s] for s in A0]
A4 = [i for i in A1 if i in A3]
A5 = {i:i*i for i in A1}
A6 = [[i,i*i] for i in A1]

答案

A0 = {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4}
A1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
A2 = []
A3 = [1, 3, 2, 5, 4]
A4 = [1, 2, 3, 4, 5]
A5 = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]

4.Python和多线程(multi-threading)。这是个好主意码?列举一些让Python代码以并行方式运行的方法。

Python并不支持真正意义上的多线程。Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意。
全局解释器锁(GIL)是一种互斥锁,即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程。由程序语言解析线程持有,用于避免代码共享可能导致的线程安全问题。每一个解释器进程中都会含有一个GIL。
每次执行python程序,都会产生一个独立的进程。在一个python的进程内,不仅有主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内。
- 所有数据都是共享的
其中代码作为一种数据也是被所有线程共享的
- 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

5. 函数参数传递

所有的变量都可以理解是内存中一个对象的“引用”。
类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象。

a = 1
def fun(a):
    a = 2
fun(a)
print a            # 1

a = []
def fun(a):
    a.append(1)
fun(a)
print a          # [1]

当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用没有关系。所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,跟外面的引用没有关系。而第二个例子里函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行修改。

6.*args,**kwargs两个参数是什么意思?为什么要使用它们?

答案:如果我们不确定要往函数中传入多少个参数,或者我们想往函数中以列表和元组的形式传参数时,那就使要用*args;如果我们不知道要往函数中传入多少个关键词参数,或者想传入字典的值作为关键词参数时,那就要使用**kwargs。
示例:

#传递任意数量的参数
>>> def print_everything(*args):
...     for count,thing in enumerate(args):
...         print '{0}. {1}'.format(count,thing)
... 
>>> print_everything('apple','banana','cabbage')
0. apple
1. banana
2. cabbage
#使用没有事先定义的参数名
>>> 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前面
>>> def f(*args,**kwargs):
...     print args, kwargs
... 
>>> l = [1,2,3]
>>> t = (4,5,6)
>>> d = {'a':7,'b':8,'c':9}
>>> f()
() {}
>>> f(1,2,3)
(1, 2, 3) {}
>>> f(1,2,3,"groovy") 
(1, 2, 3, 'groovy') {}
>>> f(a=1,b=2,c=3)
() {'a': 1, 'c': 3, 'b': 2}
>>> f(a=1,b=2,c=3,zzz="hi")
() {'a': 1, 'c': 3, 'b': 2, 'zzz': 'hi'}
>>> f(1,2,3,a=1,b=2,c=3)
(1, 2, 3) {'a': 1, 'c': 3, 'b': 2}
>>> f(*l,**d)
(1, 2, 3) {'a': 7, 'c': 9, 'b': 8}
>>> f(*t,**d)
(4, 5, 6) {'a': 7, 'c': 9, 'b': 8}
>>> f(q="winning",**d)
() {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
>>> f(1,2,*t,q="winning",**d)
(1, 2, 4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}

>>> def f2(arg1,arg2,*args,**kwargs): 
...     print arg1,arg2, args, kwargs
... 
>>> f2(1,2,3)
1 2 (3,) {}
>>> f2(1,2,3,"groovy")
1 2 (3, 'groovy') {}
>>> f2(arg1=1,arg2=2,c=3)
1 2 () {'c': 3}
>>> f2(arg1=1,arg2=2,c=3,zzz="hi")
1 2 () {'c': 3, 'zzz': 'hi'}
>>> f2(1,2,3,a=1,b=2,c=3)
1 2 (3,) {'a': 1, 'c': 3, 'b': 2}
>>> f2(*l,**d)
1 2 (3,) {'a': 7, 'c': 9, 'b': 8}
>>> f2(*t,**d)
4 5 (6,) {'a': 7, 'c': 9, 'b': 8}
>>> f2(1,2,*t) 
1 2 (4, 5, 6) {}
>>> f2(1,1,q="winning",**d)
1 1 () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
>>> f2(1,2,*t,q="winning",**d)
1 2 (4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}

7.装饰器(decorator)

答:装饰器是一种特殊的函数,要么接受函数作为输入参数,并返回一个函数,要么接受一个类作为输入参数,并返回一个类。@标记是语法糖(syntactic sugar),可以让你以简单易读得方式装饰目标对象。

# 装饰器就是把其他函数作为参数的函数
def my_shiny_new_decorator(a_function_to_decorate):

    # 在函数里面,装饰器在运行中定义函数: 包装.
    # 这个函数将被包装在原始函数的外面,所以可以在原始函数之前和之后执行其他代码..
    def the_wrapper_around_the_original_function():

        # 把要在原始函数被调用前的代码放在这里
        print "Before the function runs"

        # 调用原始函数(用括号)
        a_function_to_decorate()

        # 把要在原始函数调用后的代码放在这里
        print "After the function runs"

    # 在这里"a_function_to_decorate" 函数永远不会被执行
    # 在这里返回刚才包装过的函数
    # 在包装函数里包含要在原始函数前后执行的代码.
    return the_wrapper_around_the_original_function

@my_shiny_new_decorator
def another_stand_alone_function():
    print "Leave me alone"

another_stand_alone_function()
#输出:
#Before the function runs
#Leave me alone
#After the function runs

@decorator就是下面的简写:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

另一个示例:

def bread(func):
    def wrapper():
        print "</''''''\>"
        func()
        print "<\______/>"
    return wrapper

def ingredients(func):
    def wrapper():
        print "#tomatoes#"
        func()
        print "~salad~"
    return wrapper

def sandwich(food="--ham--"):
    print food

sandwich()
#outputs: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

用Python装饰器语法糖:

@bread
@ingredients
def sandwich(food="--ham--"):
    print food

sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

更多示例

8.静态方法(staticmethod),类方法(classmethod)和实例方法

def foo(x):
    print "executing foo(%s)"%(x)

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

a=A()

函数参数里面的self和cls是对类或者实例的绑定,对于一般的函数来说,我们可以这么调用foo(x),这个函数就是最常用的,它的工作跟任何东西(类,实例)无关。
对于实例方法,在类里每次定义方法的时候都需要绑定这个实例,就是foo(self, x),为什么要这么做呢?因为实例方法的调用离不开实例,需要把实例自己传给函数,调用的时候是这样的a.foo(x)(其实是foo(a, x)).
对于类方法是一样的,只不过它传递的是类而不是实例,A.class_foo(x).注意这里的self和cls可以替换别的参数,但是python的约定是这俩,还是不要改的好。
对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用a.static_foo(x)或者A.static_foo(x)来调用.

\实例方法类方法静态方法
a = A()a.foo(x)a.class_foo(x)a.static_foo(x)
A不可用A.class_foo(x)A.static_foo(x)

9.类变量和实例变量

>>> class Person:
...     name="aaa"
... 
>>> p1=Person()
>>> p2=Person()
>>> p1.name="bbb"
>>> print p1.name
bbb
>>> print p2.name
aaa
>>> print Person.name
aaa

>>> class Person:
...     name=[]
... 
>>> p1=Person()
>>> p2=Person()
>>> p1.name.append(1)
>>> print p1.name
[1]
>>> print p2.name
[1]
>>> print Person.name
[1]

类变量就是供类使用的变量,实例变量就是供实例使用的.
这里p1.name=”bbb”是实例调用了类变量,这其实就是函数传参的问题,p1.name一开始是指向的类变量name=”aaa”,但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了。

10. Python自省

自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对象的类型.比如type(),dir(),getattr(),hasattr(),isinstance().

11. Python中单下划线和双下划线

>>> class MyClass():
...     def __init__(self):
...             self.__superprivate = "Hello"
...             self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

Python 用下划线作为变量前缀和后缀指定特殊变量。

_xxx 不能用’from module import *’导入,解析器用_classname__xxx来代替这个名字,以区别和其他类相同的命名.
__xxx__系统定义名字,用来区别其他用户自定义的命名,以防冲突
__xxx 类中的私有变量名

核心风格:避免用下划线作为变量名的开始。

因为下划线对解释器有特殊的意义,而且是内建标识符所使用的符号,建议程序员避免用下划线作为变量名的开始。一般来讲,变量名xxx被看作是“私有 的”,在模块或类外不可以使用。当变量是私有的时候,用_xxx来表示变量是很好的习惯。因为变量名__xxx_对Python 来说有特殊含义,对于普通的变量应当避免这种命名风格。

“单下划线” 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;
“双下划线” 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。

以单下划线开头(foo)的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用“from xxx import *”而导入;以双下划线开头的(__foo)代表类的私有成员;以双下划线开头和结尾的(__foo_)代表python里特殊方法专用的标识,如__init__()代表类的构造函数

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

%无法同时传递一个变量和元组。

"hi there %s" % name

如果name恰好是(1,2,3),它将会抛出一个TypeError异常.为了保证它总是正确的,你必须这样做:

"hi there %s" % (name,)   # 提供一个单元素的数组而不是一个参数

format通过{}和:来代替%。
“映射”示例
通过位置
字符串的format函数可以接受不限个参数,位置可以不按顺序,可以不用或者用多次

>>> '{0},{1}'.format('kzc',18)
'kzc,18'
>>> '{},{}'.format('kzc',18)
'kzc,18'
>>> '{1},{0},{1}'.format('kzc',18)
'18,kzc,18'

通过关键字参数

>>> '{name},{age}'.format(age=18,name='kzc')
'kzc,18'

通过对象属性

>>> class Names():
...     name1='Kevin'
...     name2='Tom'
... 
>>> 'hello {names.name1},I am {names.name2}'.format(names=Names)
'hello Kevin,I am Tom'

通过下标

>>> names=['Kevin','Tom']
>>> 'hello {names[0]},I am {names[1]}'.format(names=names)
'hello Kevin,I am Tom'
>>> 'hello {0[0]},I am {0[1]}'.format(names)
'hello Kevin,I am Tom'

通过字典的key

>>> names={'name1':'Kevin','name2':'Tom'}
>>> 'hello {names[name1]},I am {names[name2]}'.format(names=names)
'hello Kevin,I am Tom'

注意:访问字典的key,不用引号的
格式限定符
它有着丰富的的“格式限定符”(语法是{}中带:号),比如:
填充与对齐
填充常跟对齐一起使用
^、<、>分别是居中、左对齐、右对齐,后面带宽度
:号后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充
比如:

#右对齐 (默认, 宽度为8)
>>> '{:>8}'.format('189')
'     189'
#数字补零 (填充左边, 宽度为8)
>>> '{:0>8}'.format('189')
'00000189'
#数字补a (填充左边, 宽度为8)
>>> '{:a>8}'.format('189')
'aaaaa189'
#数字补a (填充右边, 宽度为8)
>>> '{:a<8}'.format('189')
'189aaaaa'
#数字补a (填充两边, 宽度为8)
>>> '{:a^8}'.format('189')
'aa189aaa'

精度与类型f
精度常跟类型f一起使用

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

其中.2表示长度为2的精度,f表示float类型。.0表示不带小数
其他类型
主要就是百分比,指数和进制了,b、d、o、x分别是二进制、十进制、八进制、十六进制。

>>> '{:.2%}'.format(0.375)
'37.50%'
>>> '{:.1%}'.format(0.375)
'37.5%'
#指数记法
>>> '{:.2e}'.format(1000000000)
'1.00e+09'
>>> '{:b}'.format(17)
'10001'
>>> '{:d}'.format(17)
'17'
>>> '{:o}'.format(17)
'21'
>>> '{:x}'.format(17)
'11'

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

这里写代>>> '{:,}'.format(1234567890)
'1,234,567,890'码片

format中用两个大括号来转义

>>> '{{ hello {0} }}'.format('Kevin')
'{ hello Kevin }'

13.迭代器、生成器

Iterables
当创建了一个列表,可以一个一个的读取它的每一项,这叫做iteration:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

Mylist是可迭代的.当用列表推导式的时候,就创建了一个列表,而这个列表也是可迭代的:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

lists,strings,files…这些可迭代的对象可以随意的读取,所以非常方便易用,但是必须把它们的值放到内存里,当它们有很多值时就会消耗太多的内存.
Generators
生成器也是迭代器的一种,但是只能迭代它们一次.原因很简单,因为它们不是全部存在内存里,它们只在要调用的时候在内存里生成:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

生成器和迭代器的区别就是用()代替[],还有不能用for i in mygenerator第二次调用生成器:首先计算0,然后会在内存里丢掉0去计算1,直到计算完4.

14.Yield的用法

Yield的用法和关键字return差不多,下面的函数将会返回一个生成器:

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # 创建生成器
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

在这里这个例子好像没什么用,不过当函数要返回一个非常大的集合并且希望只读一次的话,那么它就非常的方便了.
要理解Yield必须先理解,当调用函数的时候,函数里的代码并没有运行.函数仅仅返回生成器对象,这就是它最微妙的地方。每当for语句迭代生成器的时候,代码才会运转.

当for语句第一次调用函数里返回的生成器对象,函数里的代码就开始运作,直到碰到yield,然后会返回本次循环的第一个返回值.所以下一次调用也将运行一次循环然后返回下一个值,直到没有值可以返回.
一旦函数运行并没有碰到yeild语句就认为生成器已经为空了.原因有可能是循环结束或者没有满足if/else之类的.

15. __new__和__init__的区别

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

16. 单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。
实现单例模式的几种方式

  • 使用模块
    Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
#mysingleton.py
class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()
#要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对象
from mysingleton import singleton
singleton.foo()
  • 使用装饰器
def Singleton(cls):
    _instance = {}

    def _singleton(*args, **kargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kargs)
        return _instance[cls]

    return _singleton

@Singleton
class A(object):
    a = 1

    def __init__(self, x=0):
        self.x = x
a1 = A(2)
a2 = A(3)
  • 使用__new__方法
    当我们实例化一个对象时,是先执行了类的__new__方法(没写时,默认调用object.__new__),实例化对象;然后再执行类的__init__方法,对这个对象进行初始化,所以可以基于这个,实现单例模式
class Singleton(object):
    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = object.__new__(cls)  
        return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)
#output
(<__main__.Singleton object at 0x7f7c8441da10>, <__main__.Singleton object at 0x7f7c8441da10>)
  • 使用metaclass方法

1.类由type创建,创建类时,type的__init__方法自动执行,类() 执行type的__call__方法(类的__new__方法,类的__init__方法)
2.对象由类创建,创建对象时,类的__init__方法自动执行,对象()执行类的 __call__ 方法

class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        pass

obj = Foo()
# 执行type的 __call__ 方法,调用 Foo类(是type的对象)的 __new__方法,用于创建对象,然后调用 Foo类(是type的对象)的 __init__方法,用于对对象初始化。

obj()    # 执行Foo的 __call__ 方法

17.继承和Python中super函数的用法

class A(object):
    def go(self):
        print "go A go!"
    def stop(self):
        print "stop A stop!"
    def pause(self):
        raise Exception("Not Implemented")

class B(A):
    def go(self):
        super(B, self).go()
        print "go B go!"

class C(A):
    def go(self):
        super(C, self).go()
        print "go C go!"
    def stop(self):
        super(C, self).stop()
        print "stop C stop!"

class D(B,C):
    def go(self):
        super(D, self).go()
        print "go D go!"
    def stop(self):
        super(D, self).stop()
        print "stop D stop!"
    def pause(self):
        print "wait D wait!"

class E(B,C): pass

a = A()
b = B()
c = C()
d = D()
e = E()
# 说明下列代码的输出结果

a.go()
b.go()
c.go()
d.go()
e.go()

a.stop()
b.stop()
c.stop()
d.stop()
e.stop()

a.pause()
b.pause()
c.pause()
d.pause()
e.pause()

答案

输出结果以注释的形式表示:

a.go()
# go A go!

b.go()
# go A go!
# go B go!

c.go()
# go A go!
# go C go!

d.go()
# go A go!
# go C go!
# go B go!
# go D go!

e.go()
# go A go!
# go C go!
# go B go!

a.stop()
# stop A stop!

b.stop()
# stop A stop!

c.stop()
# stop A stop!
# stop C stop!

d.stop()
# stop A stop!
# stop C stop!
# stop D stop!

e.stop()
# stop A stop!

a.pause()
# ... Exception: Not Implemented

b.pause()
# ... Exception: Not Implemented

c.pause()
# ... Exception: Not Implemented

d.pause()
# wait D wait!

e.pause()
# ...Exception: Not Implemented

18. Python中的作用域

Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。
当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:
本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)

19.简要描述Python的垃圾回收机制(garbage collection)

答:

  • Python在内存中存储了每个对象的引用计数(reference count),用来跟踪和回收垃圾。如果计数值变成0,那么相应的对象就会小时,分配给该对象的内存就会释放出来用作它用。
  • 偶尔也会出现引用循环(reference cycle)。垃圾回收器会定时寻找这个循环,并将其回收。举个例子,假设有两个对象o1和o2,而且符合o1.x == o2和o2.x == o1这两个条件。如果o1和o2没有其他代码引用,那么它们就不应该继续存在。但它们的引用计数都是1。
  • Python中通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率,使用了某些启发式算法(heuristics)来加速垃圾回收。例如,越晚创建的对象更有可能被回收。对象被创建之后,垃圾回收器会分配它们所属的代(generation)。每个对象都会被分配一个代,而被分配更年轻代的对象是优先被处理的。
  • 在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题。基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。

20. 闭包

答:闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
当一个内嵌函数引用其外部作作用域的变量,就会得到一个闭包。
创建一个闭包必须满足以下几点:

  • 必须有一个内嵌函数
  • 内嵌函数必须引用外部函数中的变量
  • 外部函数的返回值必须是内嵌函数

21. python里的引用、copy、deepcopy的区别

copy.copy 浅拷贝 仅仅拷贝父对象,不会拷贝对象的内部的子对象。
copy.deepcopy 深拷贝 拷贝对象及其子对象

>>> import copy
>>> a = [1,2,3,4,['a','b']]  #原始对象
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> e = a[:] 
>>> a.append(5)    #修改对象a
>>> a[4].append('c') #修改对象a中的['a', 'b']数组子对象
>>> print 'a = ',a
a =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> print 'b = ',b
b =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> print 'c = ',c
c =  [1, 2, 3, 4, ['a', 'b', 'c']]
>>> print 'd = ',d
d =  [1, 2, 3, 4, ['a', 'b']]
>>> print 'e = ',e
e =  [1, 2, 3, 4, ['a', 'b', 'c']]

分析:b是a的赋值,传的是对象的一个引用。两者指向的对象相同,打印结果必定相同。
c仅仅拷贝了a的中的父对象。对于a中嵌套的子对象列表并没有拷贝,所以c中的1,2,3,4是拷贝得到的副本,属于自己的,但嵌套的子对象列表还是a原来的。
d因为是深拷贝,所以子对象父对象都是拷贝产生的新的副本。全然不受原来a的影响。
e分片操作的效果等价于浅拷贝,结果和c同样。

def ggg():
    l = [1, 2, 3]           #局部变量
    print 'local var ', l

    return l

def fff(l):
    l.append(100)
    l[2].append('c')

    print "global var ", l  #全局变量

    return l

a = ggg()
print a 
a.append(10)
print a
ggg()

print '\n\n'

a = [1, 2, ['a', 'b'], 3]
b = fff(a)
print a
b.append(1000)
print b
print a

执行结果:

local var  [1, 2, 3]
[1, 2, 3]
[1, 2, 3, 10]
local var  [1, 2, 3]


global var  [1, 2, ['a', 'b', 'c'], 3, 100]
[1, 2, ['a', 'b', 'c'], 3, 100]
[1, 2, ['a', 'b', 'c'], 3, 100, 1000]
[1, 2, ['a', 'b', 'c'], 3, 100, 1000]

分析:
a接受函数ggg返回的局部变量。但改动a后第二次调用函数ggg,ggg内的局部变量l并没有受到影响,可见,ggg返回局部变量时是拷贝传递,就像C++中的一样:函数不可能返回局部变量的引用,由于局部变量保存在函数的栈空间中,函数调用结束后,栈被销毁。不能够对局部变量进行引用了。

将a传递给函数fff后。a被改变了。说明进行了引用传递;函数fff的返回值赋给b。对b的改动也会影响到a,说明返回值也是进行的引用传递(只是这里返回的可不是局部变量了)。

22.read,readline和readlines

假设a.txt的内容如下所示:

Hello
Welcome
What is the fuck...
  • read()方法
    read()方法从文件当前位置起读取size个字节,若无参数size,则表示读取整个文件至文件结束为止,将文件内容放到一个字符串变量中,返回为字符串对象
f = open("a.txt")
lines = f.read()
print lines
print(type(lines))
f.close()

输出:

Hello
Welcome
What is the fuck...
<type 'str'> #字符串类型
  • readline()方法
    每次读出一行内容,使用生成器方法。所以,读取时占用内存小,比较适合大文件,该方法返回一个字符串对象。
f = open("a.txt")
line = f.readline()
print(type(line))
while line:
 print line,
 line = f.readline()
f.close()

输出:

<type 'str'>
Hello
Welcome
What is the fuck...
  • readlines()方法
    读取整个文件所有行,保存在一个列表(list)变量中,每行作为一个元素,但读取大文件会比较占内存。
f = open("a.txt")
lines = f.readlines()
print(type(lines))
for line in lines:
 print line,
f.close()

输出:

<type 'list'>
Hello
Welcome
What is the fuck...

23 .“猴子补丁”(monkey patching)指的是什么?这种做法好吗?

答案:
“猴子补丁”就是指,在函数或对象已经定义之后,再去改变它们的行为。
举个例子:

import datetime
datetime.datetime.now = lambda: datetime.datetime(2012, 12, 12)

大部分情况下,这是种很不好的做法 - 因为函数在代码库中的行为最好是都保持一致。打“猴子补丁”的原因可能是为了测试。mock包对实现这个目的很有帮助。
MOCK
Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。mock基本使用介绍

24.代码示例分析

下面代码会输出什么:

def f(x,l=[]):
    for i in range(x):
        l.append(i*i)
    print l

f(2)
f(3,[3,2,1])
f(3)

答案:

[0, 1]
[3, 2, 1, 0, 1, 4]
[0, 1, 0, 1, 4]

答案分析:
第一个函数调用十分明显,for循环先后将0和1添加至了空列表l中。l是变量的名字,指向内存中存储的一个列表。
第二个函数调用在一块新的内存中创建了新的列表。l这时指向了新生成的列表。之后再往新列表中添加0、1、2和4。
第三个函数调用时,它使用了之前内存地址中存储的旧列表。这就是为什么它的前两个元素是0和1。

参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值