探索python3高级知识 - 超长干货(附源代码及本书markdown版本)

Python3高级知识

全书内容:amazing-python / Study / study_program / book
源代码:amazing-python / Study / study_program


开始吧!


首先我们从Python鲜为人知或是高级语法开始学习。 ## for ... else ...

我们所熟知的python中的for循环的用法是这样的:

for i in range(1,10):
    print(i)
#或者是这样:
for i in [1,2,3]:
    print(i)

然而真的就只有这些吗?答案是否定的。事实上,Python也给for循环增加了else语法,看下面的例子:

for i in range(1,10):
    if i==5:
        break
else:
    print("1.without break")
for i in range(1,10):
    if i==15:
        break
else:
    print("2.without break")

输出为:

2.without break

可以看出else语句在没有break时执行。

函数注解

尽管Python是一门动态类型语言,但在Python3.5中加入了类型检查功能,代码如下:

def add(num1:int,num2:int)->int:
    return num1+num2

你可以使用parameter:type->type来表示参数类型和返回类型。

你也可以使用__annotations__属性获取这些type。当然,由于Python强大的动态特性,你可以现场修改这些type,(虽然这明显不是一个很好的实践):

print(add.__annotations__)
print(add(1,2))

add.__annotations__["num1"] = str
add.__annotations__["num2"] = str
add.__annotations__["return"] = str

print(add.__annotations__)
print(add("hello ", "world"))

结果:

{'num1': <class 'int'>, 'num2': <class 'int'>, 'return': <class 'int'>}
3
{'num1': <class 'str'>, 'num2': <class 'str'>, 'return': <class 'str'>}
hello world

继续我们的高级语法:

with语句

相信大家对下面的代码已经很熟悉了:

with open(“test.txt”,”a”) as f:
    f.write(“Hello,open!”)

上面这段代码的含义也就是打开test.txt文件并写入Hello,open!,然而大家有没有想过with真正有什么用呢,下面是一个不用with的例子:

try:
    f=open(“test.txt”,”a”)
    f.write(“Hello,open!”)
except:
    pass
finally:
    f.close()

很明显使用with后代码量减少了,也使代码更具可读性了,接下来我们就来讲讲怎么自己实现一个类来和with结合使用:

class With():
    def __enter__(self):
        print("enter!")
        return self
    def operate(self,name,bug):
        print("operate " + name + "!" + bug)
    def __exit__(self,exc_type,exc_value,traceback):
        print("exc_type : " + str(exc_type))
        print("exc_value : " + str(exc_value))
        print("traceback : " + str(traceback))
        print("exit!")
bug=1
with With() as opr:
    opr.operate("nick",bug)

上述代码执行结果为:

enter!
exc_type : <class 'TypeError'>
exc_value : must be str, not int
traceback : <traceback object at 0x7fd9130c16c8>
exit!
Traceback (most recent call last):
  File "/tmp/225769800/main.py", line 16, in <module>
    opr.operate("nick",bug)
  File "/tmp/225769800/main.py", line 6, in operate
    print("operate " + name + "!" + bug)
TypeError: must be str, not int

首先我们使用__enter__方法表示开始的一些操作,接下来我们可以在类中定义我们进行的操作,最后使用__exit__方法保证退出,可以看出虽然抛出了错误,程序依然执行完了最后的__exit__方法,那么如果我们不想让程序抛出错误呢?

class With():
	def __enter__(self):
		print("enter!")
		return self
	def operate(self,name,bug):
		print("operate " + name + "!" + bug)
	def __exit__(self,exc_type,exc_value,traceback):
		print("exc_type : " + str(exc_type))
		print("exc_value : " + str(exc_value))
		print("traceback : " + str(traceback))
		print("exit!")
		return True
		
bug=1
with With() as opr:
	opr.operate("nick",bug)

执行结果为:

enter!
exc_type : <class 'TypeError'>
exc_value : must be str, not int
traceback : <traceback object at 0x7f64bb550708>
exit!

只要我们在__exit__方法中return True程序就不会抛出错误了。


yield

yield也就是生成器,我们用代码说话:

def yield_test():
    for i in range(10):
		yield i
def normal_test():
	num_list=[]
	for i in range(10):
		num_list.append(i)
	return num_list

yield_nums=yield_test()
print(yield_nums)
for num in yield_nums:
	print(num)

你会得到如下结果:

<generator object yield_test at 0x7f4880aeb410>
0
1
2
3
4
5
6
7
8
9

从上面这段代码来看,明显yield的实现更加简洁,再看一个例子:

def yield_test():
	for i in range(10):
		yield i
yield_nums = yield_test()
while True:
	try:
		print(yield_nums.__next__())
		print(next(yield_nums))
	except StopIteration as e:
		print(e)
		break

我们可以使用next来获取下一个值,在没有yield时会触发StopIterarion错误。

其中yield还有一个其他的作用:节约你的内存。yield只在你需要时计算下一个值,这里我们不加叙述,大家可以参考内置库itertools中的一些函数。


装饰器

装饰器可以精简代码,使代码更可读,它可以用来装饰函数或者类,看一个简单的例子吧:

def do():
    def real_decorator(function):
        def wrapped(*args,**kwargs):
            result=function(*args,**kwargs)
            print("l am a decorator!")
            return result
        return wrapped
    return real_decorator

@do()
def hello():
    print("Hello World!")
hello()

这段代码运行结果如下:

Hello World!
l am a decorator!

装饰器的语法是在函数的前面使用@wrapper,我们来逐步分析一下这段代码:

result=function(*args,**kwargs)

这段代码表示执行这个函数。

print("l am a decorator!")

这是我们在函数执行完后做的操作。

return result

返回函数运行结果。

    return wrapped
return real_decorator

返回这些里面的函数。

接下来我们就能看到在函数执行好后输出了l am a decorator了。接下来我们来写一个更加实用的装饰器,计算函数运行时间:

import time
def time_c(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        elapsed = (time.time() - start)
        print("{} : Time used: {}".format(func.__name__, elapsed))
    return wrapper

@time_c
def sleep_test():
    time.sleep(2)
sleep_test()

结果为:

sleep_test : Time used: 2.000603675842285

注意一点,func.__name__即是函数的名字。


yield和send

我们之前学习了yield作为生成器的语法和好处,其实yield还有另一种用法,看例子:

def yield_send():
    print("please tell me what you want to say")
    while True:
        words= yield
        print("l receive your words : " + str(words))
send_test=yield_send()
next(send_test)
send_test.send("hello")

我们直接使用了words = yield,并使用send给yield传送信息,注意:要先使用next,不然会报错不能给一个刚开始的函数传递值。 结果如下:

l received your words : hello

super

首先我们从一个简单的类开始:

class me:
    def hello(self):
        print("Hello,everyone!")
class my_friend(me):
    def hello(self):
        me.hello(self)
        print("l am his friend")
my_friend().hello()

这会得到如下结果:

Hello,everyone!
l am his friend

那么我们如何使用super重新完成这段代码呢,这样做:

class me:
    def hello(self):
        print("Hello,everyone!")
class my_friend_with_super(me):
	def hello(self):
		#super(my_friend_with_super,self).hello()
		super().hello()
		print("l am his friend")		
my_friend_with_super().hello()

我们用super达到了同样的效果。注释掉的那个是简化形式。super可以调用超类中的方法,再来看一个稍微难一点的例子,与classmethod结合使用:

class Tea:
	def __init__(self, things):
		self.things = things
	def __repr__(self):
		return self.things
	@classmethod
	def need(cls):
		return cls(["sugar", "pearl", "water"])
class PearlTea(Tea):
	@classmethod
	def need(cls):
		needed = super().need().things
		needed += ["pearl"] * 2
		return needed
mytea = Tea.need()
mypearltea = PearlTea.need()
print(mytea.things)
print(mypearltea)

你会得到:

['sugar', 'pearl', 'water']
['sugar', 'pearl', 'water', 'pearl', 'pearl']

我们从中华可以得到,在super只提供第一个参数时,它将返回一个unbound(未绑定的类型)。super是一个较难的知识点,下次我们继续。


property

property装饰器会会让我们定义设置包括获取属性等的操作,下面是一个比较经典的例子,正方形:

class property_test:
	def __init__(self,height,weight):
		self.w = weight
		self.h = height
		
	def __repr__(self):
		return f"{self.__class__.__name__}({self.weight},{self.height})"
		
	@property
	def weight(self):
		return self.w
	
	@weight.setter
	def weight(self,new_weight):
		self.w = new_weight
	
	@property
	def height(self):
		return self.h
	
	@height.setter
	def height(self,new_height):
		self.h = new_height

you=property_test(50,170)
print(you.weight,you.height)
you.weight = 55
print(you)
you.height = 175
print(you)
print(help(property_test))

看看结果吧:

170 50
property_test(55,50)
property_test(55,175)
Help on class property_test in module __main__:

class property_test(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self, height, weight)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __repr__(self)
 |      Return repr(self).
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  height
 |  
 |  weight

None

通过property装饰器我们可以便捷地设置和获取属性,快使用起来吧。

*args and **kwargs

*args和kwargs帮助我们自定义参数,*args接受像 ,a,这样的,而kwargs接受像 ,a = b, 之类的参数:

def kwargs_args(name,*args,**kwargs):
	print(name)
	print(args)
	print(kwargs)

kwargs_args("nick", "foo", "bar", sex="male")

上述代码得到的结果是:(我想不用解释了吧)

nick
('foo', 'bar')
{'sex': 'male'}

列表推导式

所谓列表推导式也就是更方便的创建(带有特点条件的)数组,比如说我们想创建一个1到100间偶数的列表,不用列表推导式:

lst = []
for i in range(10):
	lst.append(i)

这会创建一个0到9的列表,看看列表推导式的写法

nums = [i for i in range(10)]

或者更复杂一些

nums = [i**2 for i in range(10)]   #创建0-9的平方
dict_nums = {i: i**2 for i in range(10)}   #字典实现

或者加入一些条件在后面,偶数列表:

if_nums = [i for i in range(10) if i % 2 == 0]

lambda关键字

lambda用于创建一些简单的函数,下面是一个正常的例子:

def lambda_add(a, b):
    return a + b

这么一个简单的函数可以使用可读性更好的lambda解决:

lambda_add = lambda a, b:a + b

接下来你就可以按一个正常的函数使用它了。

这次我们介绍一个其他的用处,就是在tkinter设置button的command处只能设置没有参数的函数的情况,只能这样

def no_para(): pass
a = top.Button("top", text = "with no para", command = no_para)

用了lambda,我们可以使用带参数的函数了!

def have_para(para): print(para)
a = top.Button("top", text="have para", command=lambda:have_para("para here"))

lambda有用吧!


*和变量

有时候我们会在变量命名前加上*,这代表什么意思呢?看一个例子你应该就明白了:

first, second, *third = 1, 2, 3, 4
print(first, second, third)
first, *second, third = 1, 2, 3, 4
print(first, second, third)

看看结果吧,其实就是取了剩余的部分:

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

int

这次我们来点有趣的小例子,我们来探究一下int类的妙用。首先int类的self有一个叫做numerator的玩意,我们用代码来解释一下这到底是什么:

class test(int):
	def __call__(self, x):
		print(x, self, self.numerator)
		return test(self.numerator + x)
a = test()
a(1)(2)(3)

多个括号即会触发多次call,上述代码的运行结果为:

1 0 0
2 1 1
3 3 3

从中我们可以看出numerator能帮我们累计一些东西。

我们可以再来看看int类中的一段定义numerator的代码

numerator = property(lambda self: object(), lambda self, v: None, lambda self: None) 
# default  """the numerator of a rational number in lowest terms"""

除了上面这种写法,我们还可以把这个类包在函数中:

def add(x):
    class AddNum(int):
        def __call__(self, x):
            return AddNum(self.numerator + x)
    return AddNum(x)

print(add(1)(2)(3)(4))

显而易见,结果是10


assert

assert是一种能让你快速进行测试的工具,它是一个关键字。用法如下

assert expression [",", expression]

如果还是有点蒙,那么一个例子可能会让你明白:

def hello(name: str):
    assert len(name) > 1, "your name is too short!"
    print("hello,", name)


hello("nick")
try:
    hello("a")
except Exception as e:
    print(e)

结果为:

hello, nick
your name is too short!

我们来仔细看看这个assert语句,首先跟着的是条件,也就是名字长度要大于1,接下来是一个字符串。连起来就是条件不成立,assert把错抛。

raise

我们写程序时是不是会遇到一些bug,那么我们怎么自己来定义并抛出错误呢,这时候就要用到我们的raise关键字了:

import traceback
class ExitBye(Exception):
    pass

try:
    print(1 / 0)
except Exception as e:
    try:
        raise ExitBye("have an error with raise") from e
    except Exception as e:
        traceback.print_exc()

首先我们定义了一个自己的错误,这个错误也就是继承Exception类的,这是一个“错误”的基类。

随后我们进行了一次错误的操作,触发了ZeroDivisionError,我们使用except避免了这个错误,并再次进入到try中,我们的raise就出场了。

raise的基本语法

raise what_error("error message")

那么大家可能就疑惑于我们后面的from e是什么意思了,这就代表是上面这个e错误触发了下面的错误,看看结果吧,我想你会明白的:

Traceback (most recent call last):
  File "/tmp/711293792/main.py", line 7, in <module>
    print(1 / 0)
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/tmp/711293792/main.py", line 10, in <module>
    raise ExitBye("have an error with raise") from e
ExitBye: have an error with raise

我们在中间可以看到“上面的错误直接导致了下面下面的错误”,这也就印证了我们的解释。

最后我们使用traceback和print_exc函数获取完整的错误信息,也就是打印出来的那些啦


nonlocal

又是python的一个关键字,nonlocal。global相信大家都知道,那么global和nonlocal有什么区别呢?首先我们来看看global:

global是用来引用全局变量的,比如说下面这个例子

test = 1
def global_1():
	print(test)
global_1()

这个例子会正常的输出1,但并非次次都如此:

test = 1
def global_2():
	test += 1
	print(test)
global_2()

上面这个例子会触发UnboundLocalError,这时候global就发挥作用了:

test = 1
def global_2():
    global test
	test += 1
	print(test)
global_2()

这样我们的函数又能正常工作了。

接下来我们来看看nonlocal,nonlocal是用来使用外部变量的,我们看一个例子:

def counter_nonlocal():
    count = 0

    def count_add():
        nonlocal count
        count += 1
        return count
    return count_add

def counter_test():
    num = counter_nonlocal()
    print(num())
    print(num())

counter_test()

这个例子会输出1 2,这里的nonlocal声明后我们使用的就是我们在外部定义的count了,也就起到了累加的作用。

这个语法比较特别,就是…,这是一个可以用来代替pass的方法额,尽管我认为pass更好:

print(type(...))


def hello():
    ...

结果:

<class 'ellipsis'>

就这样结束啦,希望大家能从中学习到学到,尽管讲解得没有那么详细,但是也算大开眼界了,如果你从中收获到了许多的话,别忘了给我点个赞哦!

我的GitHub

本书的GitHub

本书的GitHub所处的项目

你可能感兴趣的:(技术杂谈)