Python常见问题总结


如何在Python中拷贝一个对象

  1. 浅拷贝 :

    使用copy.copy,它可以进行对象的浅拷贝(shallow copy),它复制了对象,但对于对象中的元素,依然使用引用(换句话说修改拷贝对象元素,则被拷贝对象元素也被修改)

  2. 深拷贝 :

    使用copy.deepcopy,它可以进行深拷贝,不仅拷贝了对象,同时也拷贝了对象中的元素,获得了全新的对象,与被拷贝对象完全独立,但这需要牺牲一定的时间和空间。

  3. 特殊拷贝:

    如要复制列表L,使用list(L),要复制一个字典d,使用dict(d),要复制一个集合s,使用set(s)。
    总结一下的话:如果你要复制某个对象object, 它属于python内建的类型type,那么你可以使用type(object)来 获得一个拷贝。

copy()与deepcopy():
deepcopy()深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。
copy()浅复制并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。这就和我们寻常意义上的复制有所不同了。

Xrange和range的区别

xrange 用法与 range 完全相同,所不同的是生成的不是一个list对象,而是一个生成器。要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间。xrange则不会直接生成一个list,而是每次调用返回其中的一个值,如果不需要直接返回一个列表,则用xrange。

python中的self

Python的类中的方法和普通的函数有一个很明显的区别,在类中的方法必须有个额外的第一个参数(self),但在调用这个方法的时候不必为这个参数赋值

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s: %s' % (self.name, self.score))

self在Python里不是关键字。self代表当前对象的地址。self能避免非限定调用造成的全局变量。
例子说明:创建了一个类MyClass,实例化MyClass得到了MyObject这个对象,然后调用这个对象的方法MyObject.method(arg1,arg2)
,这个过程中,Python会自动转为Myclass.mehod(MyObject,arg1,arg2)
这就是Python的self的原理了。即使你的类的方法不需要任何参数,但还是得给这个方法定义一个self参数,虽然我们在实例化调用的时候不用理会这个参数不用给它赋值。

class Python:
   def selfDemo(self):
   print 'Python,why self?'
p = Python()
p.selfDemo()

pickling和unpickling

Pickle模块读入任何Python对象,将它们转换成字符串,然后使用dump函数将其转储到一个文件中——这个过程叫做pickling。反之从存储的字符串文件中提取原始Python对象的过程,叫做unpickling。

Python程序的执行原理

Python解释器先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后虚拟机一条一条执行字节码指令,从而完成程序的执行。
字节码在Python虚拟机程序里对应的是PyCodeObject对象。”.pyc”文件是字节码在磁盘上的表现形式。
PyCodeObject对象的创建时机是模块加载的时候,即import。
Python代码的编译结果就是PyCodeObject对象。

typedef struct {
    PyObject_HEAD
    int co_argcount;        /* 位置参数个数 */
    int co_nlocals;         /* 局部变量个数 */
    int co_stacksize;       /* 栈大小 */
    int co_flags;   
    PyObject *co_code;      /* 字节码指令序列 */
    PyObject *co_consts;    /* 所有常量集合 */
    PyObject *co_names;     /* 所有符号名称集合 */
    PyObject *co_varnames;  /* 局部变量名称集合 */
    PyObject *co_freevars;  /* 闭包用的的变量名集合 */
    PyObject *co_cellvars;  /* 内部嵌套函数引用的变量名集合 */
    /* The rest doesn’t count for hash/cmp */
    PyObject *co_filename;  /* 代码所在文件名 */
    PyObject *co_name;      /* 模块名|函数名|类名 */
    int co_firstlineno;     /* 代码块在文件中的起始行号 */
    PyObject *co_lnotab;    /* 字节码指令和行号的对应关系 */
    void *co_zombieframe;   /* for optimization only (see frameobject.c) */
} PyCodeObject;

在 Python中,类、函数、module 都对应着一个独立的命名空间。而一个独立的命名空间会对应一个 PyCodeObject 对象。
命名空间的意义,就是用来确定一个变量符号到底对应什么对象。命名空间可以一个套一个地形成一条命名空间链,Python虚拟机在执行的过程中,会有很大一部分时间消耗在从这条命名空间链中确定一个符号所对应的对象是什么。
命名空间是由一个 dict 对象实现的,它维护了(name,obj)这样的关联关系。

[demo.py]
import foo
a = [1, 'python']
a = 'a string'
def func():
a = 1
b = 257
print(a + b)
print(a)
if __name__ == '__main__':
func()
foo.add(1, 2)

上面的demo.py文件编译后会生成**两个**PyCodeObject(一个对应整个demo.py文件模块,一个对应func),只是在demo.py这个module层的 PyCodeObject中通过一个变量符号func嵌套了一个函数的 PyCodeObject。
import foo这行代码会在demo.py这个模块的命名空间中创建一个新的变量名 foo,foo将绑定到一个 PyCodeObject 对象,也就是foo.py的编译结果。

[demo.py]
class A:
     pass
 def f():
     pass
 a = A()
 f()

Python编译器对demo.py源码编译之后,会创建 3个 PyCodeObject对象:第一个是对应整个demo.py文件代表的Code Block,第二个是对应Class A代表的Code Block,第三个是对应f代表的Code Block。

python内存管理机

  1. 引用计数
    python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数,当对象被创建或被引用时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为0时,它被垃圾回收
    引用计数增加:
    1.对象被创建:x=4
    2.另外的别人被创建:y=x
    3.被作为参数传递给函数:foo(x)
    4.作为容器对象的一个元素:a=[1,x,’33’]
    引用计数减少:
    1.一个本地引用离开了它的作用域。比如上面的foo(x)函数结束时,x指向的对象引用减1。
    2.对象的别名被显式的销毁:del x ;或者del y
    3.对象的一个别名被赋值给其他对象:x=789
    4.对象从一个窗口对象中移除:myList.remove(x)
    5.窗口对象本身被销毁:del myList,或者窗口对象本身离开了作用域。
  2. 垃圾回收
    1、当内存中有不再使用的部分时,垃圾收集器就会把他们清理掉。它会去检查那些引用计数为0的对象,然后清除其在内存的空间。当然除了引用计数为0的会被清除,还有一种情况也会被垃圾收集器清掉:当两个对象相互引用时,他们本身其他的引用已经为0了。
    2、垃圾回收机制还有一个循环垃圾回收器, 确保释放循环引用对象(a引用b, b引用a, 导致其引用计数永远不为0)。
    在Python中,许多时候申请的内存都是小块的内存,这些小块内存在申请后,很快又会被释放,由于这些内存的申请并不是为了创建对象,所以并没有对象一级的内存池机制。这就意味着Python在运行期间会大量地执行malloc和free的操作,频繁地在用户态和核心态之间进行切换,这将严重影响Python的执行效率。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
  3. 内存池机制
    Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
    Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的 malloc。另外Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。

Python装饰器

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

上述是一个decorator,接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:相当于执行了now=log(now) )

@log
def now():
    print('2015-3-25')

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2015-3-25

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')
执行结果如下:
>>> now()
execute now():
2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)

上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的_name_已经从原来的’now’变成了’wrapper’

>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的name等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.name = func.name这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

或者针对带参数的decorator:

import functools
def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

Python 迭代对象、迭代器、生成器

https://www.zhihu.com/question/20829330

python列表排序

Python list内置sort()方法用来排序,也可以用python内置的全局sorted()方法来对可迭代的序列排序生成新的序列。
1.sorted(iterable,key=None,reverse=False),返回新的列表,对所有可迭代的对象均有效
2.sort(key=None,reverse=False) 就地改变列表 reverse:True反序;False 正序
(key参数来指定一个函数,此函数将在每个元素比较前被调用,reverse 参数,用来指定是否降序)

*args,**kwargs参数

  • 当不确定你的函数里将要传递多少参数时,你可以使用位置参数包裹*args,它可以传递任意数量的位置参数
def print_everything(*args):
    for count, thing in enumerate(args):
        print('{0}. {1}'.format(count, thing))
print_everything('apple', 'banana', 'cabbage')
'''
 - apple
 - banana
 - cabbage
'''
  • 同理,关键字参数包裹**kwargs允许你使用没有事先定义的关键字参数
def table_things(**kwargs):
    for name, value in kwargs.items():
        print('{0} = {1}'.format(name, value))
table_things(apple = 'fruit', cabbage = 'vegetable')
'''
apple = fruit
cabbage = vegetable
'''
  • 位置参数、关键字参数、位置参数包裹、关键字参数包裹的混合使用
    位置参数首先获得参数值,然后其他所有的参数都传递给*args和**kwargs。位置参数在参数的最前端,*args和**kwargs可以同时在传参时使用,但是*args必须在**kwargs前面。
func(positional_args, keyword_args, *tuple_nonkw_args, **dict_kw_args)
'''
positional_args:位置参数
keyword_args:关键字参数
*tuple_nonkw_args:非关键字不定长参数
**dict_kw_args:关键字不定长参数
'''
  • 调用函数时,我们也可以使用 * 和 ** 语法
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

*可以传递列表(或者元组)的每一项并把它们解包,这必须与它们在函数里的参数相吻合。

Python join()方法

用于将序列中的元素以指定的字符连接生成一个新的字符串
‘sep’.join(seq)
sep:分隔符。可以为空
seq:要连接的元素序列、字符串、元组、字典

一行Python代码打印出9*9 乘法表

print "\n".join("\t".join(["%s*%s=%s" %(x,y,x*y) for y in range(1, x+1)]) for x in range(1, 10))

Python的名称空间和作用域

  • 名称空间:
    Python 的名称(Name)是对象的一个标识(Identifier)。在 Python 里面一切皆对象,名称用来引用对象。
    名称空间是名称到对象的映射
    在 Python 中,名称空间采用字典来实现。Python 的名称空间包括:
    内置名称空间,例如,内置名称空间包含 Python 的内置函数,如,abs()
    模块名称空间,全局名称空间,在模块内定义的名称
    局部名称空间,例如,在函数(function)或者类(class)被调用时,其内部包含的名称
    不同的名称空间内的名称不会相互冲突,即是它们采用相同的名称。这也正是名称空间的作用
    内置名称空间在 Python 解释器启动时就创建了,直到 Python 解释器退出时内置名称空间才失效。这使得我们可以在程序的任何位置使用内置名称空间内的名称,例如,id(),print()等函数。
    模块名称空间当模块被引用时创建,直到 Python 解释器退出时模块名称空间才失效。
    函数名称空间在函数被调用时创建,函数返回后失效。
  • 作用域:
    Python 的作用域(scope)决定了我们在程序中能否直接使用名称空间中的名称,直接访问的意思是指不需要在名称前添加名称空间的前缀。对于 Python 来说,至少存在以下三类的作用域。
    函数作用域,包括了函数内的局部名称
    模块作用域,包括了模块内的全局名称
    内置作用域,包括了内置名称
    当在函数内部使用一个名称时,为了查找出该名称所引用的对象,Python 解释器先在函数名称空间查找,接着在模块名称空间查找,最后在内置名称空间查找,直到寻找到该名称为止。(作用域链)
    当函数 A 处于函数 B 的内部时,函数 A 的作用域处于函数 B 作用域之内。

Python中的闭包

如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数。
在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

li = [lambda :x for x in range(10)]

li = [lambda :x for x in range(10)]
       res = li[0]()
   print(res)
 #输出:9

li = [lambda :x for x in range(10)]
是一个列表解析表达式,每个元素都是一个函数,每个函数返回的是x的值。
for x in range(10)会循环10次,直到x=9结束,所以x的值是9,然后生成一个函数列表,每个函数的功能是返回x的值。
res = li0
此时,调用函数列表中的第一个函数,也就是返回x的值,而x的值上面已经知道了就是9
所以最后输出的是9
函数在调用的时候执行。在列表解析式中,循环10次生成的只是函数,不会返回x的值因为它还没有被调用。

Python中的if name == ‘main

当.py文件被直接运行时,if name == ‘main‘之下的代码块将被运行;当.py文件以模块形式被导入时,if name == ‘main‘之下的代码块不被运行。
Python不像编译型语言那样先将程序编译成二进制再运行,而是动态的逐行解释运行。也就是从脚本第一行开始运行,没有统一的入口。一个Python源码文件(.py)除了可以被直接运行外,还可以作为模块(也就是库),被其他.py文件导入。不管是直接运行还是被导入,.py文件的最顶层代码都会被运行(Python用缩进来区分代码层次),而当一个.py文件作为模块被导入时,我们可能不希望一部分代码被运行。
name是内置变量,可用于表示当前模块的名字。
main是顶层代码执行作用域的名字。

sort 与 sorted 区别

sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
对字典的值排序:
• sorted(d.items(),key = lambda x:x[1],reverse = False)
对字典进行排序后,其数据结构的类型已经由原来的字典变成了list

• import operator
sorted(d.items(),key = operator.itemgetter(1))
此方法数据结构保持不变
同时对键和值排序:
dict= sorted(dic.items(),key=operator.itemgetter(0,1))

python字典和列表(元组)的比较(cmp)

  • 字典比较的算法按照以下顺序进行:
    (1)比较字典长度
    如果字典的长度不同,那么用 cmp(dict1, dict2) 比较大小时,如果字典 dict1 比 dict2 长,cmp()返回正值,如果 dict2 比 dict1 长,则返回负值。也就是说,字典中的键的个数越多,这个字典就越大,即:
    len(dict1) > len(dict2) ==> dict1 > dict2
    (2)比较字典的键
    如果两个字典的长度相同,那就按字典的键比较;键比较的顺序和 keys()方法返回键的顺序相同。 (注意: 相同的键会映射到哈希表的同一位置,这保证了对字典键的检查的一致性。) 这时,如果两个字典的键不匹配时,对这两个(不匹配的键)直接进行比较。当 dict1 中第一个不同的键大于 dict2 中第一个不同的键,cmp()会返回正值。
    (3)比较字典的值
    如果两个字典的长度相同而且它们的键也完全匹配,则用字典中每个相同的键所对应的值进行比较。一旦出现不匹配的值,就对这两个值进行直接比较。若 dict1 比 dict 2 中相同的键所对应的值大,cmp()会返回正值。
    (4)完全匹配
    到此为止,即,每个字典有相同的长度、相同的键、每个键也对应相同的值,则字典完全匹配,返回 0 值。
  • 列表比较的算法按照以下顺序进行:
    (1)对两个列表的元组进行比较
    (2)如果比较的元素是同类型的,则比较其值,返回结果。
    (3)如果两个元素不是同一种类型,则检查他们是否是数字。
    a.如果是数字,执行必要的数字强制类型转换,然后比较。 b.如果有一方的元素是数字,则另一方的元素“大”(数字是“最小的”)。 c.否则,通过类型名字的字母顺序进行比较。
    a)如果是数字,执行必要的数字强制类型转换,然后比较。
    b)如果有一方的元素是数字,则另一方的元素“大”(数字是“最小的”)。
    c)否则,通过类型名字的字母顺序进行比较。
    (4)如果有一个列表首先到达末尾,则另一个长一点的列表“大”。
    (5)如果我们用尽了两个列表的元素而且所有的元素都是相等的,那么结果就是个平局,就是说返回一个0。

Python中列表和字典有什么区别

列表是序列,可以理解为数据结构中的数组,字典可以理解为数据结构中的hashmap,他俩都可以作为集合来存储数据
从差异特征上来说
1. list是有序的,dict是无序的
2. list通过索引访问,dict使用key访问
3. list随着数量的正常增长要想查找元素的时间复杂度为O(n), dict不随数量而增长而变化,时间复杂度都为O(1)
4. dict的占用内存稍比list大,会在1.5倍左右(这个两个数据结构应该对应c的哈希表和数组,因为哈希表需要额外内存记录映射关系,而数组只需要通过索引就能计算出下一个节点的位置,所以哈希表占用的内存比数组大)
特征决定用途:
list一般可作为队列、堆栈使用,而dict一般作为聚合统计或者快速使用特征访问等

猜你喜欢

转载自blog.csdn.net/changyan_123/article/details/80616014