python中奇怪的知识又增加了

本文依据该作者内容进行思考与创作,进一步学习与使用,添加个人理解与应用,由于作者使用的是py2的语法,我这里将例子转为py3语法,属于半转载内容,有兴趣的同学可点击链接前往该作者处查看,给个star,该文持续更新,直到看完整文。

传参数问题

a = 1
def fun(a):
    print("func_in",id(a))   #func_out 140721063826240
    a = 2
    print("re-point",id(a), id(2))   # re-point 140721063826272 140721063826272
    
print("func_out",id(a), id(1))  # func_out 140721063826240 140721063826240
fun(a)
print(a) # 1


a = []
def fun(a):
    print("func_in",id(a))  # func_in 2732600561160
    a.append(1)
print("func_out",id(a))     # func_out 2732600561160
fun(a)
print (a)  # [1]

可以看到第一个例子传参调用函数时变量在函数里面改变了,但是并未影响到函数外的变量,属于局部变量,开辟了新的地址保存,所以打印外部变量时值还是1。根据作者的内容我们新增一个例子变成全局变量试试。

a = 1
def fun():
    global a
    print("func_in",id(a))   #func_out 140721063826240
    a = 2
    print("re-point",id(a), id(2))   # re-point 140721063826272 140721063826272
    
print("func_out",id(a), id(1))  # func_out 140721063826240 140721063826240
fun()
print(id(a)) #140721063826272
print(a) # 2

可以看到将a变为全局变量时,传入的是原地址,将a的值改变后开辟新地址保存,外头的变量a地址也一并变为新地址。这就很好的说明了作者说的不可更改对象,例2的列表地址没变是因为它属于可更改对象,而我们无论是局部变量还是全局变量,在面对不可更改对象时都会开辟新的地址赋值。

总结类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而 list, dict, set 等则是可以修改的对象。

类变量和实例变量

类变量是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。例如下例中,num_of_instance 就是类变量,用于跟踪存在着多少个Test 的实例。
实例变量实例化之后,每个实例单独拥有的变量。

class Test(object):  
    num_of_instance = 0  
    def __init__(self, name):  
        self.name = name  
        Test.num_of_instance += 1  

if __name__ == '__main__':  
    print(Test.num_of_instance)   # 0
    t1 = Test('jack')  
    print(Test.num_of_instance)   # 1
    t2 = Test('lucy')  
    print(t1.name , t1.num_of_instance)  # jack 2
    print(t2.name , t2.num_of_instance)  # lucy 2
    
#--------------------------------例子分割线--------------------------------#
class Test(object):  
    num_of_instance = 0  
    def __init__(self, name):  
        self.name = name  
        self.num_of_instance += 1  

if __name__ == '__main__':  
    print(Test.num_of_instance)   # 0
    t1 = Test('jack')  
    print(Test.num_of_instance)   # 0
    t2 = Test('lucy')  
    print(t1.name , t1.num_of_instance)  # jack 1
    print(t2.name , t2.num_of_instance)  # lucy 1

#--------------------------------例子分割线--------------------------------#
class Person:
    name=[]

p1=Person()
p2=Person()
p1.name.append(1)
print(p1.name)  # [1]
print(p2.name)  # [1]
print (Person.name)  # [1]

在第一个例子当中类变量就是num_of_instance,实例变量就是name,不同之处在于初始化__init__(),类变量我们在初始化的时候直接赋值给类名调用变量,这样就相当于上面的全局变量,而每个实例的变量 相当于局部变量,仅在自己实例有效。而我们在将类变量变为self调用时会发现该变量变成了实例变量,所以我们可以就此理解一下self的作用与类名直接调用的作用。self的意思是实例对象本身,所以变量的作用域取决于调用的类型。

总结如果是类调用就是类变量,实例调用就是实例变量,对应全局变量与局部变量。变量的调用参照传参数问题可变与不可变。

字典推导式

将列表转为字典,列表元素要包含key跟value。还有一种是两个列表变为字典,详情传送

# 字典推导式
a = [('name','zhangsan'),('age','11'),('phone','a')]
d = {key: value for (key, value) in a}
print(d) #{'name': 'zhangsan', 'age': '11', 'phone': 'a'}

字符串格式化:%和.format

.format在许多方面看起来更便利,对于%最烦人的是它无法同时传递一个变量和元组,你可能会想下面的代码不会有什么问题:

"hi there %s" % name

但是,如果name恰好是(1,2,3),它将会抛出一个TypeError异常。正确写法:

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

这里作者并没有过多赘述,我这里来尝试应用一下。

a=1.123
b='123'
c=(1,2,3)
d=[1,2,3]
e={'a':1,'b':2,'c':3}
print("这是a:%s"%a)#%s表示字符模式  这是a:1.123
print("这是a:%.2f"%a)#%.2f表示float模式,保留两位小数 这是a:1.12
print('%s'%(c,))#(1, 2, 3)
print("这是e:a的值%(a)s,b的值%(b)s,d的值%(c)s,"%e)#%字典打印格式

# :b,二进制。:o,八进制。:d,整型。:x,16进制(小写)。:X,16进制(大写)。:%,百分数(默认保留6位小数)
strs = "二进制:{:b},八进制:{:o},整型:{:d},十六进制小写:{:x},大写:{:X},百分数:{:%}".format(10,10,10,10,10,10,10,10)
#二进制:1010,八进制:12,整型:10,十六进制小写:a,大写:A,百分数:1000.000000%
print(strs)

print("{0[0]},{1[1]},{2[2]}".format(b,c,d)) #字符串,元组,列表 1,2,3
print("{a},{b},{c}".format(**e))#字典打印格式1,2,3

迭代器与生成器

将列表生成式中[]改成() 之后数据结构是否改变? 答案:是,从列表变为生成器。

L = [x*x for x in range(10)]
print(L) #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
g = (x*x for x in range(10))
g #<generator object <genexpr> at 0x0000027C3BC7A318>

列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。因此,没有必要创建完整的列表(节省大量内存空间)。在Python中,我们可以采用生成器:边循环,边计算的机制—>generator

 

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象

yield

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)
    
for i in odd():
    print(i)
"""
step 1
1
step 2
3
step 3
5
"""

杨辉三角

#杨辉三角
#           1
#          / \
#         1   1
#        / \ / \
#       1   2   1
#      / \ / \ / \
#     1   3   3   1
#    / \ / \ / \ / \
#   1   4   6   4   1
#  / \ / \ / \ / \ / \
# 1   5   10  10  5   1

def triangles(maxs):
    a=[1]
    n=0
    while n<maxs:
        b=[a[0]]
        if n==0:
            yield b
        for i in range(1,len(a)):
            b.append(a[i-1]+a[i])
        b.append(a[len(a)-1])
        a=b
        yield b
        n+=1
for i in triangles(10):
    print(i)
"""
[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]
[1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1]
"""

 

 

猜你喜欢

转载自blog.csdn.net/Cxk___/article/details/105805690