Python“奇葩”语法你都见过哪些
初学Python会遇到很多自己从未见过的语法,这些语法在其它编程语言中都没见过,有些语法是Python独创的,有些语法只是改变了书写形式自己又造一种新形式,这里罗列一下我认为Python中比较特殊的语法。
1. pass占位符
当函数或者代码块中不需要写任何代码时需要显式的写pao M w : t # Kss, pass表示2 ? B z #占位符, 如果不写会报语法错误。
在其它编程语言中如果一个方法是空的给出一对大括号{}就表示方法体是空的,但是Python的方法体不是用大括号表示的而是用冒号+缩进方式表示的,Python为了表示一个空的方法体发明了这个pass占位符。
个人认为4 e J - +使用缩进方式而不是使用大括号{}形式表示方法体不好: - 使用缩进方式当粘贴复制代码时就会丢失缩` ) j v u进,这是一件很尴尬的事情,粘贴好代码还要非常认真的看一f * E . S a Q下代码缩进是不是对的 - 使用 t I缩进方式是一个很随意的决定。具体典故好像是随2 | p * 8 X v ] s便问了几个不懂开发的人问哪种方式他们认为最好,结果选缩进方式的比较多,后来就这么草率的决定使用缩进方式了。
public void test() {
// java空方法体
}def func():
pass
if True:
pass2. _ _ init_ _.py
Python中的init.py作用:
1. 标识一个普H N x 2通的文件夹为包Package。
在编程语言中一般都使用package来管理源文件,用于对源文件分类,防止命名冲突,其实包就是一个普通的文件夹。像Java直接在src目录下New Pa% P l ockage就创建了一个包,src表示源文件位置。但是Python创建Package时` 6 t p不但会创建一个文件夹,还会创建一个init.py的文件,这{ O m A S _ w } U个文件的作用用于标识此目录是一个pacO 4 W w z ?kage而不是L Y D一个普通的文件夹。像Java直接就约定好源文件src目录了,maven约定好4 m 1 M h % y s Z了测试类test和f @ t @ 0 ; = (资源文件resource的位置。但Python没有任何约定,没有约定的后果就是你创建一个文件 , Z B : ,夹不知道这个文件夹究竟是一个包还是一个普通的文件夹呢,Python的解决办法是如果你要创建包那么这个包下必须有init.py文件这个文件才被认为是package,如果没有这个固定的文件就认为这是一个普通的文件夹q j @ z b *。
在开发中大家普遍认x H V T 5 0 g B为约定大于配置,如果不约定就会非常的灵活,太灵活也不是什么好事。如果一个项目有很多个包,每个包下都要有个init.py文件,个人感1 o H r s S觉显得臃肿,不优雅,个人不太理解这种设计。
2. 使用通配符*导入包下的所有模块时需要在init.py文件中定义一个all属性,属性值是可以被允许导入的列表,只有列举出来的模块才允许被导入。
# 在Python3.8中如果models包下__init__.py文件没有定义__a2 ) E G I nll__,那么通过*号不会导入任何模块
test.py
from models import *
print(dir())# 如果在models.__init__.py文件t p n l ( m N中定义了__all__, 那么只有被列举的值才允许被导入
__F 4 6 c : kall__ = views.test.py
定义了all所以只有model, model2被导入进来了
from models import *
print(dir())3. package每次被导入时都会执行init.py文件中a X 7的代码。
iB A _ C C - m }nit.py文件也是一个普通的py源码文件,也可以写任何python代码。可以写个print测试一下。
models.init.py py print(\'package models imported...\')
views.test.py ```py
执行该语句就会执行models.init.py中的prinO c b } ? X G st代码
from models import * ``8 @ G`
注意:每次执行from models import *都会执行models.init.py中的print代码,这样设计不知道什么场景下会被使用到。很多资料都说不建议在.init.py文件中写业务代码。= ( R 6
3. 正索引和负索引
python中支持两种索引,正索引和负索引(负索引语法还是第一次A 6 a ! K , 6 !接触到,虽然第一次接触但是并不反感这个语法,反而感觉这种语法很好很赞成),可以用于字符串{ 9 k t z U s i `、列表、元组: - 头索引(正0 h g _ I索引):从左边开始,最左边第一个下标为0,最左边第二个下标为1 - 尾索引(负索引):从右边开始,最右边第一个下标为-Y S J | % w1,最右边第二个下标为-2
索引:一个下标称之为t H % g 6 q x e 7索引 切片:两个下标称之为切片
```py
foo+ . X = \'abcdef\' fooW H V | o c \'a\' foo \'ab\' foo \'f\' foo \'F o = Dde\' foo \'def\' ```
>>> l =
>>> l = 0
&gG x V ? R ) 1t;y $ y>> l
>>> l =
>>> l
>>> l =
>>> l
>>> del l
>>> l
4. while else 和 for eK $ H Slse
循环代码块中可以有else代码块(我也是第一次见到这种m u z 7语法),当循环条件为False时执行3 U } V n x 0 Relse,如果循环体L d h w 8 9 :中遇到break时不执行else代码块的。
i = 0
while i < 5:
print(f\"{i} 小于5\")
i += 1
else:
print(f\"{( / } G J U Mi} 大于等于5\")
# 执行结果
0 小于5
1 小于5
2 小于5
3 小于5
4 小于5
5 大于等于5f) a Xor i in :
print(f\"i={i}\")
if i == 2:
break
else:
print(f\"for ended, current i = {i}\")
# 执行结果
i=1
i=25. 三位运算符
Java中的三位运算^ B j符是用 ? : 来实现的j | 0 y。Python中是用if elo r % w ) } N ,sP & [ p . ae来实现的(突然换种语法感觉不太H M T适应q 0 5 e) ```java int a = 2; int b = 1;
int reA _ X b ; k A #sult =N k * B G - a >= b ? a : b; System.out.println(result); ```
a = 2
b = 1
# if条件成立返回if前面的表达式,if条件不成立返回else对应的表达式
result = a if a >{ t % c t O S + }= b else b
# 2
print(result)6. 文档注释doctest
Python中的文档注释dj ~ * 7octest- = x q是写在模块的第一行或者函数的第一行使用三个引, ` n号写的一段字符串注释,注释中一般包括函数作用的解释以及给出如何使t o [ Q +用函数的示例程序,示例程序用的是Python SX b + ~ Zhell格式。
文档测试的作用: - 给出一些示H ^ 8例代码,便于快速了解使用 - 通过工具来生成文档,类似于Java中的Javadoc
def abs(? h 1 l u , } e #n)# 9 a w ? V G 0 &:
\"\"\"
Function to get absolute value of number.
usd B + 1 e Zage::
>>> abs(1)
1
>>> abs(-1)
1
>>> abs(0)
0
\"\"\"
return n if n >= 0 else -n
if __name__ == \'__main__\':
import doctest
# 执行文档测试
doctest.testmod(C h P v o E _ t)7. del 关键字
del 关键字用于删除: m S M 列表、字典中的元素,或者删除变量。 del这种用法即不像面向过程的语法(面向过程一般都是调用函数),也不是面向对象的语法(+ ) i ) g x ^ ! 9面向对象一般都是通过对象来调用方法),del这种语法比较像Shell命令行语法。
list =
del list
pri{ u 9 O * 0 I vnS r $ + g j Zt(list)
del list8. 异常 try except else finally
在其它编程语言中一般都是try catch finally, Python用的不Q L | R a ~是catch而是except,并且python还支持else代码块。elU G n + ; 2 f ise子句将在try子句没有发生任何异常的时候执行
Python中的大部分异常类都是以V u J 7 3 UError作为后缀的,如Indentatx D tionError、AssertionError、ValueError、TypeError、ZeroDivisionError, 也有些异常没有啥后缀如StopIteration,感觉命名不统一。
# 自定义异常
c| F P T u flass ServerException(Exception):
def __init__(self, value):
self.value =2 @ ! f ? value
def __str__(self):
return repr(self.vj S D ?alue)
tryC M 1:
raise ServerExceptU P [ d { i f ,ion(C % t [ - L一个自定义异常\")
except ServerException as err:
print(\"ServerException: {0}\".format(err))
except ValueError:
print(\"ValueEu P w Drror\")
except:
print(\"unexcepted error\")
# 不处理Y B G异常,再次抛出去
raise
else:
prin_ ` v {t(\"else\")
finaln n v O _ T K Q Rly:
print(\"finally\")else的作用是try代码块正常执行完毕(没有报错)会执行else代码i S r 1 n块。那么把else代码块中的代码单独k 2 k l放在else代码块中和放在try代码块的最后部分有什么区别???
示例1:要else代码块,try代码块执行完再毕执行else代码块,如果else代码块报错E s ; i 0 3 R l q那么except是不会捕获异常的,那就继续往外抛出异常。py try: print(\"try\") exc8 R ~ } C w :ept: print(\"Exception\") else: assert 1 == 2 finally: print(V ! i i A b\"finally\")
示例2:不要else代码块,直接将else代码块中代码放在try代码块的最后面。0 U x f如果else代码块中的代码发生异常,会被except捕获,执行excep8 2 么函数执行的结果可能会和你预期的结$ ) m , w v (果不一致。) F - ` [ ylist传参属于引用传递而不是值传递。
list =
def change_list(l):
l.append(3)
change_list(list)
#
print(list)def append_end(l=):
print(\"id(l)=\", id(l))
lw n 6 + x W S k /.append(\"END\")
return l
#
print(append_end())
\"\"\"
第二次调用参数l的内存地址竟然和第一次的完全一样。
这说明一个问题:参数默认值只会分配一次内存,如果不传参数所有函数调用都使用同一块内f d c存,这就? E S f . w (解释了第二次函数调用为啥是X 2 l而不是
\"\"6 G / (\"
#
print(append_end())解决函数参数默认是可变类型带来的异常结果就是将函数参数默认值改为不可变类型None ```py def append_end2(l=NF : T ] x k / &one): prC + D U B 6 | oint(\"id(l)=C O K @ x ] E s\", id(lo 6 e M _ Q)) iA m f n A 1 8f l is None: # 如果没有X p p i k传值,将l显式清空 l = J 2 w l.append(\"END\") return l
print(append_end2())
print(append_end2()) ```
11. global和noG z ;nlocal
当内部作用域想修改外部作用 a o ( L a ! E :域的变量时,就要显式使Y & n用globalX L R和nonlocal关键字。原因是如果在局部作用于声明一个和全局作用于相同的名字的变量,Python会认为局部变量和全局变量是两个不同的变量(虽然名字相同),要告诉H u : g T C 1 ) WPython* } e # A 4这是同一个变量就要使用关键字来修饰。
- 如果函数内部要修改全局变量,需要使用global关键字修饰一下全局变量;
- 如果要在嵌套函数内部修改外层函数中的变量,需要使用nonlocal关W P u c b 5 $键字来修饰一下;
foobar = 1
def func():
foo? g ~ q a *bar = 10
prij 5 V J }nt(f\"func foobar={foobar}\")
func()
priP u Y P 9 = s % Ont(foobar)foobar = 1
def func():
global foobar
foobar = 10
print(f\"func foobar={foobar}\"+ 4 w)
# func foobar=10
func()
# 10
print(foobar)def outer():
foobar = 1
def| J n O 0 ~ innN ~ T q `eb X zr():
nonlocal foobar
foobar = 10
print(f\"inner foobar={foobar}~ G k ( 7 U 7 _ $\")
inner()
print(f\"outD C _ 3 Wer foobar={foobar}\z 8 Q I % $ - 5 Z")
outer()12. 嵌套函数
函数定义可以嵌套函数,这在} G , p I U 7 ? f其它语言中也经常见到但是可以嵌套多层函数还是第一次见。
def function():
print(\"function\")
def func():
print/ r M + v q i(\"func\")
def fun():
print(\"fun\")
return fun
return func
function()()()13. lambda表达式
lambda表达式在很多编程语言中n = h 5 K ;都有类似语法,但是Python中的lambda表达式却和其它语言相差很大。
- Python中的lambda表达式必须使用lambda关键字声明,在其它编程语言中一般都不会使用任何关键字的
- Python中的lambda表达式主体式一个表达式(也就是主体只能写一行代码),而不是一个代码块(代码块可以写多行),在其它编程语言中一般都是代码块
z = 10
sum = lambda x, y: x + y + z
print(sum(1, 2))
print(type(sum))14. 函数注释(- O D . v ) 4 a *Function Annotations)
Python中的函数定l ! ` 7 a S K义参数不需8 m [ { C a y要指定参数类型,函数签名中不需要写返回值类型。如果不了解这个方法的作用a m ,,只看函数签名是看不出来啥,也不知道传参数传什么类型,也不知道这个函数的返回值是什么类型,就q , = y算知道函数的作用也不能完全确定函数的返回值是什么类型,是int类型还是float类型呢?不指定参数类型不指定返回值类型,这使得函数的签名非常模糊,非常的不直观,也不利于快速了解函数的作用。py def foobar(a, b): pass
像很多服务端语言都/ t 8 J z M X V会指定参数的类型和D = 6 0 `返回值类型,这些都作为函数签名的一部* 6 q z } p # `分。& I如下Java方法的定义:java /** * 除法 * @param a 除数 * @param b 被除数 * @return 除法结果 * @throws ArithmeticException 算术异常 */ public static long div(long a, long b) throws ArithmeticException { return a / b; }
为了解决函数F , + E +签名不明确i O G 4 2的问题,Python3中引入了函数注释。注意此函数注释并不太像我们通常所说的注释:解释函数的作用。Ja % ~ b ; /va语言中的注释一般分为几个部分:一部分用于描述函数的作用;一部分描述参数T ~ X;一部T I E J f C ? _分描述返回值;一部分描述异常信息。Python中的函数注释只能用来描述参数的S M N 8 4数据类型和作用以及字段的返回值类型,不能描述整个函数的作用。
函数注释作用是提高代码可读性,暗示传入参数及返回数据的类型, 注意; n N 8这只是提示参数和返回值的类型,并不校验参数的值是不是这种类型,因为Python中参数名是没有数据类型的,实际上还是可以任意传任意类型的值,这里只是提示要传入的数据类型,作为一种注释来提示你。
函数注释包括:
- 参数注释:以冒号 : 标记,可以是建议传入的参数数据类型type,也可以是一个帮助help字符串注释
- 返回值注释:以箭头 -> 标记,建议函数返回值Z 4 } { @ p z ` M的数据类型
参数~ ; `类型和返回值类型注释py def div(a: int, b: int) -> float: return a / b
帮助字符串注释py def div(a: \"除数\", b: \"被除数\") -> float: return a / b
参数类型注释和帮助字符串注释混用py def dic ) O B 8 v c qv(a: int, b: \"被除数\") ->Z ^ ) l g i a x N g 5\': \'被除数\'}, \'retuC 5 { T , z ; Wrn\': }
print(div.annotations)```
个人觉得把字符串帮助描述写在函数签名里使得d ] ) d 1 E P函数签名过于冗余不够简K I $洁,还不如在函数体p @ - e * % C U @内些文档注释看着清晰。 ```py def div(a: int, b: int) : ! D ) Z ` -&g} { . |t; float: r\"\"\"除U , ] { c 9 9法运算
:pa` p U ( 7 A q , 9ram a: 除数
:param b: 被除数
:return: 除法结果
\"\"\"
return a / b
动态注a 1 C _ N a l释
动态注释就是动态的修改注释的值。如果要修改return中的a + } 9 ^ n X ^值,那么函数的返回类型需要给__annotations_E * d ,_一个初始值。
__annotations__ 就像一个函数的静态属性一样了,m { Q函数每次的结果都会被记住。
```py
def div(L V I 2 % Ea: int, b: int# g 9 8 @ | ^) -> 0:
r\"\"\" 除A d o X C y P法运算
:param a:
:param b:
:return:
\"\S Q [ w D H U 2"\"
result = a // b
div.__annotations__ += re~ u t p w 0 d 2sult
return rF 2 ] Wesult
# 0
print(div.__5 h , % Q j rannotations__)
print(div(4, 2))
print(div(10, 2))
print(div(2, 2))
# 8
print(d. L i Z _ Wiv.__annotations__)15. 动态添加属性和方法
Python支持动态的添加属性和方法(实例方法、静态方法),Javascript脚本语言也支持此语法。
class Foobar:
pass
foo9 T ; Ubar = Foobar();
foobar.attr1 = \"动态添加属性\"
print(foobar.attr1)
fk : y n from types ims 4 z ) Xport MethodType
def test(self, value):
print(f\"动态添加{value}方法\")
# 给某一个对象动态] 5 f W ! ) A e添加一个实力方法,其它对象是没有该方法的
foobar.fv Y v n ` k J punc = MethodType(test, foobar)
foobar.func(\"实例\")
# 给类添加? 7 L静态方法
Foobar.test = MethodType(test, Foobar)
Foobar.test(\"类\")type() 函数即可用来获取变量V { ;的类型,也可用用于动态创建类型。 ```py def foo(self): print(\9 ? j L z S B'Hello World\ 6 v ; J Jr() ```
16. w = l k ) 5 ]ith关键字
with关键字的作用自动关闭资源或者自动获取锁和自动释放锁,不需要程序员显式的关闭资源和释放锁了,再也不会由于忘记关闭资K { 1 5源或者忘记释放锁造成bug了,系统会自动做这件事。
关闭资源 ```py try4 7 m @ 7 n R 1: f = open(\"testJ p k S 7 ` q.txt\") print(f.readline()) except BaseException as e: prin/ a { N c . i %t(e) finally: f.close()
w R\"123456\")
import functools int16 = functools.partial(int, base=16) int16(\"12345\")
斐波那契数列(FZ ^ , ` -ibonaf M B m F H H R Pcci sequence),又称黄金分割数列,因数学家列昂纳多斐波那契(Leonardoda F[ G k l I 2ibonacci)7 + A ! 4 $ U以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)$ A l ? , o ~=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶| 9 H m W s t ;体结构、% G I ! , f l 5 ft; 1) {
return 0;
} else if (n &lI j 4t; 3) {
return 1;
}
return fibonacci(n -1) + fibonacci(n - 2);
}
private static lony o a p x x 2 9 Wg fibonacci(int n) {
if (n < 1) {
rJ + S F Beturn 0;
}
long& b h a = 1;
long b =W W K N U 5 r c a 1;
long temp = 0;
for(int i = 3; i <=n; i++) {
// 指向最新的前一个值
temp = a;
// 最后一个值变成前一个值
a = b;
// 最新的下一个值 = 原来的b值 + 前一个值
b = b + temp;
}
return b;
}21. 交换两个变量的值
一般情况下交换两个变量的值1 } x 3 # H 3 F需要借助一个临时变量c 4 [ l ~ k utemp来实现,Python元组语法可以{ 8 b一次同时声明多个变量,也可以同时修改Y + ) y多个元祖中的值( I . n u d u K G,使用此语法可以简单的完成两个变量值交换。py x, y = 1, 2 x, y = y, x