全书内容:amazing-python / Study / study_program / book
源代码:amazing-python / Study / study_program
我们所熟知的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 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也就是生成器,我们用代码说话:
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作为生成器的语法和好处,其实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
首先我们从一个简单的类开始:
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装饰器会会让我们定义设置包括获取属性等的操作,下面是一个比较经典的例子,正方形:
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和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用于创建一些简单的函数,下面是一个正常的例子:
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类的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 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把错抛。
我们写程序时是不是会遇到一些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函数获取完整的错误信息,也就是打印出来的那些啦
又是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'>
就这样结束啦,希望大家能从中学习到学到,尽管讲解得没有那么详细,但是也算大开眼界了,如果你从中收获到了许多的话,别忘了给我点个赞哦!