分享

python核心知识点1

 闲读古书 2019-09-01

1.列表解析式和字典解析式

列表解析式语法 [expr for value in collection if condition]

生成式语法 (expr for value in collection if condition) 生成器

  • 简单用法
In [1]: [i*1 for i in range(5)]
Out[2]: [0, 1, 2, 3, 4]

In [29]: {x:random()  for x in 'abc'}
Out[29]: {'a': 2, 'b': 2, 'c': 2}
  • 复杂用法
>>> [(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1]  
[(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]

2.三元运算

# 普通条件语句
if 1 == 1:
    name = 'wupeiqi'
else:
    name = 'alex'
    
# 三元运算
name = 'wupeiqi' if 1 == 1 else 'alex'

3.map,reduce,filter,sorted函数的用法

  • map()函数接收两个参数,一个是函数名,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。
>>> def f(x):
        return x * x
    
>>> map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]
    
>>> map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])
['1', '2', '3', '4', '5', '6', '7', '8', '9']

map通常与lambda结合使用,写出精致而高效的语句

  • reduce()把一个函数作用在一个序列[x1, x2, x3...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,结果返回的是一个值。其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>>> def fn(x, y):
    ... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
  • filter()和map()也接收一个函数名和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素
    例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n):
     return n % 2 == 1

filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])
# 结果: [1, 5, 9, 15]
  • sorted(iterable[,cmp,[,key[,reverse=True]]])
    iterable包括字符串,序列,字典等可迭代对象,默认情况下reverse=True(升序),reverse=False为降序
    sorted与sort的区别???
    用list.sort()方法来排序,此时list本身将被修改
In [1]: a=[3,5,9,5,2]

In [2]: b=sorted(a)

In [3]: b
Out[4]: [2, 3, 5, 5, 9]

In [5]: a
Out[6]: [3, 5, 9, 5, 2]

In [7]: a.sort()

In [8]: a
Out[9]: [2, 3, 5, 5, 9]

综合练习

num = input('输入一个整数:')
#将整数转换成字符串
s = str(num)

#定义map参数函数
def f(s):
    #字符与数字字典
    dic = {'1':1,'2':2,'3':3,'4':4,'5':5,"6":6,'7':7,'8':8,'9':9,'0':0}
    return dic[s]

#定义reduce参数函数
def add(x,y):
    return x + y

#调用map()函数,将字符串转换成对应数字序列,并打印
s = map(f,s)
print "输入整数%d的组成数字为%s"%(num,s),

#调用reduce函数,对数字序列求和,并打印
Sum = reduce(add,s)
print "其和为:%d"%Sum

4.lambda匿名函数

lambda arg:expression
# 定义函数(普通方式)
def func(arg):
    return arg + 1

# 执行函数
result = func(123)

# ###################### lambda ######################

# 定义函数(lambda表达式)
my_lambda = lambda arg : arg + 1

# 执行函数
result = my_lambda(123)

5.dict按值搜索最大值或最小值

In [1]: d = {'a':7,'b':8,'c':5}
In [2]: min(d.items(),key=lambda x:x[1])
Out[3]:('c', 5)
In [4]: min(d.items(),key=lambda x:x[1])[1]
Out[5]:5

In [6]: d.items()#返回元素为元祖的序列
Out[7]: dict_items([('b', 8), ('a', 7), ('c', 5)])
  
Out[8]: help(min)
min(iterable, *[, default=obj, key=func]) -> value
min(arg1, arg2, *args, *[, key=func]) -> value

6.for循环中变量i的作用域

在for 循环里,变量一直存在在上下文中。就算是在循环外面,变量引用仍然有效。这里有个问题容易被忽略,如果在循环之前已经有一个同名对象存在,这个对象是被覆盖的。

x = 5
for x in range(10):
    pass
print(x)#9
----------------------------------
for i in range(4):
    d = i*2
    s = "string" + str(i)
print(d)#6
print(s)#srting3

7.值引用和地址引用

python中变量名里存储的是对象的地址(引用),这区别于c语言

a=10时的内存示意图

image
In [1]: m=10000

In [2]: id(m)
Out[2]: 4365939312

In [3]: n=m  #地址赋值

In [4]: id(n)
Out[4]: 4365939312
  • 了解python里的赋值的含义???
def f(m):                          |  def的时候就已经在栈中存放了
    m=[2,3]  #赋值相当于重新绑定对象   |  变量m,当把a传给m时,m引用了a
                                   |  的内容,但是对m赋值的时候,m
a = [4,5]                          |  指向了新值,不影响a对应的值。
f(a)                               |
print(a)  #[4,5]                  |
def f(m):
    m.append(4)

a = [4,5]
f(a)
print(a)  #[4,5,4]

python函数参数传递是传对象或者说是传对象的引用。函数参数在传递的过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象,而对于不可变对象,由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后赋值实现的。《91条建议之31》

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

8.append的陷阱

import random
lis = [1,2,3,4]
l = []
for i in range(5):
    random.shuffle(lis)
    print(lis, hex(id(lis)))
    l.append(lis)  #append()传入list的引用
print(l)
([1, 3, 4, 2], '0x107227950')
([3, 4, 1, 2], '0x107227950')
([3, 2, 4, 1], '0x107227950')
([2, 3, 4, 1], '0x107227950')
([4, 3, 1, 2], '0x107227950')
[[4, 3, 1, 2], [4, 3, 1, 2], [4, 3, 1, 2], [4, 3, 1, 2], [4, 3, 1, 2]]

9.__new__和__init__的区别

按字面意思理解就可以。 __new__ 用于创建对象,而 __init__ 是在创建完成之后初始化对象状态。Python 中只不过是把创建对象和初始化这两个概念分开了,而其他语言中直接用一个构造函数解决。

class A(object):
    def __new__(cls, *args, **kwargs):
        print cls
        print hex(id(cls))   #cls就是类对象A
        # print args
        # print kwargs
        print "==========================================="
        instance = object.__new__(cls, *args, **kwargs)   #instance就是实例对象a1
        print hex(id(instance))
        return instance   #若没有return语句,则无法创建实例对象a1

    # def __init__(self, a, b):
    #     print "init gets called"
    #     print "self is", self
    #     self.a, self.b = a, b

a1=A()
print "==========================================="
print A
print hex(id(A))
print "==========================================="
print a1
print hex(id(a1))

Build Result:
<class '__main__.A'>
0x22163e0
===========================================
0x21d6a10
===========================================
<class '__main__.A'>
0x22163e0
===========================================
<__main__.A object at 0x021D6A10>
0x21d6a10

从上例看出,对象实例化的内部过程:
当执行a1=A(),从上例看出,对象实例化的内部过程:当执行a1=A(),首先调用__new__方法,由于__new__方法返回的是一个实例对象(a1),故相当于创建了一个实例对象,再次调用__init__方法,来对变量初始化,再次调用__init__方法,来对变量初始化。

  • __new__方法默认返回实例对象供__init__方法、实例方法使用。
  • __init__ 方法为初始化方法,为类的实例提供一些属性或完成一些动作。
  • __new__方法创建实例对象供__init__方法使用,__init__方法定制实例对象。
  • __new__ 方法必须返回值,__init__方法不需要返回值。(如果返回非None值就报错)
  • 一般用不上__new__方法

10.__repr__定制类的用法

通常情况下

In [1]: class A(object):
   ...:     pass
   ...:

In [2]: a=A()

In [3]: print a
<__main__.A object at 0x03CB0FD0>

我们可以修改输出a的表达式,例如:

In [4]:  from PIL import Image
   ...:
   ...:  a=Image.Image()
   ...:
In [5]: print a
<PIL.Image.Image image mode= size=0x0 at 0x3CB2190>

查看Image类,发现__repre__方法被重写了

def __repr__(self):
    return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
        self.__class__.__module__, self.__class__.__name__,
        self.mode, self.size[0], self.size[1],
        id(self))

11.类属性和实例属性

一.类属性和实例属性
当有类属性时,再定义实例属性是因为实例属性的变量名指向了新的值,类属性并没有被改变。说到底就是类对象和实例对象一般不共有同一属性和方法。

class Person(object):
    """定义一个具有类属性的类"""
    _die = True

    def __init__(self, age):
        self.age = age            #age不是类属性

pa = Person(20)
print(Person._die, id(Person._die))#输出值:True 1531278160
print(pa._die, id(pa._die))#输出值:True 1531278160
pa._die = 1  
print(pa._die, id(pa._die))#输出值:1 1531459344
del pa._die
print(pa._die, id(pa._die))#输出值:True 1531278160
class Test(object):
    name = 'scolia'

a = Test()
a.name = 'scolia good'  # 通过实例进行修改,此时实例的name属性是重新创建的,与类的name属性不是同一个
print(Test.name) #scolia
print(a.name)   #scolia good

本质:当函数内访问变量时,会先在函数内部查询有没有这个变量,如果没有,就到外层中找。这里的情况是我在实例中访问一个属性,但是我实例中没有,我就试图去创建我的类中寻找有没有这个属性。找到了,就有,没找到,就抛出异常。当我去赋值实例对象中没有的变量时,其实就是对该对象创建一个变量,这是由于python是动态语言决定的。而当我试图用实例去修改一个在类中不可变的属性的时候,我实际上并没有修改,而是在我的实例中创建了这个属性。而当我再次访问这个属性的时候,我实例中有,就不用去类中寻找了。
如果用一张图来表示的话:

image

关于类与实例对象的深处理解:

创建的实例对象不包含任何的属性(构造函数除外),但是跟成员方法绑定。

  • 访问的时候是访问类对象中的属性
  • 赋值的时候,实例对象重新创建变量名
  • 类访问成员方法时,必须带上绑定的实例对象,即<类.方法(实例对象)>
  • 类方法(@classmethod)和成员方法的区别??? 类.方法()直接访问
In [25]: class A(object):
    ...:     m=1
    ...:     n=2
    ...:

In [26]: a=A()

In [27]: A.__dict__
Out[27]:
dict_proxy({'__dict__': <attribute '__dict__' of
            '__doc__': None,
            '__module__': '__main__',
            '__weakref__': <attribute '__weakref_
            'm': 1,
            'n': 2})

In [28]: a.__dict__
Out[28]: {}

In [29]: A.m
Out[29]: 1

In [30]: a.m
Out[30]: 1

In [31]: hex(id(A.m))
Out[31]: '0x1e0a5d8'

In [32]: hex(id(a.m))
Out[32]: '0x1e0a5d8'

In [33]: a.m=12  #赋值之后不在是同一个m

In [35]: hex(id(a.m))
Out[35]: '0x1e0a554'

In [36]: hex(id(A.m))
Out[36]: '0x1e0a5d8'

两种写法的区别:

In [53]: class Kls(object):
    ...:     no_inst = 0
    ...:     def __init__(self):
    ...:         Kls.no_inst = Kls.no_inst + 1
    ...:

In [54]: k1=Kls()

In [55]: k1.__dict__
Out[55]: {}

In [56]: class Kls(object):
    ...:     no_inst = 0
    ...:     def __init__(self):
    ...:         self._inst = self.no_inst + 1
    ...:

In [57]: k1=Kls()

In [58]: k1.__dict__
Out[58]: {'_inst': 1}

12.@classmethod和@staticmethod

一般来说,要使用某个类的方法,需要先实例化一个对象再调用方法。而使用@staticmethod或@classmethod,就可以不需要实例化,直接类名.方法名()来调用。这有利于组织代码,把某些应该属于某个类的函数给放到那个类里去,同时有利于命名空间的整洁。

既然@staticmethod和@classmethod都可以直接类名.方法名()来调用,那他们有什么区别呢?从它们的使用上来看

  • @staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。类似于全局函数。
  • @classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。

@classmethod 是一个函数修饰符,它表示接下来的是一个类方法,而对于平常我们见到的则叫做实例方法。 类方法的第一个参数cls,而实例方法的第一个参数是self,表示该类的一个实例。普通对象方法至少需要一个self参数,代表类对象实例。

类方法有类变量cls传入,从而可以用cls做一些相关的处理。并且有子类继承时,调用该类方法时,传入的类变量cls是子类,而非父类。 对于类方法,可以通过类来调用,就像Test.foo()

如果要调用@staticmethod方法,通过类对象或对象实例调用;而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。==@classmethod最常见的用途是定义备选构造方法==

下面上代码。

class A(object):
    bar = 1   #这是类的属性,而非实例的属性
    def foo(self): #这是实例的方法,注意只要带有self就是实例的方法或者属性
        print 'foo'

    @staticmethod
    def static_foo():
        print 'static_foo'
        print A.bar

    @classmethod
    def class_foo(cls):
        print 'class_foo'
        print cls.bar
        print cls().foo()

A.static_foo()
A.class_foo()
============================================
out:
static_foo
1
class_foo
1
foo

@classmethod means: when this method is called, we pass the class as the first argument instead of the instance of that class (as we normally do with methods). This means you can use the class and its properties inside that method rather than a particular instance.

@staticmethod means: when this method is called, we don't pass an instance of the class to it (as we normally do with methods). This means you can put a function inside a class but you can't access the instance of that class (this is useful when your method does not use the instance).

13.assert的用法

在开发一个程序时候,与其让它运行时崩溃,不如在它出现错误条件时就崩溃(返回错误)。这时候断言assert 就显得非常有用。

assert的语法:

assert expression1 [“,” expression2]

等价语句:

if not expression1:     
    raise Exception(expression2)

example:

In [6]: x = 1
In [7]: y = 2
In [8]: assert x == y,'not equal’. #若x == y,则会继续执行下去
----------------------------------------------------------
AssertionError            Traceback (most recent call last)
<ipython-input-8-30354b60974a> in <module>()
----> 1 assert x == y,'not equal'

AssertionError: not equal

14.format的用法

它通过{}和:来代替%。

用法是:字符串.format(args)

1.通过位置

In [1]: '{0},{1}'.format('kzc',18) 
Out[1]: 'kzc,18'
In [2]: '{},{}'.format('kzc',18) 
Out[2]: 'kzc,18'
In [3]: '{1},{0},{1}'.format('kzc',18) 
Out[3]: '18,kzc,18'

字符串的format函数可以接受不限个参数,位置可以不按顺序,可以不用或者用多次,不过2.6不能为空{},2.7才可以。
2.通过关键字参数

In [5]: '{name},{age}'.format(age=18,name='kzc') 
Out[5]: 'kzc,18'

3.通过下标

In [7]: p=['kzc',18]
In [8]: '{0[0]},{0[1]}'.format(p)
Out[8]: 'kzc,18'

4.格式限定符

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

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

In [15]: '{:>8}'.format('189')
Out[15]: '   189'
In [16]: '{:0>8}'.format('189')
Out[16]: '00000189'
In [17]: '{:a>8}'.format('189')
Out[17]: 'aaaaa189'

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

In [44]: '{:.2f}'.format(321.33345)
Out[44]: '321.33'

其中.2表示长度为2的精度,f表示float类型。

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

In [54]: '{:b}'.format(17)
Out[54]: '10001'
In [55]: '{:d}'.format(17)
Out[55]: '17'
In [56]: '{:o}'.format(17)
Out[56]: '21'
In [57]: '{:x}'.format(17)
Out[57]: '11'

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

In [47]: '{:,}'.format(1234567890)
Out[47]: '1,234,567,890'

15.*和**收集参数以及解包的用法

在def函数时,参数前的****收集其余的位置参数,并返回一个元祖**收集关键字参数,并返回一个字典。关键字参数就是参数列表中有专门定义“参数=值”
例:

def print_params_2(title,*params1,**params2):
    print title
    print params1
    print params2
    
print_params_2("params:",1,2,3,x=5,y=6)
params:
(1,2,3)
{‘x’:5,’y’:6}

解包:当调用函数时,将*args或**kwargs作为参数传入

def foo(*args,**kwargs):
    print(args)
    print(kwargs)
    print(*args)
    print(*kwargs)

foo(1,2,3,a=4,b=5)
########################
(1, 2, 3)
{'a': 4, 'b': 5}
1 2 3
a b

当定义函数时,*和**是用来收集参数

当调用函数时,*是用来解包参数

16.zip的用法

zip(list1,list2)返回一个list,元素是list1和list2组成的元组

>>> zip([1,2,3],[6,7,8])
[(1, 6), (2, 7), (3, 8)]
>>> zip((1,2,3),(6,7,8))
[(1, 6), (2, 7), (3, 8)]

17.__closure__之闭包

def outlayer():
    a = 30
    b = 15
    c = 20
    print(hex(id(b)))
    print(hex(id(c)))
    def inlayer(x):
        return 2*x+b+c   #引用外部变量b和c,b和c有时又叫做环境变量
                        #只可以访问环境变量,若要修改,需要加nonlocal
    return inlayer       # return a function object

temp = outlayer()
print('='*10)
print(outlayer.__closure__) #outlayer函数的__closure__为空
print('='*10)
print(temp.__closure__)    #只包含b和c环境变量,a对于inlayer不是,因为没有调用
0x100275d00
0x100275da0
==========
None
==========
0x100275d00
0x100275da0
(<cell at 0x1006c8ca8: int object at 0x100275d00>,<cell at 0x1006c8ee8: int object at 0x100275da0>)

一个函数inlayer和它的环境变量b,c合在一起,就构成了一个闭包(closure)。在Python中,所谓的闭包是一个包含有环境变量取值的函数对象。环境变量取值被保存在函数对象的__closure__属性中。

18.nonlocal的用法

nonlocal关键字用来在函数或其他作用域中使用(修改)外层(非全局)变量,一般出现在闭包或者装饰器中。

def outlayer():
    c = 20

    def inlayer(x):
        nonlocal c  #声明使用上层函数中的c,不可以使用global
        c = c+1
        print('c is', c)
        return 2*x+c   
    return inlayer       

temp = outlayer()
print(temp(5))
c is 21
31

19.Counter()简单统计个数

Counter是collections模块中的一个类,可以用来简单统计容器中数据的个数

In [1]: from collections import Counter
In [2]: b=[1,2,3,4,5,0,6,3,4,6,0]
In [3]: c = Counter(b)
In [4]: c
Out[5]: Counter({0: 2, 1: 1, 2: 1, 3: 2, 4: 2, 5: 1, 6: 2})

20.mro搜索顺序

MRO(Method Resolution Order)

class Root: 
    pass 
class A(Root): 
    pass 
class B(Root): 
    pass 
class C(A, B): 
    pass 

print(C.mro) 
# 输出结果为: # 
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Root'>, <class 'object'>)

MRO实际上是对继承树做层序遍历的结果,把一棵带有结构的树变成了一个线性的表,所以沿着这个列表一直往上, 就可以无重复的遍历完整棵树, 也就解决了多继承中的Diamond问题。

20.super的用法

super是一个类。调用super()这个方法时,只是返回一个super对象,并不做其他的操作。然后对这个super对象进行方法调用时,发生的事情如下:

  1. 找到第一个参数的__mro__列表中的下一个直接定义了该方法的类
  2. 该(父)类调用方法,参数绑定的对象为子类对象
class Root:
    def __init__(self):
        print('Root')

class A(Root):
    def __init__(self):
        print('A1')
        super().__init__() # 等同于super(A, self).__init__(self)
        print('A2')

a = A()
A1
Root
A2

在A的构造方法中,先调用super()得到一个super对象,然后向这个对象调用__init__方法,这时super对象会搜索A的__mro__列表,找到第一个定义了__init__方法的类, 于是就找到了Root, 然后调用Root.__init__(self),这里的self是super()的第二个参数,是编译器自动填充的,也就是A的__init__的第一个参数,这样就完成__init__方法调用的分配。

注意: 在许多语言的继承中,==子类必须调用父类的构造方法,就是为了保证子类的对象能够填充上父类的属性!==而不是初始化一个父类对象…。Python中就好多了,所谓的调用父类构造方法, 就是明明白白地把self传给父类的构造方法,

如果没有多继承, super其实和通过父类来调用方法差不多. 但, super还有个好处: 当B继承自A, 写成了A.__init__, 如果根据需要进行重构全部要改成继承自 E,那么全部都得改一次! 这样很麻烦而且容易出错! 而使用super()就不用一个一个改了(只需类定义中改一改就好了)

补充:对面向对象的理解

其实我觉得Python里面这样的语法更容易理解面向对象的本质, 比Java中隐式地传this更容易理解。所谓函数,就是一段代码,接受输入,返回输出。所谓方法,就是一个函数有了一个隐式传递的参数。所以方法就是一段代码,是类的所有实例共享, 唯一不同的是各个实例调用的时候传给方法的this 或者self不一样而已。

构造方法是什么呢?其实也是一个实例方法啊,它只有在对象生成了之后才能调用,所以Python中__init__方法的参数是self啊。调用构造方法时其实已经为对象分配了内存, 构造方法只是起到初始化的作用,也就是为这段内存里面赋点初值而已。

Java中所谓的静态变量其实也就是类的变量, 其实也就是为类也分配了内存,里面存了这些变量,所以Python中的类对象我觉得是很合理的,也比Java要直观。至于静态方法,那就与对象一点关系都没有了,本质就是个独立的函数,只不过写在了类里面而已。而Python中的classmethod其实也是一种静态方法,不过它会依赖于cls对象,这个cls就是类对象,但是只要想用这个方法,类对象必然是存在的,不像实例对象一样需要手动的实例化,所以classmethod也可以看做是一种静态变量。而staticmethod就是真正的静态方法了,是独立的函数,不依赖任何对象。

Java中的实例方法是必须依赖于对象存在的, 因为要隐式的传输this,如果对象不存在这个this也没法隐式了。所以在静态方法中是没有this指针的,也就没法调用实例方法。而Python中的实例方法是可以通过类名来调用的。只不过因为这时候self没办法隐式传递,所以必须得显式地传递。

22.__call__回调函数

一般来说,魔法方法都是隐式调用的

__call__()放在类中定义。

python中,函数是一个对象

>>> f = abs   #绝对值函数
>>> f.__name__
'abs'
>>> f(-10)
10

由于 f 可以被调用,所以,f 被称为可调用对象。所有的函数都是可调用对象。

一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()。

我们把 Person 类变成一个可调用对象:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def __call__(self, friend):
        print 'My name is %s...' % self.name
        print 'My friend is %s...' % friend

现在可以对 Person 实例直接调用:

>>> person = Person('Bob', 'male')

>>> person('Tim')

My name is Bob...

My friend is Tim...

单看 person('Tim') 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。

23.dict的get函数

get函数相比较于通过下标的方式获取值得好处就是,即使字典中没有想要的key,也会根据get后面的参数输出结果,对用户友好;而下标方式直接报错

>>> a = {'a':1,'b':2}
>>> a.get('a')
1
>>> a.get('c',"None")
'None'

24.hasattr() getattr() setattr() 函数使用方法详解

hasattr(object, name)
判断一个对象里面是否有name属性或者name方法,返回BOOL值,有name特性返回True, 否则返回False。
需要注意的是name要用括号括起来

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> hasattr(t, "name") #判断对象有name属性
True
>>> hasattr(t, "run")  #判断对象有run方法
True

getattr(object, name[,default])
获取对象object的属性或者方法,如果存在打印出来,如果不存在,打印出默认值,默认值可选。
需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行这个方法,
可以在后面添加一对括号。

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> getattr(t, "name") #获取name属性,存在就打印出来。
'xiaohua'
>>> getattr(t, "run")  #获取run方法,存在就打印出方法的内存地址。
<bound method test.run of <__main__.test instance at 0x0269C878>>
>>> getattr(t, "run"())  #获取run方法,后面加括号可以将这个方法运行。
'HelloWord'
>>> getattr(t, "age")  #获取一个不存在的属性。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: test instance has no attribute 'age'
>>> getattr(t, "age","18")  #若属性不存在,返回一个默认值。
'18'

setattr(object, name, values)
给对象的属性赋值,若属性不存在,先创建再赋值。

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> hasattr(t, "age")   #判断属性是否存在
False
>>> setattr(t, "age", "18")   #为属相赋值,并没有返回值
>>> hasattr(t, "age")    #属性存在了
True

一种综合的用法是:判断一个对象的属性是否存在,若不存在就添加该属性。

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> getattr(t, "age")    #age属性不存在
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: test instance has no attribute 'age'
>>> getattr(t, "age", setattr(t, "age", "18")) #age属性不存在时,设置该属性
'18'
>>> getattr(t, "age")  #可检测设置成功
'18'

25.object,type和metaclass的关系

  • type类是元类,用来创建类(对象)的,事实是调用了type的__new__创建的类(对象)
  • __new__是用来创建实例对象的,如果没有复写__new__方法,事实上是调用了object的__new__
  • metaclass关键字是用来定制元类的,这样子后续我们定义的类就可以用定制的元类来创建。经典的运用就是ORM
  • type也是继承了object,并对object的__new__进行了复写
  • 父类(对象)先创建,之后才是子类(对象)

26.浅拷贝和深拷贝

普通的赋值在python中是浅拷贝,及传递的是对象的引用;若要深拷贝,必须导入copy模块

copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。

copy.deepcopy 深拷贝 拷贝对象及其子对象

import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
 
b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝
 
a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象
 
print 'a = ', a
print 'b = ', b
print 'c = ', c
print 'd = ', d
输出结果:
a =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c =  [1, 2, 3, 4, ['a', 'b', 'c']]
d =  [1, 2, 3, 4, ['a', 'b']]

深拷贝在拷贝的时候,若拷贝对象里包含引用,不仅拷贝引用,而且把引用的内容也再复制一份。理解的含义。

27.私有属性是可以访问的

python中定义私有属性的方法是在变量名前加'_'或者'__'

_foo:一种约定,用来指定变量私有。程序员用来指定私有变量的一种方式。但是外部仍可直接访问

__foo:这个有真正的意义:解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名。

但是我们可以访问私有属性,因为python中私有属性是通过名字重整的机制实现的,改变了私有属性名,从而报出name defined异常。

In [19]: class A():
    ...:     def __init__(self):
    ...:         self.__num=100
    ...:         

In [20]: a=A()

In [21]: a.__num
---------------------------------------------------------------

AttributeError: 'A' object has no attribute '__num'

In [22]: dir(a)
Out[22]: 
['_A__num',
 '__class__',
...
 '__weakref__']

In [23]: a._A__num
Out[23]: 100

28.属性拦截器__getattribute__

当访问对象的属性时,其实先调用对象中的魔法方法__getattribute__

class Itcast(object):
    def __init__(self,subject1):
        self.subject1 = subject1
        self.subject2 = 'cpp'

    #属性访问时拦截器,打log
    def __getattribute__(self,obj):
        if obj == 'subject1':
            print('log subject1')
            return 'redirect python'
        else:   #测试时注释掉这2行,将找不到subject2
            return object.__getattribute__(self,obj)

    def show(self):
        print('this is Itcast')

s = Itcast("python")
print(s.subject1)
print(s.subject2)
log subject1
redirect python
cpp

29.对象中都是属性,方法只不过是属性的引用

In [24]: class A():
    ...:     pass
    ...: 

In [25]: a=A()

In [26]: a.f
---------------------------------------------------------------

AttributeError: 'A' object has no attribute 'f'

In [27]: a.f()
---------------------------------------------------------------

AttributeError: 'A' object has no attribute 'f'

所以当调用a.f()的时候也会默认执行类中__getattribute__方法

30.GIL全局解释器锁

从名字上看能告诉我们很多东西,很显然,这是一个加在解释器上的全局(从解释器的角度看)锁(从互斥或者类似角度看)。GIL使得同一时刻只有一个线程在一个CPU上执行字节码,无法保证多个线程映射到多个CPU上执行。对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在执行。事实上,线程并不是完全运行完成后释放GIL,所以线程安全也是相对的

在Python多线程下,每个线程的执行方式:

1.获取GIL

2.执行代码直到sleep或者是python虚拟机将其挂起(虚拟机会根据执行的字节码行数以及时间片释放GIL)或者遇到IO操作

3.释放GIL

可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行

解决办法就是多进程和协程(协程也只是单CPU,单线程但是能减小切换代价提升性能)。

31.当函数的参数是list引发的问题

==比较的是值的大小;is是比较地址???

32.上下文管理 __enter__,__exit__

应用场景:

  • 文件的读写
  • 数据库的读写操作
  • Flask的上下文管理

上下文管理协议:当使用with语句时,解释器会自动调用 __enter__,__exit__

class Sample:
    def __enter__(self):
        print('enter')  #进入资源
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit')  #释放资源
    def do_something(self):
        print('do something')

with Sample() as sample:   
    sample.do_something()

输出

enter
do something
exit

进入with语句,调用__enter__;退出with语句,调用__exit__

事实上sample并不是sample=Sample(),而是__enter__返回的对象,即如果__enter__没有return,sample将为None。

exc_type:异常类型;exc_val:异常值;exc_tb:traceback。如果with语句中有异常发生,__exit__中会收集这些异常信息。

33.__slots__

Python是一门动态语言,可以在运行过程中,修改实例的属性和增删方法。一般,任何类的实例包含一个字典dict,Python通过这个字典可以将任意属性绑定到实例上。有时候我们只想使用固定的属性,而不想任意绑定属性,这时候我们可以定义一个属性名称集合,只有在这个集合里的名称才可以绑定。slots就是完成这个功能的。

class test_slots(object):  
    __slots__='x','y'  
    def printHello(self):  
        print 'hello!'  
  
class test(object):  
    def printHello(self):  
        print 'hello'  
  
print dir(test_slots) #可以看到test_slots类结构里面包含__slots__,x,y  
print dir(test)#test类结构里包含__dict__  
print '**************************************'  
ts=test_slots()  
t=test()  
print dir(ts) #可以看到ts实例结构里面包含__slots__,x,y,不能任意绑定属性  
print dir(t) #t实例结构里包含__dict__,可以任意绑定属性  
print '***************************************'  
ts.x=11 #只能绑定__slots__名称集合里的属性  
t.x=12 #可以任意绑定属性  
print ts.x,t.x  
ts.y=22 #只能绑定__slots__名称集合里的属性  
t.y=23  #可以任意绑定属性  
print ts.y,t.y  
#ts.z=33 #无法绑定__slots__集合之外的属性(AttributeError: 'test_slots' object has no attribute 'z')  
t.z=34 #可以任意绑定属性  
print t.z   

正如上面所说的,默认情况下,Python的新式类和经典类的实例都有一个dict来存储实例的属性。这在一般情况下还不错,而且非常灵活,乃至在程序中可以随意设置新的属性。但是,对一些在”编译”前就知道有几个固定属性的小class来说,这个dict就有点浪费内存了。当需要创建大量实例的时候,这个问题变得尤为突出。一种解决方法是在新式类中定义一个slots属性。slots声明中包含若干实例变量,并为每个实例预留恰好足够的空间来保存每个变量;这样Python就不会再使用dict,从而节省空间。

34.__unicode__,__str__

__unicode____str__类似于java的toString方法。在java中,当打印对象的时候,其实是调用了对象的toString方法。同样,我们可以定制对象打印输出的格式

unicode是用在python2里,而str是用在python3里

#不复写__str__方法,调用的是object的方法
class A:
    def __init__(self,name,age):
        self.name=name
        self.age=age

a=A('lu',25)
print(a)  #<__main__.A object at 0x101978cf8>

#复写__str__方法,调用的是object的方法
class A:
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __str__(self):
        return self.name + ' is ' + str(self.age) + ' years old'

a=A('lu',25)
print(a)  #lu is 25 years old

35.文件缓冲

36.for不可对可迭代对象进行修改

在使用python的可迭代对象时有一条定律,那就是永远都不要对迭代对象进行数据的修改。 我们可以用copy(),生成一个副本,对这个副本进行遍历。

In [10]: l = ['a','b','c','d']

In [11]: for index,key in enumerate(l):
    ...:     if key == 'c':
    ...:         l.pop(index)

In [12]: l
['a', 'b', 'c']
        
########推荐
        for index,key in enumerate(l.copy()):
    ...:     if key == 'c':
    ...:         l.pop(index)

37.__getitem____setitem__方法来实现“[ ]”符号的使用

class Map:
    def __init__(self):
        self.Q_key = []
        self.Q_value = []

    def put(self, key, value):
        self.Q_key.append(key)
        self.Q_value.append(value)

    def get(self, key, default=None):
        for index,k in enumerate(self.Q_key):
            if k == key:
                return self.Q_value[index]
        return default

    def __getitem__(self, key):
        return self.get(key)

    def __setitem__(self, key, value):
         self.put(key, value)

m = Map()
m['a']=1
m['b']=2
print(m['b'])

38.list的倒序切片

In [1]: l=[1,2,3,4,5,6]

In [7]: l[-1:1:-1]
Out[7]: [6, 5, 4, 3]

In [8]: l[-1:0:-1]
Out[8]: [6, 5, 4, 3, 2]

In [9]: l[-1::-1]
Out[9]: [6, 5, 4, 3, 2, 1]

39.字节流与字符流

40.__name__的含义

#a.py
print(__name__)

运行a.py,执行结果为__main__

但如果对于一个b.py作为模块在a中使用,b的__name__将变成对应b的模块名

#b.py
print(__name__)

#a.py
import b

运行a.py(如果运行b.py,结果扔是main),执行结果为b

所以为了测试模块的相应功能,而避免在主程序中运行,通常我们需要在执行的代码中这样写

if __name__ == '__main__':
    do something

这样子就能很好的控制模块的有效输出

41.为什么不能用可变对象作为函数的默认参数值

先来看一道题目:

>>> def func(numbers=[], num=1):
...     numbers.append(num)
...     return numbers

>>> func()
[1]
>>> func()
[1, 1]
>>> func()
[1, 1, 1]

我们似乎发现了一个Bug,每次用相同的方式调用函数 func() 时,返回结果竟然不一样,而且每次返回的列表在不断地变长。

>>> id(func())
4330472840
>>> id(func())
4330472840

从上面可以看出,函数的返回值其实是同一个列表对象,因为他们的id值是一样的,只不过是列表中的元素在变化。为什么会这样呢?

这要从函数的特性说起,在 Python 中,函数是第一类对象(function is the first class object),换而言之,函数也是对象,跟整数、字符串一样可以赋值给变量、当做参数传递、还可以作为返回值。函数也有自己的属性,比如函数的名字、函数的默认参数列表。

# 函数的名字
>>> func.__name__  
'func'

# 函数的默认参数列表
>>> func.__defaults__  
([1, 1, 1, 1, 1], 1)

def是一条可执行语句,Python 解释器执行 def 语句时,就会在内存中就创建了一个函数对象(此时,函数里面的代码逻辑并不会执行,因为还没调用嘛),在全局命名空间,有一个函数名(变量叫 func)会指向该函数对象,记住,至始至终,不管该函数调用多少次,函数对象只有一个,就是function object,不会因为调用多次而出现多个函数对象。

image

函数对象生成之后,它的属性:名字和默认参数列表都将初始化完成。

image

初始化完成时,属性 __default__ 中的第一个默认参数 numbers 指向一个空列表。

当函数第一次被调用时,就是第一次执行 func()时,开始执行函数里面的逻辑代码(此时函数不再需要初始化了),代码逻辑就是往numbers中添加一个值为1的元素

image

第二次调用 func(),继续往numbers中添加一个元素

image

第三次、四次依此类推。

所以现在你应该明白为什么调用同一个函数,返回值确每次都不一样了吧。因为他们共享的是同一个列表(numbers)对象,只是每调用一次就往该列表中增加了一个元素

如果我们显示地指定 numbers 参数,结果截然不同。

>>> func(numbers=[10, 11])
[10, 11, 1]
image

因为numbers被重新赋值了,它不再指向原来初始化时的那个列表了,而是指向了我们传递过去的那个新列表对象,因此返回值变成了 [10, 11, 1]

那么我们应该如何避免前面那种情况发生呢?就是不要用可变对象作为参数的默认值。

正确方式:

>>> def func(numbers=None, num=1):
...     if numbers is None:
...         numbers = [num]
...     else:
...         numbers.append(num)
...     return numbers
...
>>> func()
[1]
>>> func()
[1]
>>> func()
[1]

如果调用时没有指定参数,那么调用方法时,默认参数 numbers 每次都被重新赋值了,所以,每次调用的时候numbers都将指向一个新的对象。这就是与前者的区别所在。

那么,是不是说我们永远都不应该用可变对象来作为参数的默认值了吗?并不是,既然Python有这样的语法,就一定有他的应用场景,就像 for … else 语法一样。我们可以用可变对象来做缓存功能

例如:计算一个数的阶乘时可以用一个可变对象的字典当作缓存值来实现缓存,缓存中保存计算好的值,第二次调用的时候就无需重复计算,直接从缓存中拿。

def factorial(num, cache={}):
    if num == 0:
        return 1
    if num not in cache:
        print('xxx')
        cache[num] = factorial(num - 1) * num
    return cache[num]

print(factorial(4))
print("-------")
print(factorial(4))

输出:

---第一次调用---
xxx
xxx
xxx
xxx
24
---第二次调用---
24

第二次调用的时候,直接从 cache 中拿了值,所以,你说用可变对象作为默认值是 Python 的缺陷吗?也并不是,对吧!你还是当作一种特性来使用。

函数的基本注意点

1.函数基础之globals()和locals()

  • 函数名.__doc__,返回函数的解释,对于函数定义时,可以将解释通过字符串写到执行语句中。
def abs(a=3)
    “this is new function“
>>>abs.__doc__
“this is new function“
  • 函数体执行到return语句就结束,不管后面是否还有语句。
  • 当def一个函数时,其实就是创建了一个函数对象,并将函数对象返回给函数名。
  • 函数定义时的参数(a=2)可以通过函数对象的__defaults__查看
  • 每次对函数的调用都创建了一个新的本地作用域。
  • 作用域(命名空间),一个隐藏的字典(键为函数名,值为函数对象的地址)
  • globals()函数返回全局变量的字典;locals()函数返回局部变量的字典(根据当前上下文来判断,若处在全局的位置,等同于globals(),见下例)。locals()函数只有在函数执行过程在才用意义,因为函数调用完成后,局部变量会从栈中删除,所以再用locals(),没有局部变量的字典
>>> def test(arg):
          z = 1
          print locals()
>>> test(4)
{'z': 1, 'arg': 4}
>>> test('doulaixuexi')
{'z': 1, 'arg': 'doulaixuexi'}
>>> print(locals())# 输出与globals()一样的结果
>>> print(globals())
  • 在函数的定义中可以访问全局变量(依据LEGB原则搜索变量),但不可以修改全局变量,除非使用global声明变量
a = 3
def test(arg):
    z = 1
    global a
    a = 100
test(4)
print(a)#100
  • vars()函数返回在当前一个作用域(命名空间)的字典

补充:

Python使用叫做名字空间的东西来记录变量的轨迹。名字空间只是一个 字典,它的键字就是变量名,字典的值就是那些变量的值。实际上,名字空间可以象Python的字典一样进行访问,一会我们就会看到。

在一个Python程序中的任何一个地方,都存在几个可用的名字空间。每个函数都有着自已的名字空间,叫做局部名字空间,它记录了函数的变量,包括 函数的参数和局部定义的变量。每个模块拥有它自已的名字空间,叫做全局名字空间,它记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常 量。还有就是内置名字空间,任何模块均可访问它,它存放着内置的函数和异常。

2.LEGB命名空间

当一行代码要使用变量x的值时,Python会到所有可用的名字空间去查找变量,按照如下顺序:

1. Local:局部名字空间特指当前函数或类的方法。如果函数定义了一个局部变量 x ,Python将使用这个变量,然后停止搜索。
2. Enclosing:外部嵌套函数的命名空间(见闭包)
3. Global:全局名字空间特指当前的模块。如果模块定义了一个名为 x 的变量,函数或类,Python将使用这个变量然后停止搜索。
4. Builtin:内置名字空间对每个模块都是全局的。作为最后的尝试,Python将假设 x 是内置函数或变量。

如果Python在这些名字空间找不到x,它将放弃查找并引发一个 NameError 的异常,同时传递There is no variable named 'x' 这样一条信息。

像Python中的许多事情一样,名字空间在运行时直接可以访问。特别地,局部名字空间可以通过内置的 locals 函数来访问。全局(模块级别)名字空间可以通过 globals 函数来访问。

变量名解析:LEGB
Python中一切都是对象。每个对象都有一个名字,名字位于名字空间中,用名字变量引用对象。对象存在于内存中一段空间。每个对象在内存中都有地址。

在Python里类型本身是对象,和实例对象一样储存在堆中,对于解释器来说类对象和实例对象没有根本上的别。

所谓“定义一个函数”,实际上也就是生成一个函数对象。而“定义一个方法”就是生成一个函数对象,并把这个对象放在一个类的__dict__中。
函数对象结构定义:

typedef struct {
    PyObject_HEAD
    PyObject *func_code;   // PyCodeObject
    PyObject *func_globals;  // 所在模块的全局名字空间
    PyObject *func_defaults;  // 参数默认值列表
    PyObject *func_closure;  // 闭包列表
    PyObject *func_doc;   // __doc__
    PyObject *func_name;   // __name__
    PyObject *func_dict;   // __dict__
    PyObject *func_weakreflist;  // 弱引用链表
    PyObject *func_module;  // 所在 Module
} PyFunctionObject;
  • 对象基本上可以看做数据(特性)以及由一系列可以存取,操作这些数据的方法所组成的集合
  • 所有的对象都属于某一个类,称为类的实例
  • self指的是实例对象本身
  • 查看a是否是b的子类,可以使用issubclass(子类,超类);查看a的基类a.__bases__
  • 匿名函数lambda 用法lambda argument1,argument2:expression using argument.功能类似于def,只是不需要再去定义一个函数名。结果返回的是一个函数对象。
def add1(a,b):
     return a+b
add1(2,3)=> 5
g = lambda a,b:a+b
g(2,3)=>5

3.闭包

内层函数引用了外层函数的变量(包括它的参数),然后返回内层函数的情况,这就是闭包。形式上,在函数的内部定义函数,并引用外部变量。

函数对象是使用def语句定义的,函数对象的作用域与def所在的层级相同。比如下面代码,我们在line_conf函数的隶属范围内定义的函数line,就只能在line_conf的隶属范围内调用。

def line_conf():
    def line(x):
        return 2*x+1
    print(line(5))   # within the scope

line_conf()      #结果11
print(line(5))       # 错误,不能再外部调用内部函数line

可以在内部函数line()的定义中引用(非修改)外部的变量

def line_conf():
    b = 15
    def line(x):
        return 2*x+b   #引用外部变量b,b有时又叫做环境变量
    return line       # return a function object

my_line = line_conf()
print(my_line(5))       #结果为25
print(line_conf()(5))   #另一种引用方式,结果为25

一个函数line和它的环境变量b合在一起,就构成了一个闭包(closure)。在Python中,所谓的闭包是一个包含有环境变量取值的函数对象。环境变量取值被保存在函数对象的__closure__属性中
在python2.X中不能对外部变量赋值,而在python3中增加了nonlocal关键字

def Fun1():
    x=5
    def Fun2():
        x=x*x
        return x
    return Fun2()

Fun1()   #错误

为什么需要闭包?

def line_conf(a, b):
    def line(x):
        return ax + b
    return line

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5), line2(5))

这个例子中,函数line与环境变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函。line函数定义一种广泛意义的函数。这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多