python小记

特殊变量

双下划线开头,双下划线结尾的变量和方法

双下划线开头,双下划线结尾的变量是特殊变量,特殊变量是可以直接访问的,不要随意取这种形式的变量名。
双下划线开头,双下划线结尾的方法是特殊方法,有特殊用途,比如__init__会在初始化实例的时候被调用,__del__在引用计数为0时被调用,__eq__在进行==比较时被调用。

单下划线开头的变量

按约定俗称,单下划线开头的变量视为私有变量,比如_name,只在类内访问使用。但是事实上是可以访问到的。

class Student:
    def __init__(self):
        self._name = 'huihui'

Student()._name # huihui, 可以访问,虽然可以访问,但是按约定俗成请将其视为私有变量

双下划线开头的变量

严格意义的私有变量,只能在类内访问,无法在类外访问。
事实上只是python解释器将变量解释成了另外的变量名,但不同的解释器会进行不同的解释。
总之记住不要在外部访问私有变量即可。

class Student:
    def __init__(self):
        self.__name = 'huihui'
    
    def get_name():
         return self.__name

Student().__name # 报错,无法访问私有变量
Student._Student__name # 通过访问解释器改写后的变量名,可以访问到__name。但不建议这么做,因为不同的解释器会进行不同的解释。

s = Student()
s.__name = 'huiqiang' # 试图修改私有变量的值

s.__name # huiqiang, 表面上看是修改成功
s.get_name() # huihui, 实际上修改失败

s._Student__name = 'huiqiang',修改解释器改写后的变量名
s.get_name() # huiqiang,修改__name私有变量成功。但不建议这么做,因为不同的解释器会进行不同的解释。

del

__del__并不是在调用del的时候被调用,而是在变量的引用计数减为0时被调用,变量每del一次则计数减少1。

class SomeClass:
    def __del__(self):
        print("Deleted!")

x = SomeClass()
y = x
del x # 引用计数减1
del y # 引用计数再减1,此时引用计数为0,调用__del__,输出Deleted!
Deleted!

迭代

for语句的迭代

for i in range(10):
    print(i)
    i = 10    # 这里的i=10不起作用,因为回到for语句时,i会被重新赋值。for语句在每一次迭代中,把in后面的可迭代对象,分配给for后面的表达式。

dic = {}
s = 'miracle'
for i, dic[i] in enumerate(s): pass   # 相当于给dic赋值,以下标为key,字符为value。
dic # {0: 'm', 1: 'i', 2: 'r', 3: 'a', 4: 'c', 5: 'l', 6: 'e'}

生成器的迭代
生成器的其中一种构造方式,就是将[for 语句]构造list这样的语句中的[]改成(),之后就会得到一个类似懒加载的生成器,只有迭代的时候才会生成具体的值。

lis = [x for x in range(3)]
print(lis) # [0,1,2]

lis = (x for x in range(3)) # 生成器
print(lis) # 得到的是生成器对象:<generator object <genexpr> at 0x7fabeb080048>
for x in lis: print(x,end=' ') # 具体迭代时才输出:0 1 2
for x in lis: print(x,end=' ') # 输出空,因为在这之前已经迭代输出完毕,之后不再得到任何值

生成器如果有判断语句,在定义生成器的时候,in子句在声明时执行,条件子句在具体运行时才执行。

arr = [1, 8, 15]
g = (x for x in arr if arr.count(x) > 0)
arr = [2, 8, 22]

print(list(g)) # [8],in子句在声明时执行,条件子句在具体执行时才运行

生成器如果有多重for循环,仅仅最外层for会被立刻决定,其它for在具体运行时才执行。

arr_1 = [1, 2, 3]
arr_2 = [10, 20, 30]
gen = (i+j for i in arr_1 for j in arr_2)
arr_1 = [4, 5, 6]
arr_2 = [400, 500, 600]

print(list(gen)) # [401, 501, 601, 402, 502, 602, 403, 503, 603]

函数的迭代

循环内部中定义的函数用到迭代变量时,如果添加“函数”到list,实际调用时每次都返回最后的迭代变量。

funcs = []
results = []
for x in range(7):
    def some_func():
        return x
    funcs.append(some_func)
    results.append(some_func())

funcs_results = [func() for func in funcs] # [6, 6, 6, 6, 6, 6, 6]
results # [0, 1, 2, 3, 4, 5, 6]

powers_of_x = [lambda x: x**i for i in range(10)]
[f(2) for f in powers_of_x] # [512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

# 如果想返回每次迭代量,在函数中指定命名变量即可,因为此时在函数中定义了新的局部变量来接收迭代时不同的传入值,如下所示。

funcs = []
for x in range(7):
    def some_func(x=x):
        return x
    funcs.append(some_func)
funcs_results = [func() for func in funcs] # [0, 1, 2, 3, 4, 5, 6]

比较运算

相等简述
值相等,通过==确定。对于不可变对象int,float,str,bool而言,值相等就是字面意思上的值相等。对于可变对象而言,list,dict和set需要保证包含元素的每一元素都值相等,而自定义对象的==通过内部的__eq__函数来确定。默认的__eq__方法中,不同类则返回False,同类则比较对象地址的hash值。

哈希值相等,通过hash()==hash()来确定。对于不可变对象而言,值相同,则哈希值相同,与地址无关。对于可变对象而言,list,dict和set都是不可哈希类型,而自定义对象则是通过内部的__hash__函数来确定,默认__hash__是返回对象地址的哈希值。

地址相等(是否同一个对象),通过id() == id()或者is来确定。除了一些驻留机制导致的相同对象之外,一般重新定义的对象地址都是不等的。关于驻留机制在python中的引用传递,可变对象,不可变对象,list注意点中有所介绍。

# 自定义对象中,__hash__和__eq__的默认实现
def __hash__(self):
    return hash(id(self))
def __eq__(self, other):
    if isinstance(other, self.__class__):
        return hash(id(self))==hash(id(other))
    else:
        return False

自定义对象的相等
建议可以先看看python中的引用传递,可变对象,不可变对象,list注意点,其中已经介绍了一些相等比较的知识,包括可变对象的驻留,编译器对同行赋值的优化等,部分知识不再重复阐述。

1.0 == 1      # True, ==比较的是值是否一致, 1.0和1在数值上是一样的,所以返回True。
1.0 is 1      # False, is比较的是对象地址是否一致,可以用id()来获取对象的地址。
id(1.0) == id(1)    # 跟上一行是一样的,返回False, id表示对象的身份(地址),1.0和1是两个对象,地址不同

class Student:
  pass

Student() is Student()  # 返回False,自定义类型是可变对象,两次定义的对象地址是不同的
id(Student()) == id(Student())  # 返回True,这里比较神奇,是因为创建一个Student对象,id()后返回地址但是进行了对象销毁,第二次又重新创建,两次占用了同一个地址,关于这一点,下面来做进一步证实。

class Prof:
    def __init__(self): print('init', end=',')
    def __del__(self): print('del', end=',')

Prof() is Prof()   # 输出'init,init,del,del',是先创建两个对象,再比较。地址不同,返回False
id(Prof()) == id(Prof()) # 输出'init,del,init,del',创建一个对象,得到一个id后销毁,然后再创建一个对象,得到一个id后销毁,两个对象可以占用同一块地址。地址相同,返回True。

for i in range(10): print(id(Student()))  # 可以看到每次返回的值都是一样的,进一步证实上面的说明

Student() == Student()  # 返回False,自定义对象的==比较由__eq__决定,默认的__eq__方法中,不同类则返回False,同类则比较对象地址的hash值。

s1 = Student()
s2 = Student()
hash(s1) == hash(s2)   # 返回False,自定义对象的默认__hash__是返回对象地址的哈希值,因为两个对象的地址不同,地址的哈希值一般也不同(注意不同值的哈希值可能会相同,因为哈希冲突,但是一般不会发生)。
hash('oh!') == hash('oh!')  # 对于不可变对象,值相同,则哈希值相同,与地址无关。

hash(Student()) == hash(Student())  # 返回True,这里返回True的原因,跟上面的id(Student()) == id(Student())的原因一致。

链式操作时的比较
注意出现两个逻辑运算符,又没有括号限定时,当两个逻辑运算都为True时,才返回True。具体看例子。

(False == False) in [False] # False,常规操作
False == (False in [False]) # False,常规操作
False == False in [False]   # 等价于(False == False) and (False in [False]), 返回True

1 > 0 < 1 # 等价于(1>0) and (0<1),返回True
(1>0) < 1 # 返回False,(1>0)返回True,True在数值上等于1
1 > (0 < 1) # 返回False

True + 10 # 11
False + 10 # 10

字典

字典dict
python中的地点dict通过“哈希值是否相等”和“值相等”和来确定key。

dic = {}
dic[1.0] = 'python'
dic[1] = 'magic'

dic[1.0]                # 输出'magic',字典认为1和1.0是同一个key。
1.0 == 1                # True
hash(1.0) == hash(1)    # True

dic    # {1.0: 'magic'},输出dic时,key保留第一次加入时的形式,即1.0。如果想把key改成1的形式,需要先del再重新赋值。

1.0 in dic    # True
1 in dic      # True
1.0 + 0j in dic  # True,小数1.0,整数1,复数1.0+0j,三者值相等且哈希值相等。

值相等和哈希相等
值相等,通过==确定。对于不可变对象int,float,str,bool而言,值相等就是字面意思上的值相等。对于可变对象而言,list,dict和set需要保证包含元素的每一元素都值相等,而自定义对象的==通过内部的__eq__函数来确定。默认的__eq__方法中,不同类则返回False,同类则比较对象地址的hash值。

哈希值相等,通过hash()==hash()来确定。对于不可变对象而言,值相同,则哈希值相同,与地址无关。对于可变对象而言,list,dict和set都是不可哈希类型,而自定义对象则是通过内部的__hash__函数来确定,默认__hash__是返回对象地址的哈希值。

# 自定义对象中,__hash__和__eq__的默认实现
def __hash__(self):
    return hash(id(self))

def __eq__(self, other):
    if isinstance(other, self.__class__):
        return hash(id(self))==hash(id(other))
    else:
        return False

类变量的继承
类变量(和实例变量)会被处理成字典形式,如果类中没有该变量,则会从父类中寻找。

class A: x = 1
class B(A): pass
B.x = 2
A.x, B.x # 1, 2 

class A: x = 1
class B(A): pass
A.x = 2
A.x, B.x # 2, 2 

try

finally
无论如何,finally都会执行,即使try里面出现了return,break,continue。
finally中如果有return返回,那就是函数的最终返回,即使try块之后还有return(后面的return不会执行)。
函数的返回值是由最终的return决定的,所以finally中的return是最终的return

def func():
    while True:
        try:
            return 'try'    # 此处换成continue或break,最终结果一样
        finally:
            return 'finally'
    return 'end'

print(func())    # 输出'finally'

Reference

  1. python中一些鲜为人知的特性:What the f*ck python
  2. 廖学峰python教程
posted @ 2024-01-06 21:05  PilgrimHui  阅读(20)  评论(0编辑  收藏  举报