Files
rikako-note/python/py.md
2025-08-19 12:55:16 +08:00

75 KiB
Raw Blame History

Python

变量

在python中可以通过如下方式创建变量

message = "Hello Python Crash Course world!"

字符串

字符串首字母大写

name = "ada lovelace"
print(name.title())

字符串全部字符大写

name = "Ada Lovelace"
print(name.upper())

字符串全部字符小写

name = "Ada Lovelace"
print(name.lower())

字符串删除空白符

name = " Asahi Ogura    `
# 删除左空白 
name.lstrip()
# 删除右空白
name.rstrip()
# 删除左右空白
name.strip()

访问字符串中字符

ptyhon可以通过数组下标来访问字符串中的字符如果字符串长度为n那么索引范围为[0, n-1]同时在python中还可以使用负数的索引索引范围为[-n, -1]

在python字符串中第一个字符可以通过索引下标0-n来访问,最后一个字符可以通过n-1-1来访问。

字符串切片

字符串切片可以通过str[from:to:step]来标识,其标识下标为[from, to)范围内的子字符串,步长为step

temp = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

其中,输出[0,10)范围内的字符串可以采用如下方式:

print(temp[0:10])  # ABCDEFGHIJ
print(temp[0:10:1]) # ABCDEFGHIJ

当步长被设置为2时输出内容如下

print(temp[0:10:2]) # ACEGI

正数索引和负数索引混用:

# 输出不包含末尾2个字符的子字符串
print(temp[0:-2]) # ABCDEFGHIJKLMNOPQRSTUVWX
print(temp[0:24]) # ABCDEFGHIJKLMNOPQRSTUVWX
print(temp[:-2]) # ABCDEFGHIJKLMNOPQRSTUVWX

# 输出末尾两个字符的子字符串
print(temp[-2:])

逆序输出字字符串

print(temp[::-1]) # ZYXWVUTSRQPONMLKJIHGFEDCBA
print(temp[-1:0:-1]) # ZYXWVUTSRQPONMLKJIHGFEDCBA
print(temp[25:0:-1]) # ZYXWVUTSRQPONMLKJIHGFEDCBA

字符串迭代

python中字符串可迭代示例如下

temp = "ABCDE"
for ind, ch in enumerate(temp):
    print("ind = "+str(ind)+", ch = "+ch, end="\n")

# ind = 0, ch = A
# ind = 1, ch = B
# ind = 2, ch = C
# ind = 3, ch = D
# ind = 4, ch = E

数字

/

python中整数执行/操作默认并不会进行整除,而是可能生成浮点数

print(3/2) # 1.5

//

python中如果要执行整除应该使用//运算符,该运算符为向下取整

print(3//2) # 1
print(-3//2) # -2

和java不同java在执行整数除时会舍弃掉小数部分例如-3/2java中结果为-1而python则是向下取整为-2

如果python中/操作想要和java保持一直可以使用math中的trunc函数,math.trunc(-3/2)返回结果和java中一样-1

数字类型向字符串类型转换

可以通过str函数将数字类型转化为字符串类型

temp = 1.2
print("temp = "+str(temp))

列表

python中通过[e1, e2, ...]的形式来标识列表

访问列表中元素

可以通过数组下标的方式来访问列表中的元素

temp = [1, "2", "san"]

print(temp[1]) # 2
print(temp[-2]) # 2

列表元素操作

修改

temp = [1, "2", "san"]

temp[2] = 3.0
print(temp) # [1, '2', 3.0]

插入到末尾

如果想要向数组末尾插入元素可以调用append方法

temp = [1, "2", "san"]

temp.append("four")
print(temp) # [1, '2', 'san', 'four']

在某位置之前插入

如果想要在特定位置之前插入元素可以调用insert方法

temp = [1, "2", "san"]

temp.insert(1, "1.5")
print(temp)  # [1, '1.5', '2', 'san']
temp.insert(4, "four")
print(temp)  # [1, '1.5', '2', 'san', 'four']

删除列表中的元素

如果想要删除列表中的元素可以通过del关键字进行删除

temp = [1, "2", "san"]

del temp[1]
print(temp) # [1, 'san']

列表与栈api

列表append操作是将元素追加到列表末尾同时列表还支持pop操作将列表末尾的元素作为栈顶元素弹出。

如果此时列表为空那么调用pop方法将抛出异常

temp = [1, "2", "san"]


print(temp.pop())  # san
print(temp.pop())  # 2
print(temp.pop())  # 1
print(temp.pop())  # IndexError: pop from empty list

还可以为pop方法指定一个下标此时可以弹出任何位置的元素

temp = [1, "2", "san"]


print(temp.pop(1)) # 2
print(temp.pop(0)) # 1
print(temp.pop(0)) # san

remove

在列表中可以调用remove来移除列表中值为xxx的元素。

remove默认只会移除第一个匹配的元素

temp = [2, 1, 2, 1, 2]

temp.remove(2)
print(temp) # [1, 2, 1, 2]

列表排序

sort

可以通过sort方法针对列表中的元素进行排序sort方法会该表列表中的元素顺序

temp = [2, 1, 2, 1, 2]

temp.sort()
print(temp) # [1, 1, 2, 2, 2]

如果想要逆向排序可以为sort方法指定reverse=True

temp = [2, 1, 2, 1, 2]

temp.sort(reverse=True)
print(temp) # [2, 2, 2, 1, 1]

sorted

如果不想改变原列表中的元素顺序而是生成一个排好序的新列表可以调用sorted函数

temp = [2, 1, 2, 1, 2]

print(sorted(temp)) # [1, 1, 2, 2, 2]
print(sorted(temp, reverse=True)) # [2, 2, 2, 1, 1]
print(temp) # [2, 1, 2, 1, 2]

列表中顺序反转

如果不想对列表中元素进行排序只是想逆序列表中的数据可以调用reverse方法

temp = [2, 1, 2, 1, 3]

temp.reverse()
print(temp) # [3, 1, 2, 1, 2]

获取列表长度

可以通过len函数获取列表的长度

temp = [1,2,3]

temp.pop()
print(len(temp)) # 2
temp.pop()
print(len(temp)) # 1
temp.pop()
print(len(temp)) # 0

遍历列表

在python中可以通过for ... in ...的形式来遍历列表中的元素

temp = [1, 2, 3]

for e in temp:
    print(e)

# 1
# 2
# 3

range

在python中可以通过range函数生成一系列数字range(m,n)会生成[m,n)范围内的数字,可用于fori形式的遍历

arr = ["1", "3", "2"]

for ind in range(0, len(arr)):
    print(arr[ind])

# 1
# 3
# 2

可以通过range函数来创建数字列表

print(list(range(0, 5))) # [0, 1, 2, 3, 4]

range也可以指定步长

print(list(range(0, 7, 2))) # [0, 2, 4, 6]

max, min, sum

针对数字型的列表支持max、min、sum等函数来求最大值、最小值、总和

arr = list(range(1, 101))

print(max(arr)) # 100
print(min(arr)) # 1
print(sum(arr)) # 5050

根据一个列表生成另一个列表

python中根据[expr(e) for e in list_a]的语法,可以快速根据一个列表得到应一个列表,目标列表中的每个元素都来源于来源列表的映射

arr = ['amd', 'nvidia']
graphic_msgs = ['fuck '+brand for brand in arr]
print(graphic_msgs) # ['fuck amd', 'fuck nvidia']

切片

列表的切片和字符串切片类似

arr = [1, 2, 3, 4, 5]
print(arr[::2]) # [1, 3, 5]

print([e**2 for e in arr[::2]]) # [1, 9, 25]

列表复制

在python中如果要复制列表可以创建整个列表的切片

arr = [1, 2, 3]
# 复制列表
arr_copied = arr[:]

print(arr_copied) # [1, 2, 3]

# 对复制后的列表进行写操作,并不会影响复制前的列表
arr_copied.pop()

print(arr_copied) # [1, 2]
print(arr) # [1, 2, 3]

元组

运行时列表中的元素可以被修改,如果想要让元素不可变,可以使用元组。

元组通过(e1, e2, e3, ...)形式来表示,和列表一样,元组也可以通过索引来访问。

但是元组中的元素无法复制,在执行赋值语句时会抛出异常。

pos = (50, 100)

pos[0] = 125 # TypeError:'tuple' object does not support item assignment

if

类似其他编程语言python也支持if/else语法

cars = ['audi', 'bmw', 'subaru', 'toyota']
for car in cars:
    if car == 'bmw':
        print(car.upper())
    else:
        print(car.title())

and/or

python可以通过andor操作符来拼接多个条件。

age = 179
is_alive = True

if age < 0 or age > 150 and is_alive:
    print("age wired")

列表中是否包含某值

如果想要检查列表中是否包含特定值,可以采用xxx in list_a的语法。

arr = [1, 3, 2]

print(1 in arr) # True

列表中是否不包含某值

如果想要检查列表中是否不包含特定值,可以采用xxx not in list_a的语法。

arr = [1, 3, 2]

print(1 not in arr) # False

多分支if/elif/else

python可以通过if/elif/else的语法实现多分支判断

age = 40
if age < 20:
    print("teen")
elif age < 40:
    print("man")
else:
    print("elder man")

字典

在python中通过字典实现了其他语言中的Map数据结构。

dic = {"name": "Ogura Asahi", "age": 17}

print(dic['name']) # Ogura Asahi

向字典中添加键值对

dic = {"name": "Ogura Asahi", "age": 17}

dic['major'] = "student"
print(dic) # {'name': 'Ogura Asahi', 'age': 17, 'major': 'student'}

删除字典中的键值对

dic = {"name": "Ogura Asahi", "age": 17}

del dic['age']
print(dic) # {'name': 'Ogura Asahi'}

字典遍历

字典遍历可以通过for k, v in dic.items()的形式完成

dic = {"name": "Ogura Asahi", "age": 17}

for key, val in dic.items():
    print("key = "+key+" , value = "+str(val))

如果只想遍历字典的key集合可以通过for k in dic.keys()的方式

dic = {"name": "Ogura Asahi", "age": 17}

for key in dic.keys():
    print("key = "+key)

如果只想遍历字典的value集合可以通过for v in dic.values()的形式

dic = {"name": "Ogura Asahi", "age": 17}

for value in dic.values():
    print("value = "+str(value))

按顺序遍历字典的key

通常字典的key遍历顺序是不一定的。如果想要按排序后的顺序遍历key可以采用如下方式

dic = {"name": "Ogura Asahi", "age": 17}

for key in sorted(dic.keys()):
    print("key = "+str(key))

while

i = 0

while i < 10:
    print(i)
    i += 1

函数

函数可通过如下方式来定义:

def fuck(brand, boss):
    print("fuck you " + boss + " and your " + brand)


fuck("Nvidia", "Jensen Huang")

在调用函数时,可以打乱传入实参顺序,只需要传参时指定参数名称即可:

def fuck(brand, boss):
    print("fuck you " + boss + " and your " + brand)


fuck(boss="Jensen Huang", brand="Nvidia")

参数默认值

声明函数时,可以为参数指定默认值,故而在调用函数时,可以对有默认值的参数进行省略

def fuck(boss, brand="Nvidia"):
    print("fuck you " + boss + " and your " + brand)


fuck("Jensen Huang")
fuck(brand="Apple", boss="Cook")

接收多个参数

通过*paramTup的形式python会将函数接收到的参数都存储在一个元组中。

def fuck(*name_list):
    msg = "fuck "
    is_head = True
    for name in name_list:
        if not is_head:
            msg += ", "
        is_head = False
        msg += name
    print(msg)


fuck("Nvidia", "Amd")

文件操作

文件读取

with open('pi_digits.txt') as file_object:
    contents = file_object.read()
    print(contents)

写入文件

filename = 'programming.txt'
with open(filename, 'w') as file_object:
    file_object.write("I love programming.")
    file_object.write("I love creating new games.")

文件末尾追加

filename = 'programming.txt'
with open(filename, 'a') as file_object:
    file_object.write("I also love finding meaning in large datasets.\n")
    file_object.write("I love creating apps that can run in a browser.\n")

module

在python中支持将一些公用的实现放在一个文件中在使用时可以在其他script中导入该文件并对公共实现进行调用。

此时放置公共实现的文件称为modulemodule中的定义可以被其他module导入。

module是一个包含python定义和statement的文件file namemodule name加上.py。在module中module name可以通过global变量__main__来进行访问。

module中可以包含executable statement和函数定义statements则是为了初始化module。executable statement只会在module被初次import时执行。

每个module都会有其独有的private namespace该namespace会用作module中所有函数的global namespace。故而module的作者可以放心的在mdoule中使用global变量module global变量有其自己的namespace并不用担心module global变量和调用方的global变量发生冲突。

故而,可以通过modname.itemname来访问module中的global变量。

如果在module A中对module B执行了导入操作,那么被导入的module B中的names将会被全部添加到module A的global namespace中。

导入语法如下:

# 直接导入fibo中的names
from fibo import fib, fib2
fib(500)
# 从module中导入所有names_开头的name不会被导入
from fibo import *
fib(500)
# 如果import语句后跟随了as那么as后跟随的名称将会和被导入module绑定
import fibo as fib
fib.fib(500)
# 和上一个的效果相同module通过`fibo`来引用
import fibo
from fibo import fib as fibonacci
fibonacci(500)

运行module

当想要运行module时可以采用如下方法

python fibo.py <arguments>

当有一段代码只有当module以main module的情况下被调用时才会被执行可以采用如下方式

if __name__ == '__main__':
    # run some code

module search path

当module被导入时python interpreter首先会在sys.builtin_module_names中搜寻module namebuilt-in-module-names中包含如下module

>>> import sys
>>> sys.builtin_module_names
('_abc', '_ast', '_bisect', '_blake2', '_codecs', '_collections', '_csv', '_datetime', '_elementtree', '_functools', '_heapq', '_imp', '_io', '_locale', '_md5', '_operator', '_pickle', '_posixsubprocess', '_random', '_sha1', '_sha256', '_sha3', '_sha512', '_signal', '_socket', '_sre', '_stat', '_statistics', '_string', '_struct', '_symtable', '_thread', '_tracemalloc', '_warnings', '_weakref', 'array', 'atexit', 'binascii', 'builtins', 'cmath', 'errno', 'faulthandler', 'fcntl', 'gc', 'grp', 'itertools', 'marshal', 'math', 'posix', 'pwd', 'pyexpat', 'select', 'spwd', 'sys', 'syslog', 'time', 'unicodedata', 'xxsubtype', 'zlib')
>>>

如果builtin_module_names中不包含被导入的module name那么其会在sys.path中寻找{module}.py文件,sys.path通过如下路径来初始化:

  • input script所处的目录路径
  • PYTHONPATH全局变量,语法和PATH相同
  • 安装包默认位置

dir

通过内置的dir函数可以输出module中的names

>>>import fibo, sys
>>>dir(fibo)
['__name__', 'fib', 'fib2']
>>>dir(sys)  
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
 '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
 '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
 '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
 'warnoptions']

当dir函数传参为空时输出当前namespace的names

>>>a = [1, 2, 3, 4, 5]
>>>import fibo
>>>fib = fibo.fib
>>>dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

dir并不会输出内置的names内置的names定义在builtinsmodule中

>>>import builtins
>>>dir(builtins)  
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
 'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError',
 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
 '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
 'zip']

package

假设python项目结构如下package和子package中包含多个moduels

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

在导入包时会在sys.path中搜索package的子目录。

如果需要python将目录视为package需要在目录下加入__init__.py文件,该文件可以为空。

使用者可以导入package中独立的模块

import sound.effects.echo
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
from sound.effects import echo
echo.echofilter(input, output, delay=0.7, atten=4)

from ... import *

对于from {package} import *,其会查看是否包下定义的__init__.py文件中定义了__all__变量,如果定义了,那么会导入__all__中的所有module。

__all__ = ["echo", "surround", "reverse"]

Errors and Exceptions

在python中存在两种不同的异常syntax errosexceptions

Syntax Errors

syntax errors又被称为parsing errors当parser检测到语法错误时会抛出该error

Exceptions

在python程序执行时即使语法都正常也有可能在执行时发生异常。在程序执行时被检测到的异常被称为exceptions但是其时可以被处理的并不会无条件的造成fatal。

handling exceptions

可以在程序中对特定类型的异常进行处理,示例如下所示:

while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("Oops!  That was no valid number.  Try again...")

上述示例中针对ValueError类型的异常进行了捕获,并提示用户输入有效的数字。但是,在上述程序实例中,用户仍然能够通过Ctrl + C中断该程序。

Ctrl + C将会以抛出KeyboardInterrupt的形式表现。

try statement中可以包含多个except为不同类型的异常指定handler最多只会有一个handler被执行。并且一个except中也可以包含多个exceptions示例如下所示

... except (RuntimeError, TypeError, NameError):
    ...     pass

包含多个except的示例如下所示:

class B(Exception):
        pass

    class C(B):
        pass

    class D(C):
        pass

    for cls in [B, C, D]:
        try:
                raise cls()
            except D:
                print("D")
            except C:
                print("C")
            except B:
                print("B")

上述示例中,会输出B, C, D。但是,如果修改except B的位置为第一位,那么输出将会变为B, B, B

在except语法中除了指定异常类型外还支持指定一个变量名来捕获异常该变量中包含args属性,能够访问构造该异常对象时传递的参数,并且,异常中还定义了__str__()方法用于打印所有的异常参数。

为捕获的异常绑定变量的示例如下所示:

try:
        raise Exception('spam', 'eggs')
        except Exception as inst:
                print(type(inst))    # the exception type
            print(inst.args)     # arguments stored in .args
            print(inst)          # __str__ allows args to be printed directly,
                                 # but may be overridden in exception subclasses
            x, y = inst.args     # unpack args
            print('x =', x)
            print('y =', y)

输出内容为:

    <class 'Exception'>
    ('spam', 'eggs')
    ('spam', 'eggs')
    x = spam
    y = eggs

python异常结构

BaseException是所有异常类的common base classException类则是BaseException的子类同时也是所有非fatal异常类的base class。

对于是BaseException子类但是不是Exception子类的类,其通常代表不应当被处理的异常,抛出该类异常通常代表程序应当被终止,其包含如下类:

  • SystemExit:由system.exit()抛出
  • KeyboardInterrupt:由想要中止程序的用户触发Ctrl + C

Exception可以被用做匹配所有异常。

import sys

try:
        f = open('myfile.txt')
            s = f.readline()
            i = int(s.strip())
        except OSError as err:
                print("OS error:", err)
        except ValueError:
            print("Could not convert data to an integer.")
        except Exception as err:
            print(f"Unexpected {err=}, {type(err)=}")
            raise

try-except语法还支持elseelse必须跟在所有except之后,其代表try block中没有抛出任何异常的场景。

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

rasing exception

raise关键字支持抛出异常,示例如下

raise NameError('HiThere')

传递给raise的必须是Exception的实例或是继承自Exception的类但传递的是类时其会使用类的默认构造器来构造Exception实例。

raise ValueError  # shorthand for 'raise ValueError()'

raise关键字也能用于对异常的重新抛出,示例如下:

try:
        raise NameError('HiThere')
        except NameError:
                print('An exception flew by!')
            raise

Exception Chaining

如果在except处理异常时处理逻辑又抛出了异常那么其会将被处理的异常关联到新异常中并且将被处理异常包含在error msg中

try:
        open("database.sqlite")
        except OSError:
                raise RuntimeError("unable to handle error")

其输出如下:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    open("database.sqlite")
    ~~~~^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError("unable to handle error")
RuntimeError: unable to handle error

如果想要设置异常的cause可以使用from语法,示例如下所示:

def func():
        raise ConnectionError

    try:
        func()
        except ConnectionError as exc:
                raise RuntimeError('Failed to open database') from exc

上述示例中,raise ... from exc直接表明RuntimeError由exc引发。

输出示例如下所示:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    func()
    ~~~~^^
  File "<stdin>", line 2, in func
ConnectionError

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

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError('Failed to open database') from exc
RuntimeError: Failed to open database

通过from None也可以关闭异常chain示例如下

try:
        open('database.sqlite')
        except OSError:
                raise RuntimeError from None

示例如下所示:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise RuntimeError from None
RuntimeError

用户自定义异常类型

用户如果要自定义异常,可以创建Exception类型的子类。

大多数异常类型都以Error结尾和standard exceptions的命名规范类似。

define cleanup action

python支持通过finally来定义clean-up action其在所有场景下都会被处理。

try:
    raise KeyboardInterrupt
finally:
    print('Goodbye, world!')

其中finally执行时机如下

  • 当抛出异常时如果异常没有被except处理那么异常在finally执行完后再次抛出
  • 当except或else中抛出异常时那么异常在finally执行完后被抛出
  • 如果在finally中执行了break, continue, return等语句,那么异常不会被抛出
  • 如果try中执行了break, continue, return那么finally会在break, continue, return执行之前执行
  • 如果finally中执行了return操作那么其return的值将会覆盖try中return的值

finally通常被用于释放资源等操作。

predefined clean-up action

部分对象定义了对象不在被需要时的clean-up action,无论针对对象的操作是否成功clean-up action都会被执行。

故而,可以通过with对该类对象进行使用,并无需手动释放其资源,示例如下:

with open("myfile.txt") as f:
        for line in f:
                print(line, end="")

在上述代码执行完后,文件会自动关闭,即使是在执行过程中遇到异常。

rasing multi exceptions

在部分时候,可能需要抛出多个异常,此时可以通过ExceptionGroup来对exception list进行wrap示例如下

def f():
        excs = [OSError('error 1'), SystemError('error 2')]
        raise ExceptionGroup('there were problems', excs)

except*

当使用except*可以选择性处理group中特定类型的异常并将其他异常传递到下游。

def f():
    raise ExceptionGroup(
        "group1",
        [
            OSError(1),
            SystemError(2),
            ExceptionGroup(
                "group2",
                [
                    OSError(3),
                    RecursionError(4)
                ]
            )
        ]
    )

try:
    f()
except* OSError as e:
    print("There were OSErrors")
except* SystemError as e:
    print("There were SystemErrors")

当使用except*语法时,即使指定了except* OSError as ee的类型仍然是ExceptionGroup而不是OSError因为try中抛出的ConditionGroup中可能含有多个OSError类型的异常,故而,实际的OSError异常对象需要通过e.exceptions来进行访问。

enriching exceptions with notes

当创建异常时,可以通过add_note(note)方法来为异常补充信息标准的traceback会按照其被添加的顺序渲染所有的note信息note信息在异常信息之后

try:
    raise TypeError('bad type')
except Exception as e:
    e.add_note('Add some information')
    e.add_note('Add some more information')
    raise

其输出如下:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
    raise TypeError('bad type')
TypeError: bad type
Add some information
Add some more information
def f():
    raise OSError('operation failed')

excs = []
for i in range(3):
    try:
        f()
    except Exception as e:
        e.add_note(f'Happened in Iteration {i+1}')
        excs.append(e)

raise ExceptionGroup('We have some problems', excs)

其异常信息如下:

 + Exception Group Traceback (most recent call last):
  |   File "<stdin>", line 1, in <module>
  |     raise ExceptionGroup('We have some problems', excs)
  | ExceptionGroup: We have some problems (3 sub-exceptions)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 1
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 2
    +---------------- 3 ----------------
    | Traceback (most recent call last):
    |   File "<stdin>", line 3, in <module>
    |     f()
    |     ~^^
    |   File "<stdin>", line 2, in f
    |     raise OSError('operation failed')
    | OSError: operation failed
    | Happened in Iteration 3
    +------------------------------------

多线程

python可以通过引入threading和concurrent.futures两个包来引入多线程

import math
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading

import requests

ts_arr = []
lock = threading.Lock()


def query_ts():
    try:
        body = requests.get("https://api.m.taobao.com/rest/api3.do", {
            "api": "mtop.common.getTimestamp"
        }).json()
    except Exception as e:
        print(e)
        raise


def query_ts_once():
    ts_beg = time.time_ns()
    query_ts()
    ts_end = time.time_ns()
    ts_spend = math.trunc((ts_end - ts_beg) / 1000000)
    with lock:
        ts_arr.append(ts_spend)


def stress_test(test_cnt):
    with ThreadPoolExecutor(max_workers=32) as executor:
        i = 0
        f_list = []
        while i < test_cnt:
            f_t = executor.submit(query_ts_once)
            f_list.append(f_t)
            i += 1
        as_completed(f_list)


stress_test(100)
print(f"max ts : {max(ts_arr)} ms")
print(f"min ts : {min(ts_arr)} ms")
print(f"avg ts : {sum(ts_arr) / len(ts_arr)} ms")
print(ts_arr)

linux命令交互

如果想要调用系统linux的命令可以使用subprocess模块。

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)

调用示例如下:

ret = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(ret.stdout)

输出如下:

total 4
-rwxrwxrwx 1 asahi asahi  110 May  4 03:18 main.py
-rwxrwxrwx 1 asahi asahi   73 May  4 01:10 shiro.json
drwxrwxrwx 1 asahi asahi 4096 Feb  7 21:39 venv

正则

python通过re模块提供了正则表达式的支持

match

match方法定义如下所示

re.match(pattern, string, flags=0)

match方法使用示例如下所示

import re


def main():
    match_res = re.match("(?P<prefix>.*) father", "i am your father")
    if match_res is None:
        print("failed match")
        return
    print(match_res.group("prefix")) # i am your
    print(match_res.group(0)) # i am your father


main()

search方法定义如下所示

re.search(pattern, string, flags=0)

search方法使用示例如下所示

pattern

类似javare提供了compile方法返回正则编译后的pattern并通过pattern执行操作

findall

pattern.findall方法会返回所有匹配的字符串

import re


def main():
    msg = '''although i use amd graphic card, i view it as a trash for its bad performance in ai.
    nvidia is also trash because its crazy price, fuck amd and nvidia!'''
    pattern = re.compile("(amd|nvidia)\\s+(graphic card)?")
    search_res_list = pattern.findall(msg)
    print(search_res_list)


main()

# [('amd', 'graphic card'), ('nvidia', ''), ('amd', '')]

finditer

pattern.finditer会返回所有匹配的match对象

import re


def main():
    msg = '''although i use amd graphic card, i view it as a trash for its bad performance in ai.
    nvidia is also trash because its crazy price, fuck amd and nvidia!'''
    pattern = re.compile("(amd|nvidia)\\s+(graphic card)?")
    search_res_list = pattern.finditer(msg)
    print([e.group(0) for e in search_res_list])


main()

# ['amd graphic card', 'nvidia ', 'amd ']

面向对象

命名空间

命名空间是name到对象的映射集合在不同的命名空间中相同name映射到的对象可能会不同。

namespace的示例有

  • 内置name集合包含内置异常名称和函数名例如abs()函数)
  • module中的global names
  • 函数调用中的local names

在某种程度上来说对象中的attributes也构成了一个命名空间

module

python支持将文件内容拆分到多个文件中将function definition放置到文件中后其他文件interactive instance中可以针对fucntion definition进行导入。

放置function definition的文件被称之为module,通常用来定义公共方法。

module name

module为包含python definition和statements的文件。file name由module name和.py后缀组成。在module中module的名称可以通过全局变量名__name__进行访问。

如果在文件fibo.py中定义如下内容:

# Fibonacci numbers module

def fib(n):
    """Write Fibonacci series up to n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib2(n):
    """Return Fibonacci series up to n."""
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

那么fibomodule中的内容可以通过如下方式进行导入

import fibo

上述导入并不会将定义在fibomodule中的function name直接添加到当前的命名空间其只是将module namefibo添加到了命名空间可以通过module name访问module中定义的内容

fibo.fib(1000)

fibo.fib2(100)

fibo.__name__

module statements

在module中除了可以包含function definition外还可以包含可执行statements。该可执行statements用于对module进行初始化。

when statements will be executed

module中的statements只会在第一次该module被导入时执行。(如果module文件其本身被执行而非作为依赖被其他文件导入那么module中的statements也同样会被执行)

每个module都拥有其自己的private namespace并且对于定义在module中的所有方法而言private module既是global namespace。故而作为module的开发者而言其可以自由的使用global variables而不用担心其global variables名称和module使用者定义的global variables发生冲突。

如果想要访问在module中定义的global variables可以使用modname.itemname的方式。

通常而言对于module的import被放置在文件的最外层最开始的部分但这并非是必须的。在文件最外层被导入的module其module name会被添加到当前文件的module glboal namespace中。

from ... import ...

import还存在一个变体可以从module中直接导入name而不是导入module后通过module name访问module中内容

from fibo import fib, fib2
fib(500)

在上述示例中,fibo并没有被引入到当前文件的global namespace中。

除此之外还可以导入module中所有的name

from fibo import *
fib(500)

同样的,fibo也不会被导入到当前global namespace中。

上述from fibo import *语句会导入fibomodule中定义的所有name但是以下划线_开头的名称除外。

通常情况下,不要使用from fibo import *来进行导入因为其引入的name是不确定的可能会覆盖你所定义的部分内容。

as

在对其他module执行import操作时可以通过as来进行重命名

import fibo as fib
fib.fib(500)

上述示例中,fib的名称将会绑定到module fibo

from fibo import fib as fibonacci
fibonacci(500)

execute module as script

当想要将module作为script执行时可以使用如下命令

python fibo.py <arguments>

上述将module作为script执行的命令,和导入module时对module进行执行的行为实际上是一致的,唯一区别如下:

  • module作为script执行: __name__值为__main__
  • module被导入: __name__值为module name

故而在module中包含如下代码

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

可以使module既可作为module被导入也可以作为可执行的脚本其中该代码块只会在module作为main file时才会被执行。

并且在module被导入时sys并不会被导入。

module search path

当module名为spam的module被导入时interpreter会按照如下顺序来进行查找

  1. 首先interpreter会从built-in module中查找spam
  2. 如果步骤1没有查询到其会在sys.path变量定义的路径中查找名为spam.py的文件

sys.path初始化

在python启动时其会初始化module search path初始化后的module search path可以通过sys.path来进行访问。

sys.path中,组成如下:

  • sys.path中第一条entry是包含input script的目录如果存在input script
    • 若未指定input script运行交互式shell运行-c command运行-m module时,sys.path的第一条entry则是当前目录
  • PYTHONPATH环境变量中定义的路径也会被添加到search path中
  • 包含standard python modules及其依赖的拓展modules的路径也会别添加到module search path中
    • 拓展modules在windows平台中后缀名为.pyd,在其他平台中后缀名为.so
    • 其中,standard python modules是平台无关的,被称为prefix
    • extension modules则是平台相关的,被称为exec_prefix

python支持通过-c来直接运行python命令示例如下

python -c 'print(1+22*33)'

python还支持通过-m来直接运行module示例如下

echo '{"name":"John","age":30}' | python -m json.tool

部分系统支持symlinks故而包含input script的目录路径会在input script的symlink被follow之后才进行计算。故而包含symlink的目录并不会被添加到sys.path中

sys.path被初始化之后python程序可以对sys.path进行修改。

sys.path中,包含被运行文件的目录路径被放在了sys.path中最开始的位置,代表如果运行文件路径中如果存在和standard library module相同名称的文件,那么运行文件路径下的同名文件将会覆盖standard library module

standard module

python附带了一个包含standard modules的library。standard modules中部分modules是内置在interpreter中的built modules中的内容可能并不是语言核心操作的一部分但是能够提供对操作系统的访问例如系统调用等。

built-in modules中的module在不同平台是可选的例如winreg module只存在于windows平台。但是sysmodule在所有平台的python interpreter中都包含。

dir function

内置的dir()方法会查看在module中定义了哪些名称其返回了一个sorted string list

>>>import fibo, sys
>>>dir(fibo)
['__name__', 'fib', 'fib2']
>>>dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
 '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
 '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
 '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing',
 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info',
 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info',
 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
 'warnoptions']

当没有为dir()方法传递参数时,dir()方法会列出当前已经定义的名称:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

其列出的名称中包含所有类型变量、module、function等

dir方法并不会列出内置的方法和变量,如果想要查看那些,可以使用dir(builtins)

import builtins
dir(builtins)

packages

packge是一种结构化python modules命名空间的方法其使用了dotted module names。例如module name为A.B其代表的是位于package A下的submodule B。

就像module可以使module的开发者们不用担心使用了和其他module中一样的变量/函数名一样dotted module name可以令module的开发者不用担心使用了和其他module相同的module name。

如果你要定义一系列的modules统一处理不同格式的sound files和sound data可以采用如下的package结构

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

当对package进行导入时python会从sys.path中查询package的子目录。

其中,__init__.py文件用于让python将该目录看作是package__init__.py文件可以是空文件,但是也可以包含对package进行初始化的代码设置__all__变量

在使用package中可以单独导入package中的module

import sound.effects.echo

上述示例中导入了sound.effects.echomodule在对module进行使用时必须采用如下方式指定mdoule的fullname

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

另一种可选的导入方式如下:

from sound.effects import echo

在使用module时只需要指定echo即可

echo.echofilter(input, output, delay=0.7, atten=4)

同时也可以直接导入module中的方法

from sound.effects.echo import echofilter

echofilter(input, output, delay=0.7, atten=4)

在使用import item.subitem.subsubitem这类语法时除了最后的item外其余item必须都要是package最后的item可以是package或module但不能是module中的内容。

import * from package

在对package执行import *的语法时,其导入行为准许如下规范:

  • 如果package的__init__.py文件中定义了__all__变量,其将认为__all__中定义了所有应该被import *所导入的module name

故而package的开发者应当负责在package版本迭代时将__all__进行更新;当然,开发者页可以决定不对import *进行支持,不定义__all__

示例如下,sound/effects/__init__.py中可能包含如下内容:

__all__ = ["echo", "surround", "reverse"]

其代表from sound.effect import *将会导入echo, surround, reverse三个module。

注意本地定义的名称可能会遮挡submodules。例如如果在sound/effects/__init__.py中定义了一个reverse函数那么from sound.effects import *将只会导入echo, surround两个module。

__all__ = [
    "echo",      # refers to the 'echo.py' file
    "surround",  # refers to the 'surround.py' file
    "reverse",   # !!! refers to the 'reverse' function now !!!
]

def reverse(msg: str):  # <-- this name shadows the 'reverse.py' submodule
    return msg[::-1]    #     in the case of a 'from sound.effects import *'

如果在__init__.py中没有定义__all__,那么from sound.effects import *只会导入__init__.py中的其他定义内容,示例如下:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

包内部相互引用

在同一个包中不同subpackage中的module可以对彼此进行引用。引用支持绝对路径和相对路径例如sound.filters.vocoder在对sound.effects中定义的包进行import时可以采用如下方式

  • 绝对路径:from sound.effects import echo
  • 相对路径:from ..effects import echo

在使用相对路径的导入时,示例如下: 当从surround执行相对路径导入时,可以执行如下形式的导入

from . import echo # 位于相同的subpackage中
from .. import formats # 访问../formats
from ..filters import equalizer # 访问../filters中的equalizer

class

python在对对象进行传递时只支持引用传递传递指向对象的指针而不支持类似c++的值传递对象拷贝。故而在python中如果对方法的入参对象进行了修改那么该修改对调用方可见。

python scope and namespaces

namespace

namespace代表name到object的映射。

常见的namespace有

  • 包含built-in names的namespacebuilt-in names包含类似abs之类的built-in函数以及built-in exception names等
  • 包含module global names的module namespace
  • 在函数调用时包含local names的local namespace

从某种意义上说对象中的attributes也构成了一个namespace。

对于module.name的访问,其实际也是针对module objectattribute的访问module中的global name和module attribute都是位于同一namespace中。

attribute分为read-onlywritable的。对于writable的attribute可以对其进行赋值示例如下

modname.the_answer = 42

同样的,writable的attribute也可以通过del来进行删除删除后该attribute将会从对象中被移除

del modname.the_answer
lifetime

在不同时机创建的namespace拥有不同的生命周期

  • built-in namespace: 在python interpreter启动时被创建并且该namespace永远不会被删除
  • module namespace: 当module的定义被读入时会创建module namespace并且module namespace会持续到interpreter退出时
  • 对于被interpreter执行的top-level statements通过交互式输入或从script中读取其被视作__main__module的一部分其拥有属于自己的namespace
  • local namespace of function:在函数被调用时创建,在函数执行完成/抛出未处理异常时namespace被删除对于递归调用每次调用都拥有其自己的namespace
LGEB

在python中LEGB规则决定了命名空间搜索的顺序

  • local: 定义在函数/类内部
  • enclosed:定义在闭包函数内
  • global:定义在全局/最上层
  • built-inpython中内置的保留名称
scope

scope代表python程序中的一个文本范围在该范围内可以对某个namespace中的name进行直接访问而无需前缀x.y.之类的限定名。

在程序执行的某个时间点都会存在3/4个nested scopes可以直接访问

  • 最内层的scope包含local names最先查找
  • scope of enclosing function查找时会从nearest neclosing scope开始包含non-local and non-global names
  • next-to-last scope包含global names
  • 最外层scope最后查找包含built-in names
global

如果使用global var_name的方式来声明变量那么针对该变量的引用和复制都会直接指向global namespace中的变量。

nonlocal

如果想要在local namespace中访问enclosing namespace中的name可以通过nonlocal var_name形式来声明变量,示例如下:

def main():
        s = "fuck"
        def s_change(p):
                nonlocal s
                s=p
            s_change("shit")
        print(s)

如果没有通过nonlocal进行声明那么对于s的赋值将会看作是在innermost scope中创建了一个同名变量并进行赋值,外部的变量值并不会改变。

通常来说在函数定义内scope为innermost scope而在函数外则是global namespace类定义会新开一个namespace

global namespace

对于module中定义的function其global namespace即是module namespace无论函数在哪里被调用。

在python中如果没有使用globalnonlocal那么变量是只读的对name的赋值永远会在innermost scope中。del语句则是从local scope对应的namespace中移除变量的绑定。

当在innermost scope中如果只对外层变量进行读操作那么无需使用global或是nonlocal;但是,如果想要修改外层变量,则需要使用global或者nonlocal,否则外层变量是只读的。

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

在上述示例中,返回结果如下:

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

Class Objects

class对象支持两种类型的操作attribute referenceinstantiation

attribute reference使用obj.name的语法。class定义如下:

class MyClass:
        """A simple example class"""
        i = 12345

        def f(self):
                return 'hello world'

MyClass.iMyClass.f是有效的attribute reference会返回一个integer和一个function。

__doc__也是一个有效的attribute返回对该class的简单文档描述。

类实例化类似于函数的调用,其语法如下:

x = MyClass()

上述示例会创建一个MyClass类型的对象并且将其赋值给本地变量x。

init

python中支持在初始化时为对象指定状态故而可以自定义构造器示例如下

class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

c = Complex(3.0, -4.5)

data attributes

python中的data attributes类似于c++中的data members但是data attributes并不需要被声明其类似于local variables其在第一次赋值时就存在了。

例如,变量x是MyClass类的实例,那么如下代码将会输出16.

method object

除了通过data attribute外另一种attribute reference的方式是method。

在python中class中的function定义了object中的method。但是x.fMyClass.f并不等价前者是method object后者是function object。

通常method在其所绑定的对象后调用

x.f()

python中x.f为一个method object支持被存储在变量中并可以在后续通过变量调用示例如下

xf = x.f

while True:
    print(xf())

如上所示method object x.f被存储在local variable xf中后,后续可以通过xf()来调用method object。

按照MyClass的类定义,x.f方法将会接收一个self参数,但是在调用xf()时,并没有传递任何参数。

实际上对于method而言其method所绑定的对象实例将会作为参数被传向function的第一个参数self

故而对于method object和function object而言x.f()实际上等价于MyClass.f(x)

对于method object而言obj.method(arg1, arg2 ...)的调用等价于Class.method(obj, arg1, arg2 ...)的function调用。

class and instance variables

简单来说每个对象的instance variables是相互独立的但是class variables则是在同一类的所有对象间都是共享的。

class Dog:

    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name):
        self.name = name    # instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'

对于class variables而言其既可以通过MyClass.name来进行访问,也可以通过obj.name来进行访问。

但是在class variable和instance variable同名时instance variable会优先被访问示例如下

class Warehouse:
       purpose = 'storage'
       region = 'west'

    w1 = Warehouse()
print(w1.purpose, w1.region)
storage west
w2 = Warehouse()
w2.region = 'east'
print(w2.purpose, w2.region)
storage east

在python中用户既可以通过method来访问data attributes也可以通过对象直接访问。并且python中也没有类似其他语言中private这类强制隐藏的机制,python中都是基于约定

在使用data attributes时应当小心如下场景

  • data attributes可能被method维护和管理若用户不经过method直接修改attribute的值可能会造成attribute的状态被破坏没有经过method中的相关校验直接将attribute改为错误值
  • 用户可以在避免命名冲突的前提下向data attributes添加自己的值

在python中对象中method的第一个参数名为self,这只是一个约定,self在python中并没有特别的意义。

function object

python中function object为一个class attribute用于定义method。function object并不一定要定义在class内部示例如下

# Function defined outside the class
def f1(self, x, y):
        return min(x, x+y)

class C:
        f = f1

        def g(self):
                return 'hello world'

    h = g

在上述示例中,类C包含f, g, h三个attributesC实例中也包含f, g, h三个method。其中hg完全等价。

在python的class定义中method可以通过self.xxx调用其他method示例如下

class Bag:
        def __init__(self):
                self.data = []

            def add(self, x):
                self.data.append(x)

            def addtwice(self, x):
                self.add(x)
                self.add(x)

method中同样可以访问global namesmethod关联的global scope是method定义所位于的module。

在python中每个值都是一个对象其class信息存储在object.__class__中。

Inheritance

在python中支持类的继承示例如下

class DerivedClassName(BaseClassName):
        <statement-1>
            .
            .
            .
            <statement-N>

attribute resolving

若派生类B继承了基类A那么在对B的对象进行attribute解析时其首先会查找派生类如果在派生类中没有找到则会继续在基类中查找。

method resolving

method解析也会从派生类开始沿着基类逐渐查询直到找到对应的function object。

在python中派生类的方法会覆盖基类的方法用c++中的概念来理解即python中所有的method都是virtual的。

在python中如果派生类方法中想要调用基类的方法可以通过BaseClassName.methodname(self, arguments),其类似于其他语言中的super

built-in functions

python中包含如下built-in function来判断继承关系

  • isinstance: 该方法通常用于检查是否实例属于某一个类
    • isinstance(obj, int)
  • issubclass: 该方法通常用于检查是否一个类派生自另一个类
    • issubclass(bool, int): 该调用返回为Truebool是int的子类

private variables

python中并不存在只能从类内部访问的private instance variable。但是python编码中存在一个规范约束

  • _开头的名称应当被作为api中的非公开部分下划线开头的可以是function, method, data member

name mangling

在类定义中,任何以__spam定义的name至少两个前缀下划线最多一个后缀下划线都会被替换为_classname__spam的形式。

name magling在不破坏列内部调用的场景下十分有用示例如下

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

在上述代码中,即使在MappingSubclass中引入__update,其名称也为_MappingSubclass__update,和_Mapping__update不同,仍然不会对父类造成影响。

在类中以__spam定义的变量,在类外部可以以_classname__spam进行访问。

odds and ends

有时想使用类似C语言中的struct惯用方法是使用dataclasses,示例如下所示:

from dataclasses import dataclass

@dataclass
    class Employee:
        name: str
        dept: str
        salary: int
>>> john = Employee('john', 'computer lab', 1000)
>>> john.dept
'computer lab'
>>> john.salary
1000

__self__, __func__

instance method object拥有如下属性

  • __self__: m.__self__代表method m关联的实例对象
  • __func__: m.__func__代表method关联的function object

iterators

在python中大多数容器对象都可以通过for-statements进行迭代

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one':1, 'two':2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

在底层for-statement针对容器对象调用了iter()方法,该方法会返回一个iterator对象iterator对象中定义了__next__()方法,该方法在终止时会抛出StopIteration异常。

可以通过内置的next()方法来调用__next__(),示例如下所示:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
        next(it)
    StopIteration

如果想要为自定义类添加iterator可以在类中定义一个__iter__()方法,该方法返回一个带__next__()方法的对象。如果一个class中包含了__next__()方法的定义那么__iter__()方法仅需返回self即可

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

generators

Generators可以简单的创建iterator其编写和函数类似但是当想要返回值时使用yield statement

每次调用next()generator都会从上次中止的地方进行恢复。示例如下所示

def reverse(data):
        for index in range(len(data)-1, -1, -1):
                yield data[index]
>>> for char in reverse('golf'):
    print(char)

f
l
o
g

通过generator完成的任何事情都可以通过iterator来完成但是generator的代码更加紧凑__iter____next__方法都是自动生成的。

在generator的多次next调用之间local variables的状态和执行状态也是自动保存的。

generator令iterator的编写更加简单。

generator expression

一些简单的迭代器可以简化为表达式:

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> unique_words = set(word for line in page  >>> for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

asyncio

python支持通过async/await语法编写并发代码。asyncio是多个python异步框架的基础。

Coroutines

可以通过async/await的语法来编写asyncio application。例如如下代码将打印hello并等待1s然后打印world

>>> import asyncio

>>> async def main():
        print('hello')
        await asyncio.sleep(1)
        print('world')

>>>  asyncio.run(main())
hello
world

如果仅简单调用main()并不会对该coroutinue进行调度

>>> main()
<coroutine object main at 0x1053bb7c8>

为了实际的运行coroutinueasyncio提供了如下机制

  • asyncio.run:在上述示例中,asyncio.run用于执行main函数的entry point

  • awaiting on coroutinue如下代码片段将会在1s后打印hello并在2s后打印world

    import asyncio
    import time
    
    async def say_after(delay, what):
            await asyncio.sleep(delay)
            print(what)
    
        async def main():
            print(f"started at {time.strftime('%X')}")
    
            await say_after(1, 'hello')
            await say_after(2, 'world')
    
            print(f"finished at {time.strftime('%X')}")
    
        asyncio.run(main())
    

    上述示例总共耗费3s首先等待hello 1s再等待world 2s

  • asyncio.create_task()该方法支持创建Tasks让多个coroutinue并发运行

    async def main():
            task1 = asyncio.create_task(
                    say_after(1, 'hello'))
    
            task2 = asyncio.create_task(
                    say_after(2, 'world'))
    
            print(f"started at {time.strftime('%X')}")
    
            # Wait until both tasks are completed (should take
            # around 2 seconds.)
            await task1
            await task2
    
            print(f"finished at {time.strftime('%X')}")
    

    上述示例总共耗费2s等待hello 1shello打印后再过1s打印world

  • asyncio.TaskGroup:该方法相较asyncio.create_task提供了一个更加现代的api示例如下

    async def main():
            async with asyncio.TaskGroup() as tg:
                    task1 = tg.create_task(
                            say_after(1, 'hello'))
    
                    task2 = tg.create_task(
                            say_after(2, 'world'))
    
                    print(f"started at {time.strftime('%X')}")
    
                # The await is implicit when the context manager exits.
    
            print(f"finished at {time.strftime('%X')}")
    

    当前示例总体耗时也为2s

Awaitables

当对象可以在await表达式中使用时该对象被称为Awaitable object。许多asyncio api被定义为接收awaitable object。

总共有3中awaitablescoroutinue, Tasks, Futures

Coroutinues

python中coroutinue是awaitable并且可以在其他coroutinue中通过await调用:

import asyncio

async def nested():
        return 42

async def main():
        # Nothing happens if we just call "nested()".
    # A coroutine object is created but not awaited,
    # so it *won't run at all*.
    nested()  # will raise a "RuntimeWarning".

        # Let's do it differently now and await it:
    print(await nested())  # will print "42".

asyncio.run(main())

Tasks

Tasks用于并发的对coroutinues进行调度。

当通过asyncio.create_task()将coroutinue封装在Task中时coroutinue将会自动的被调度

import asyncio

async def nested():
        return 42

async def main():
        # Schedule nested() to run soon concurrently
    # with "main()".
    task = asyncio.create_task(nested())

    # "task" can now be used to cancel "nested()", or
    # can simply be awaited to wait until it is complete:
    await task

    asyncio.run(main())

Futures

Future是一个底层的awaitable对象代表一个异步操作的最终结果。

当一个Future对象被awaited时代表coroutinue将会等待直至Future执行完成。

Creating Tasks

asyncio.create_task(coro, *, name=None, context=None)

将coroutinue封装在Task对象中并且对其进行调度。该方法会返回一个Task object。

该task将会在get_running_loop()返回的loop中执行如果当前线程中没有running loop那么将会抛出RuntimeError

Task Cancellation

task可以被简单和安全的取消。当task被取消时并不会立马中断task抛出asyncio.CancelledError而是等到task下一次执行await时才会抛出异常。

推荐通过try/finally来执行clean-up逻辑当显式捕获的asyncio.CancelledError通常在clean-up操作完成后都应该对异常重新抛出。

asyncio中存在部分组件允许结构化的并发例如asyncio.TaskGroup其内部实现中使用了cancellation如果coroutinue吞了异常将会导致asyncio中TaskGroup等组件行为异常

类似的,用户代码中通常也不应该调用uncancel

但是,如果真的需要对asyncio.CancelledError进行suppress其在吞异常之后还需要调用uncancel来取消cancellation state。