分享

Python“奇葩”语法你都见过哪些

 常有理 2020-05-02

初学Python会遇到很多自己从未见过的语法,这些语法在其它编程语言中都没见过,有些语法是Python独创的,有些语法只是改变了书写形是自己又造一种新形式,这里罗列一下我认为Python中比较特殊的语法。

1. pass占位符

当函数或者代码块中不需要写任何代码时需要显式的写pass, pass表示占位符, 如果不写会报语法错误。

在其它编程语言中如果一个方法是空的给出一对大括号{}就表示方法体是空的,但是Python的方法体不是用大括号表示的而是用冒号+缩进方式表示的,Python为了表示一个空的方法体发明了这个pass占位符。

个人认为使用缩进方式而不是使用大括号{}形式表示方法体不好: - 使用缩进方式当粘贴复制代码时就会丢失缩进,这是一件很尴尬的事情,粘贴好代码还要非常认真的看一下代码缩进是不是对的 - 使用缩进方式是一个很随意的决定。具体典故好像是随便问了几个不懂开发的人问哪种方式他们认为最好,结果选缩进方式的比较多,后来就这么草率的决定使用缩进方式了。

public void test() { // java空方法体}
def func():    passif True:    pass
2. _ _ init_ _.py

Python中的init.py作用:

1. 标识一个普通的文件夹为包Package。

在编程语言中一般都使用package来管理源文件,用于对源文件分类,防止命名冲突,其实包就是一个普通的文件夹。像Java直接在src目录下New Package就创建了一个包,src表示源文件位置。但是Python创建Package时不但会创建一个文件夹,还会创建一个init.py的文件,这个文件的作用用于标识此目录是一个package而不是一个普通的文件夹。像Java直接就约定好源文件src目录了,maven约定好了测试类test和资源文件resource的位置。但Python没有任何约定,没有约定的后果就是你创建一个文件夹不知道这个文件夹究竟是一个包还是一个普通的文件夹呢,Python的解决办法是如果你要创建包那么这个包下必须有init.py文件这个文件才被认为是package,如果没有这个固定的文件就认为这是一个普通的文件夹。

在开发中大家普遍认为约定大于配置,如果不约定就会非常的灵活,太灵活也不是什么好事。如果一个项目有很多个包,每个包下都要有个init.py文件,个人感觉显得臃肿,不优雅,个人不太理解这种设计。

initall
# 在Python3.8中如果models包下__init__.py文件没有定义__all__,那么通过*号不会导入任何模块test.pyfrom models import *print(dir())

# 如果在models.__init__.py文件中定义了__all__, 那么只有被列举的值才允许被导入__all__ = ['model', 'model2']

views.test.py

定义了all所以只有model, model2被导入进来了from models import *print(dir())
init

init.py文件也是一个普通的py源码文件,也可以写任何python代码。可以写个print测试一下。

models.init.py py print('package models imported...')

views.test.py ```py

执行该语句就会执行models.init.py中的print代码

from models import * ```

注意:每次执行from models import *都会执行models.init.py中的print代码,这样设计不知道什么场景下会被使用到。很多资料都说不建议在.init.py文件中写业务代码。

3. 正索引和负索引

python中支持两种索引,正索引和负索引(负索引语法还是第一次接触到,虽然第一次接触但是并不反感这个语法,反而感觉这种语法很好很赞成),可以用于字符串、列表、元组: - 头索引(正索引):从左边开始,最左边第一个下标为0,最左边第二个下标为1 - 尾索引(负索引):从右边开始,最右边第一个下标为-1,最右边第二个下标为-2

索引:一个下标称之为索引 [1] 切片:两个下标称之为切片 [1:3]

Python“奇葩”语法你都见过哪些

```py

foo = 'abcdef' foo[0] 'a' foo[0:2] 'ab' foo[-1] 'f' foo[-3: -1] 'de' foo[-3: ] 'def' ```

>>> l = [1, 2, 3, 4]>>> l[0] = 0>>> l[0, 2, 3, 4]>>> l[1:3] = [22, 33]>>> l[0, 22, 33, 4]>>> l[1:3] = []>>> l[0, 4]>>> del l[1]>>> l[0]
4. while else 和 for else

循环代码块中可以有else代码块(我也是第一次见到这种语法),当循环条件为False时执行else,如果循环体中遇到break时不执行else代码块的。

i = 0while i < 5: print(f'{i} 小于5') i += 1else: print(f'{i} 大于等于5')# 执行结果0 小于51 小于52 小于53 小于54 小于55 大于等于5
for i in [1, 2, 3]:    print(f'i={i}')    if i == 2:        breakelse:    print(f'for ended, current i = {i}')# 执行结果i=1i=2
5. 三位运算符

Java中的三位运算符是用 ? : 来实现的。Python中是用if else来实现的(突然换种语法感觉不太适应) ```java int a = 2; int b = 1;

int result = a >= b ? a : b; System.out.println(result); ```

a = 2b = 1# if条件成立返回if前面的表达式,if条件不成立返回else对应的表达式result = a if a >= b else b# 2print(result)
6. 文档注释doctest

Python中的文档注释doctest是写在模块的第一行或者函数的第一行使用三个引号写的一段字符串注释,注释中一般包括函数作用的解释以及给出如何使用函数的示例程序,示例程序用的是Python Shell格式。

文档测试的作用: - 给出一些示例代码,便于快速了解使用 - 通过工具来生成文档,类似于Java中的Javadoc

def abs(n):    '''    Function to get absolute value of number.    usage::    >>> abs(1)    1    >>> abs(-1)    1    >>> abs(0)    0    '''    return n if n >= 0 else -nif __name__ == '__main__':    import doctest    # 执行文档测试    doctest.testmod()
7. del 关键字

del 关键字用于删除列表、字典中的元素,或者删除变量。 del这种用法即不像面向过程的语法(面向过程一般都是调用函数),也不是面向对象的语法(面向对象一般都是通过对象来调用方法),del这种语法比较像Shell命令行语法。

list = [1, 2, 3]del list[0]print(list)del list
8. 异常 try except else finally

在其它编程语言中一般都是try catch finally, Python用的不是catch而是except,并且python还支持else代码块。else子句将在try子句没有发生任何异常的时候执行

Python中的大部分异常类都是以Error作为后缀的,如IndentationError、AssertionError、ValueError、TypeError、ZeroDivisionError, 也有些异常没有啥后缀如StopIteration,感觉命名不统一。

# 自定义异常class ServerException(Exception):    def __init__(self, value):        self.value = value    def __str__(self):        return repr(self.value)try:    raise ServerException('抛出一个自定义异常')except ServerException as err:    print('ServerException: {0}'.format(err))except ValueError:    print('ValueError')except:    print('unexcepted error')    # 不处理异常,再次抛出去    raiseelse:    print('else')finally:    print('finally')

else的作用是try代码块正常执行完毕(没有报错)会执行else代码块。那么把else代码块中的代码单独放在else代码块中和放在try代码块的最后部分有什么区别???

示例1:要else代码块,try代码块执行完再毕执行else代码块,如果else代码块报错那么except是不会捕获异常的,那就继续往外抛出异常。py try: print('try') except: print('Exception') else: assert 1 == 2 finally: print('finally')

示例2:不要else代码块,直接将else代码块中代码放在try代码块的最后面。如果else代码块中的代码发生异常,会被except捕获,执行except代码块。

try: print('try') # else 代码块 assert 1 == 2except: print('Exception')finally: print('finally')
9. 在if、try、while、for代码块中声明的变量是全局变量

Python在代码块中声明的变量是'全局变量'这和Java语言完全相反,Java在代码块中声明的变量都是局部变量当代码块块执行完毕了就释放了局部变量,Python则完全不同。 ```py if True: a = 'if' print(a)

try: b = 'try' except ValueError: # 局部变量,外面不能访问 c = 'except' else: d = 'else' finally: e = 'finally'

print(b)

print(c)

print(d) print(e)

while True: f = 'while' break print(f)

for i in range(10): g = 'for' print(g) ```

10. 函数参数默认值类型只能为不可变类型

如果函数参数默认值为可变类型如list列表,那么函数执行的结果可能会和你预期的结果不一致。list传参属于引用传递而不是值传递。

list = [1, 2]def change_list(l):    l.append(3)change_list(list)# [1, 2, 3]print(list)
def append_end(l=[]): print('id(l)=', id(l)) l.append('END') return l# ['END']print(append_end())'''第二次调用参数l的内存地址竟然和第一次的完全一样。这说明一个问题:参数默认值只会分配一次内存,如果不传参数所有函数调用都使用同一块内存,这就解释了第二次函数调用为啥是['END', 'END']而不是['END']'''# ['END', 'END']print(append_end())

解决函数参数默认是可变类型带来的异常结果就是将函数参数默认值改为不可变类型None ```py def append_end2(l=None): print('id(l)=', id(l)) if l is None: # 如果没有传值,将l显式清空 l = [] l.append('END') return l

['END']

print(append_end2())

['END']

print(append_end2()) ```

11. global和nonlocal

当内部作用域想修改外部作用域的变量时,就要显式使用global和nonlocal关键字。原因是如果在局部作用于声明一个和全局作用于相同的名字的变量,Python会认为局部变量和全局变量是两个不同的变量(虽然名字相同),要告诉Python这是同一个变量就要使用关键字来修饰。

  • 如果函数内部要修改全局变量,需要使用global关键字修饰一下全局变量;
  • 如果要在嵌套函数内部修改外层函数中的变量,需要使用nonlocal关键字来修饰一下;
foobar = 1def func():    foobar = 10    print(f'func foobar={foobar}')func()print(foobar)
foobar = 1def func(): global foobar foobar = 10 print(f'func foobar={foobar}')# func foobar=10func()# 10print(foobar)
def outer():    foobar = 1    def inner():        nonlocal foobar        foobar = 10        print(f'inner foobar={foobar}')    inner()    print(f'outer foobar={foobar}')outer()
12. 嵌套函数

函数定义可以嵌套函数,这在其它语言中也经常见到但是可以嵌套多层函数还是第一次见。

def function(): print('function') def func(): print('func') def fun(): print('fun') return fun return funcfunction()()()
13. lambda表达式

lambda表达式在很多编程语言中都有类似语法,但是Python中的lambda表达式却和其它语言相差很大。

  • Python中的lambda表达式必须使用lambda关键字声明,在其它编程语言中一般都不会使用任何关键字的
  • Python中的lambda表达式主体式一个表达式(也就是主体只能写一行代码),而不是一个代码块(代码块可以写多行),在其它编程语言中一般都是代码块
z = 10sum = lambda x, y: x + y + zprint(sum(1, 2))print(type(sum))
14. 函数注释(Function Annotations)

Python中的函数定义参数不需要指定参数类型,函数签名中不需要写返回值类型。如果不了解这个方法的作用,只看函数签名是看不出来啥,也不知道传参数传什么类型,也不知道这个函数的返回值是什么类型,就算知道函数的作用也不能完全确定函数的返回值是什么类型,是int类型还是float类型呢?不指定参数类型不指定返回值类型,这使得函数的签名非常模糊,非常的不直观,也不利于快速了解函数的作用。py def foobar(a, b): pass

像很多服务端语言都会指定参数的类型和返回值类型,这些都作为函数签名的一部分。如下Java方法的定义:java /** * 除法 * @param a 除数 * @param b 被除数 * @return 除法结果 * @throws ArithmeticException 算术异常 */ public static long div(long a, long b) throws ArithmeticException { return a / b; }

为了解决函数签名不明确的问题,Python3中引入了函数注释。注意此函数注释并不太像我们通常所说的注释:解释函数的作用。Java语言中的注释一般分为几个部分:一部分用于描述函数的作用;一部分描述参数;一部分描述返回值;一部分描述异常信息。Python中的函数注释只能用来描述参数的数据类型和作用以及字段的返回值类型,不能描述整个函数的作用。

函数注释作用是提高代码可读性,暗示传入参数及返回数据的类型, 注意这只是提示参数和返回值的类型,并不校验参数的值是不是这种类型,因为Python中参数名是没有数据类型的,实际上还是可以任意传任意类型的值,这里只是提示要传入的数据类型,作为一种注释来提示你。

函数注释包括:

  • 参数注释:以冒号 : 标记,可以是建议传入的参数数据类型type,也可以是一个帮助help字符串注释
  • 返回值注释:以箭头 -> 标记,建议函数返回值的数据类型

参数类型和返回值类型注释py def div(a: int, b: int) -> float: return a / b

帮助字符串注释py def div(a: '除数', b: '被除数') -> float: return a / b

参数类型注释和帮助字符串注释混用py def div(a: int, b: '被除数') -> float: return a / b

同时支持参数数据类型和帮助字符串注释 ```py def div(a: dict(type= int, help='除数'), b: dict(type= int, help='被除数')) -> float: return a / b

注释是一种帮助开发人员了解代码的,并不是可执行代码的一部分,函数注释只是一种提示,并不是强制。虽然我们指定了参数的数据类型,但是我们让然虽然传值,我们可以传float类型的值。

print(div(4.4, 2.0))

可以通过annotations属性来获取函数注释,返回有一个dict,返回值的key为”return“。通过函数名.annotations来获取

{'a': {'type': , 'help': '除数'}, 'b': {'type': , 'help': '被除数'}, 'return': }

print(div.annotations)```

个人觉得把字符串帮助描述写在函数签名里使得函数签名过于冗余不够简洁,还不如在函数体内些文档注释看着清晰。 ```py def div(a: int, b: int) -> float: r'''除法运算

:param a: 除数:param b: 被除数:return: 除法结果'''return a / b
动态注释动态注释就是动态的修改注释的值。如果要修改return中的值,那么函数的返回类型需要给__annotations__['return']一个初始值。__annotations__['return'] 就像一个函数的静态属性一样了,函数每次的结果都会被记住。```pydef div(a: int, b: int) -> 0:    r''' 除法运算    :param a:    :param b:    :return:    '''    result = a // b    div.__annotations__['return'] += result    return result# 0print(div.__annotations__['return'])print(div(4, 2))print(div(10, 2))print(div(2, 2))# 8print(div.__annotations__['return'])
15. 动态添加属性和方法

Python支持动态的添加属性和方法(实例方法、静态方法),Javascript脚本语言也支持此语法。

class Foobar: passfoobar = Foobar();foobar.attr1 = '动态添加属性'print(foobar.attr1)from types import MethodTypedef test(self, value): print(f'动态添加{value}方法')# 给某一个对象动态添加一个实力方法,其它对象是没有该方法的foobar.func = MethodType(test, foobar)foobar.func('实例')# 给类添加静态方法Foobar.test = MethodType(test, Foobar)Foobar.test('类')

type() 函数即可用来获取变量的类型,也可用用于动态创建类型。 ```py def foo(self): print('Hello World')

参数1:类名

参数2:父类

参数3:方法

Hello = type('Hello', (object,), dict(foobar=foo)) obj = Hello() obj.foobar() ```

16. with关键字

with关键字的作用自动关闭资源或者自动获取锁和自动释放锁,不需要程序员显式的关闭资源和释放锁了,再也不会由于忘记关闭资源或者忘记释放锁造成bug了,系统会自动做这件事。

关闭资源 ```py try: f = open('test.txt') print(f.readline()) except BaseException as e: print(e) finally: f.close()

with方式

with open('test.txt') as f: print(f.readline())```

with自动获取锁和释放锁 ```py import threading

lock = threading.Lock()

lock.acquire() print('线程安全代码...') lock.release() ```

import threadinglock = threading.Lock()with lock:    print('线程安全代码...')
17. import 模块 和 from 模块 import 成员 的区别
  • import 模块 : 导入之后要想使用模块里的函数、类或者变量,需要通过 '模块.' 语法来调用,而不能直接使用模块内的成员函数或者变量。如果把Python中的import 模块理解成Java中的创建对象 Sys sys = new Sys();通过对象点调用就是sys.argv
  • from 模块 import 成员:导入之后可以直接使用模块内的函数、类或者变量,而不需要在函数或者变量加'模块.',类似于Java中的静态导入。
import sysprint(sys.argv)
# 导入多个用逗号分隔,导入所有用星号*from sys import argv, pathprint(argv)print(path)
18. id() 和 ctypes
  • id()函数用户获取变量的内存地址
  • ctypes模块可以根据内存地址获取对应的值
import ctypesvalue = 'Hello World'memory_address = id(value)print(memory_address)raw_value = ctypes.cast(memory_address, ctypes.py_object).value # 根据内存地址获取对应的值print(raw_value)
19.数学方式比较大小

在其它编程语言中一般是不允许数学方式比较的,原因是一些符号是特殊语法而不是数学用的比较符号,在Python中这些数学比较符号没有被用来作为语法使用,所以支持数学方式的比较py x = 10 result = 1 < x < 10 result = 20>= x >= 10

20.偏函数

使用原函数指定一些默认值来封装出一个新函数。如果某个函数经常使用某个固定的默认参数,可以通过偏函数语法来简化调用,减少传参个数。 ```py def int16(value): return int(value, base=16)

int16('123456')

import functools int16 = functools.partial(int, base=16) int16('12345')

斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。```java/** * f(1) = 1 * f(2) = 1 * f(3) = f(2) + f(1) = 1 + 1 = 2 * f(4) = f(3) + f(2) = 2 + 1 = 3 * f(n) = f(n-1) + f(n-2) * @param n * @return */private static long fibonacci(int n) { if (n < 1) {  return 0; } else if (n < 3) {  return 1; } return fibonacci(n -1) + fibonacci(n - 2);}private static long fibonacci(int n) { if (n < 1) {  return 0; } long a = 1; long b = 1; long temp = 0; for(int i = 3; i <=n; i++) {  // 指向最新的前一个值  temp = a;  // 最后一个值变成前一个值  a = b;  // 最新的下一个值 = 原来的b值 + 前一个值  b = b + temp; } return b;}
21. 交换两个变量的值

一般情况下交换两个变量的值需要借助一个临时变量temp来实现,Python元组语法可以一次同时声明多个变量,也可以同时修改多个元组中的值,使用此语法可以简单的完成两个变量值交换。py x, y = 1, 2 x, y = y, x

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多