Python学习笔记
- Windows:可以使用WinPython,这是一个免安装、开箱即用的Python发布版,包含很多预置工具
- Linux:通常已经随操作系统安装
环境变量 | 说明 | ||
PYTHON_HOME | Python安装目录 | ||
PATH | 添加 %PYTHON_HOME%;%PYTHON_HOME%\Scripts | ||
PYTHONPATH |
Python的模块搜索路径,在前面出现的优先级高 仅在Windows下你可能需要手工设置此环境变量 Linux的模块安装位置与Windows不同:
可以使用下面的命令得到PYTHONPATH:
|
Ubuntu14.04.3自带的Python版本时2.7.6和3.4.3。你可以下载并构建自己的版本,但是不要全局的安装,替换系统的python2、python3符号链接可能导致系统无法工作。
下面的脚本示例如何安装3.5版本的Python:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
cd ~/Downloads/3.5.1 ./configure --prefix=$HOME/Python/3.5.1 make && make install # 检查PYTHONPATH cd $HOME/Python/3.5.1/bin ./python3 -c "import sys; print ('\n'.join(x for x in sys.path if x))" # 输出如下: # /home/alex/Python/3.5.1/lib/python35.zip # /home/alex/Python/3.5.1/lib/python3.5 # /home/alex/Python/3.5.1/lib/python3.5/plat-linux # /home/alex/Python/3.5.1/lib/python3.5/lib-dynload # /home/alex/Python/3.5.1/lib/python3.5/site-packages |
Ubuntu下,Python解释器的默认安装位置为/usr/bin/python;Windows下是C:\python27。需要将其添加到环境变量PATH中,然后打开Shell窗口:
1 2 3 4 5 6 7 8 9 10 |
#运行交互式的解释器 python #输入文件结束符(Unix的Ctrl+D、Windows的Ctrl+Z)可以退出解释器 #调用quit()函数亦可退出解释器 #启动解释器并执行命令,应当用单引号包围命令,防止有空格之类的特殊字符 python -c command [arg] ... #调用作为脚本使用的模块 python -m module [arg] ... |
调用解释器时,脚本名、参数传入字符串列表sys.argv中,该列表至少有一个元素。
- 没有给定脚本名、参数时,sys.argv[0] = ''
- 脚本名指定为标准输入时,sys.argv[0] = '-'
- 使用-c参数调用解释器时,sys.argv[0] = '-c'
- 使用-m参数调用解释器时,sys.argv[0] = 模块全名
从终端读取命令并执行,称为交互模式,以主提示符(>>>)为依据执行,输入多行结构,则需要附加从属提示符(...),例如:
1 2 3 4 |
>>> if name == 'Alex': ... print "Hello " + name ... Hello Alex |
有错误发生时,解释器在stderr上打印错误信息、调用栈跟踪。在交互模式下,会返回主提示符;如果从文件输入执行,则以非零状态退出。
使用try的except子句可以捕获异常
Linux下,Python脚本可以直接执行(需要chmod +x):
1 2 |
#! /usr/bin/env python ... |
在Windows下,安装程序会把*.py与python.exe关联,可以双击执行,*pyw类似,但是不显示控制台窗口
如果需要让解释器在每次启动时均执行一个脚本,可以设置环境变量:PYTHONSTARTUP,这类似于Linux Shell的.profile文件
钩子方法sitecustomize、usercustomize用于提供本地化。
1 2 3 4 |
print "The answer is", 2*2 print("The answer is", 2*2) # 可以定制打印项之间的分隔符 print("There are <", 2**32, "> possibilities!", sep="") |
字典的 dict.keys(), dict.items(), dict.values()方法返回的不再是列表,而是“视图”。因此:
1 2 3 |
k = d.keys(); k.sort() # ERR k = sorted(d) # OK |
map(), filter(), zip()等返回迭代器。要获得列表,可以用 list()包装,或者使用列表推导:
1 |
result = [ x for x in map()] |
在Python 3中,使用文本和(二进制)数据两个概念,来代替Uincode字符串和8-bit字符串。所有文本都是基于Unicode的,但是编码后的Unicode表示为二进制数据。存储文本的类型是 str。存储二进制数据的类型为 bytes。
不再需要使用u前缀来表示文本: u"...",但是要表示二进制数据直接量则必须使用前缀 b"..."。
任何混合文本、数据的操作会导致TypeError。你必须明确的进行转换:
1 2 3 4 |
# 编码为二进制数据 str.encode() # 编码为Unicode文本 bytes.decode() |
在原始字符串中,反斜杠被原样看待,例如: r'\u20ac' 是6字符的串。
PEP 3107 – Function Annotations引入了为函数添加任何元数据(注解)的能力,注意这些注解没有任何具体语义,不改变函数的运行时行为,仅仅用于文档、类型提示(这是最重要的用法)以及为第三方框架提供信息。
语法形式:
1 2 3 4 5 |
def my_function(arg1: annotation1, arg2: annotation2) -> annotation3: # 类型提示的例子: def greet(name: str, age: int) -> str: return f"Hello, {name}! You are {age} years old." |
调用函数时,这种参数必须使用关键字参数语法来传入。要定义仅关键字参数,使用一个 *号,其后面的参数均为仅关键字参数:
1 2 3 4 |
def my_function(arg1, *, kwarg1, kwarg2): print(f"arg1: {arg1}, kwarg1: {kwarg1}, kwarg2: {kwarg2}") my_function(10, kwarg1="a", kwarg2="b") |
用于直接对外层作用域(非顶级作用域)中的变量进行赋值:
1 2 3 4 5 6 |
def outer_function(): outer_var = 10 def inner_function(): nonlocal outer_var # Declare outer_var as nonlocal to access it from the outer_function scope outer_var += 5 |
用于接收迭代器中所有其它对象:
1 |
(a, *rest, b) = range(5) # rest为 [1, 2, 3 ] |
类似与列表推导:
1 2 3 4 |
{k: v for k, v in stuff} squares = {i: i**2 for i in range(1, 6)} even_squares = {i: i**2 for i in range(1, 11) if i % 2 == 0} |
例如: {1, 2},注意{}表示空字典而非集合。空集合是 set()。
集合推导的语法和列表推导一样: {x for x in stuff}
1 2 |
0o720 0b1010 |
1 2 3 4 5 6 |
# 不在支持: class C: __metaclass__ = M # 新语法: class C(metaclass=M): |
原先字典迭代的顺序是任意的,可以使用 collections.OrderedDict来保证迭代顺序(按插入顺序迭代):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from collections import OrderedDict # Creating an OrderedDict fruits = OrderedDict([ ('apple', 4), ('banana', 6), ('orange', 2), ('grapes', 10), ]) # Adding a new item to the OrderedDict fruits['strawberry'] = 8 # Output: apple -> 4, banana -> 6, orange -> 2, grapes -> 10, strawberry -> 8 for fruit, count in fruits.items(): print(f"{fruit} -> {count}", end=", ") |
1 2 |
print("{}, {}".format("a", "b")) # It now implicitly auto-numbers the fields. Output: "a, b" print("{:,}".format(9876543210)) # The comma format specifier. Output: "9,876,543,210" |
1 2 3 4 5 |
import math nums = [1e20, 1, -1e20] print(sum(nums)) # Result: 0.0, which can have an accumulated error print(math.fsum(nums)) # Result: 1.0, a mathematically accurate summation |
强大、灵活的命令行参数解析模块,支持位置参数、子命令等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import argparse def main(): parser = argparse.ArgumentParser(description="A simple script demonstrating argparse.") parser.add_argument("-n", "--name", required=True, help="Your name") parser.add_argument("-a", "--age", type=int, help="Your age") args = parser.parse_args() print(f"Hello, {args.name}!") if args.age: print(f"You are {args.age} years old.") if __name__ == "__main__": main() |
提供了基于线程/进程等方式来异步执行callable的高层接口,简化了工作线程、进程的管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import concurrent.futures import time def perform_work(n): time.sleep(n) return f"Work completed after {n} seconds" # Run two tasks concurrently using ThreadPoolExecutor: with concurrent.futures.ThreadPoolExecutor() as executor: work_items = [2, 3] results = [executor.submit(perform_work, n) for n in work_items] for result in concurrent.futures.as_completed(results): print(result.result()) # Run two tasks concurrently using ProcessPoolExecutor: with concurrent.futures.ProcessPoolExecutor() as executor: work_items = [2, 3] results = [executor.submit(perform_work, n) for n in work_items] for result in concurrent.futures.as_completed(results): print(result.result()) |
该表达式允许一个生成器将它的部分操作,委托给另外一个生成器。当一个生成器在迭代由另外一个生成器产生的条目时,可以简化代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def concat_gen(list_of_generators): for gen in list_of_generators: yield from gen # 每次调用触发一次yield,第一个生成器完毕后,才循环到第二个 gen1 = (x for x in range(1, 4)) # Generates 1, 2, 3 gen2 = (x for x in range(4, 7)) # Generates 4, 5, 6 # Concatenate the generators using `yield from` result_gen = concat_gen([gen1, gen2]) for x in result_gen: print(x) # Output: 1, 2, 3, 4, 5, 6 |
用于代替第三方虚拟(隔离)环境模块virtualenv。pyvenv脚本用于管理虚拟环境。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Create a virtual environment python -m venv my_virtual_env # Activate the virtual environment (on Linux or macOS) source my_virtual_env/bin/activate # Activate the virtual environment (on Windows) my_virtual_env\Scripts\activate.bat # Install packages within the virtual environment pip install requests # Deactivate the virtual environment when done deactivate |
支持基于LZMA算法的压缩,也就是那些扩展名为.xz .7z .lzma的压缩包。
1 2 3 4 5 6 7 8 9 10 11 |
import lzma data = b"Example data that will be compressed using LZMA." # Compress data using LZMA compressed_data = lzma.compress(data) # Decompress the data back to its original form original_data = lzma.decompress(compressed_data) assert data == original_data, "Decompressed data should match the original data." |
用于在关键事件(例如段错误)时dump出Python的调用栈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import faulthandler import os import time import sys # Enable faulthandler to dump tracebacks to a file with open("traceback.log", "w") as logfile: faulthandler.dump_traceback(file=logfile) # You can also enable fault handling for uncaught exceptions and signals (e.g., SIGSEGV) faulthandler.enable(file=sys.stderr, all_threads=True) # Your program code... time.sleep(1) |
引入一种机制,允许单个包跨越多个目录,可以实现更好的模块化,并且让过大的包易于维护。例如对于下面的目录结构:
1 2 3 4 5 6 7 8 9 |
dir1/ my_package/ __init__.py module1.py dir2/ my_package/ # no __init__.py file needed module2.py |
可以将my_package看作单一的命名空间包,不需要任何额外配置,导入该包中定义的两个模块:
1 2 |
from my_package.module1 import class1 from my_package.module2 import class2 |
这个模块用于实现异步IO、并发编程。从3.5开始,可以利用关键字 async / await。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import asyncio async def hello(): print("Hello") await asyncio.sleep(1) print("World") async def main(): # Schedule two coroutines to run concurrently task1 = asyncio.create_task(hello()) task2 = asyncio.create_task(hello()) await task1 await task2 # Execute main asynchronously using asyncio.run (Python 3.7 and later) asyncio.run(main()) |
注意,对async函数的调用,会得到一个协程(或者生成器),而不是直接同步的执行函数体:
1 2 3 4 5 6 7 8 9 10 |
# 异步函数(协程) async def async_function(): return 1 print(type(async_function()) is types.CoroutineType) # 异步生成器 async def async_generator(): yield 1 print(type(async_generator()) is types.AsyncGeneratorType) |
你可以向操作普通协程那样,对其调用send():
1 2 3 4 5 |
try: async_function().send(None) except StopIteration as e: # 生成器/协程在正常返回退出时会抛出一个StopIteration异常,而原来的返回值会存放在StopIteration对象的value属性中 print(e.value) |
在async函数中,可以使用await挂起自身,并等待另外一个协程的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
async def async_function(): return 1 async def await_coroutine(): # await语法只能出现在通过async修饰的函数中,否则会报SyntaxError错误 result = await async_function() print(result) run(await_coroutine()) # 注意 await后面必须跟着一个 Awaitable,或者实现了 __await__ 方法: class Awaitable(metaclass=ABCMeta): __slots__ = () @abstractmethod def __await__(self): yield @classmethod def __subclasshook__(cls, C): if cls is Awaitable: return _check_methods(C, "__await__") return NotImplemente |
可以方便的定义简单的枚举类。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from enum import Enum class Color(Enum): RED = 1 GREEN = 2 BLUE = 3 # Usage examples print(Color.RED) # Output: Color.RED print(Color.RED.name) # Output: RED print(Color.RED.value) # Output: 1 # 可以让枚举类同时继承A,这样它就有了A类型的能力,例如用在需要A的地方 |
用于简化文件系统路径操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from pathlib import Path path = Path("example.txt") # Check if the file exists if not path.exists(): # Create a new file and write some content path.write_text("Hello, World!") # Read file content content = path.read_text() print(content) # Output: Hello, World! # List all files in the current directory for file_path in Path(".").iterdir(): print(file_path) |
为pickle增加了新版本(4)的默认协议,支持更加高效的、针对大规模数据结构的串行化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import pickle data = { "name": "Alice", "age": 30, "city": "New York", } # Serialize data using protocol version 4 (new in Python 3.4) serialized = pickle.dumps(data, protocol=4) # Deserialize the data back to a Python object deserialized_data = pickle.loads(serialized) assert data == deserialized_data, "Deserialized data should match the original data." |
提供了事件驱动的IO框架、在套接字以及其它非阻塞IO上的多路复用的IO操作支持。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# This example shows how to use `selectors` for a simple echo server. # For brevity, error handling is omitted. This example is for Linux/Unix-based systems using `selectors.DefaultSelector`. import socket import selectors sel = selectors.DefaultSelector() def accept(sock): conn, addr = sock.accept() conn.setblocking(False) sel.register(conn, selectors.EVENT_READ, read) def read(conn): received_data = conn.recv(1000) if received_data: # Echo the received data back to the client conn.sendall(received_data) else: # Connection closed, unregister the socket from selector sel.unregister(conn) conn.close() server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('localhost', 1234)) server_socket.listen(5) server_socket.setblocking(False) sel.register(server_socket, selectors.EVENT_READ, accept) while True: events = sel.select() for key, _ in events: key.data(key.fileobj) |
通过装饰器 functools.singledispatch实现,用于定义多个版本的函数实现,每个函数针对不同的参数类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from functools import singledispatch @singledispatch def process_data(data): print(f"Unknown data type: {data}") @process_data.register(int) def _(data: int): print(f"Processing integer data: {data}") @process_data.register(str) def _(data: str): print(f"Processing string data: {data}") process_data(42) # Output: Processing integer data: 42 process_data("Hello") # Output: Processing string data: Hello process_data([1, 2, 3]) # Output: Unknown data type: [1, 2, 3] |
1 2 3 4 5 6 7 8 9 |
import numpy as np mat1 = np.array([[1, 2], [3, 4]]) mat2 = np.array([[5, 6], [7, 8]]) mat_product = mat1 @ mat2 # Matrix multiplication using new @ operator # Output: array([[19, 22], [43, 50]]) print(mat_product) |
可以在单个函数调用、单个推导(comprehension)操作中,从多个可迭代对象中解包多个元素:
1 2 3 4 5 6 7 8 9 10 |
a = [1, 2] b = [3, 4, 5] # Merging lists using unpacking generalizations merged_list = [*a, *b] # Output: [1, 2, 3, 4, 5] dict1 = {'a': 1, 'b': 2} dict2 = {'c': 3, 'd': 4} merged_dict = {**dict1, **dict2} # Output: {'a': 1, 'b': 2, 'c': 3, 'd': 4} |
包 typing提供了增强的类型提示能力,例如,可以指定容器类型的元素的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from typing import List, Dict # 列表值类型 字典键值了类型 非容器类型 def greet(names: List[str], age_dict: Dict[str, int]) -> str: greetings = [] for name in names: age = age_dict.get(name) greeting = f"Hello, {name}! You are {age} years old." greetings.append(greeting) return "\n".join(greetings) names_list = ["Alice", "Bob", "Charlie"] ages_dict = {"Alice": 25, "Bob": 30, "Charlie": 35} result = greet(names_list, ages_dict) print(result) |
除了常用的集合类型容器,typing模块还提供了:
- Optional:用于提示参数或者字段是可选的
- Union:用于提示参数可能是多个类型之一
- Any:不限制类型
- Callable:提示参数是可调用的
对于复杂的、反复使用的类型提示,可以定义别名:
1 2 |
Person = Tuple[str, int] People = List[Person] |
这个语句初始化一个异步的上下文管理器。异步上下文管理器对象必须实现特定的方法,以创建/清理临时上下文,即使有异常抛出,清理工作也会执行。需要实现的方法如下:
- async def __aenter__(self):进入async with块时该方法被调用
- async def __aexit__(self, exc_type, exc_value, traceback):离开块时该方法被调用
示例:
1 2 3 4 5 6 |
import aiofiles async def read_file(file_name: str) -> str: async with aiofiles.open(file_name, mode="r") as f: text = await f.read() return text |
和同步的上下文管理器的主要区别是:资源创建、主体逻辑、资源清理都是异步进行的,而后者这三个操作是同步进行(即在同一个事件循环中、阻塞其它操作)。
1 2 3 |
name = "John" age = 25 print(f"Hello, my name is {name} and I'm {age} years old.") |
1 2 |
one_million = 1_000_000 print(f"One million is written as {one_million}.") |
1 2 3 4 5 |
count: int = 0 def greet(name: str) -> str: return f'Hello, {name}' print(greet("John")) |
可以对列表、集合、字典以及列表推导、生成器等使用 async for操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import asyncio async def ticker(delay, to): """Yield numbers from 0 to `to` every `delay` seconds.""" for i in range(to): yield i await asyncio.sleep(delay) async def run(): # 这里的迭代是异步进行的 async for i in ticker(1, 5): print(i) asyncio.run(run()) |
这个特性使得使用前向引用(forward references ,即引用尚未定义的类型)和生命类型提示变得简单,不再需要在类型注解中使用字符串直接量。
当存在类型之间的循环引用时,类型注解可能会导致问题,为了解决此问题,需要使用字符串直接量:
1 2 3 4 5 6 7 |
class A: def x(self) -> "B": # Using string literal for forward reference pass class B: def y(self) -> A: pass |
有了该特性后,则可以:
1 2 3 4 5 6 7 8 9 |
from __future__ import annotations # Import annotations from __future__ class A: def x(self) -> B: # No need for string literal pass class B: def y(self) -> A: pass |
这个模块提供了一个装饰器,用来装饰一个仅仅用来存放数据的类型:
1 2 3 4 5 6 7 8 9 10 11 12 |
from dataclasses import dataclass @dataclass class Point: x: float y: float p1 = Point(1.0, 2.5) p2 = Point(3.5, 0.5) print(p1) # Output: Point(x=1.0, y=2.5) print(p2) # Output: Point(x=3.5, y=0.5) |
调用该函数,可以直接从代码进入Python Debugger(PDB),可以方便调试:
1 2 3 4 5 |
def divide(a, b): breakpoint() # Debugger will be triggered here return a / b result = divide(4, 2) |
支持将赋值作为表达式的一部分:
1 2 3 4 |
n = 10 while (squared := n * n) < 100: print(squared) n += 1 |
支持指定某些函数参数,必须以位置参数的形式传入:
1 2 3 4 5 6 7 8 |
def my_function(pos1, pos2, /, pos_or_kwarg1, *, kwarg1, kwarg2): pass # Allowed: my_function(1, 2, 3, kwarg1=4, kwarg2=5) # Not allowed: my_function(1, 2, pos_or_kwarg1=3, kwarg1=4, kwarg2=5) |
符号 / 前面的都是仅位置参数。
内置函数可以用来逆转字典键值对顺序:
1 2 3 4 |
my_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4} reversed_items = list(reversed(my_dict.items())) print(reversed_items) # Output: [('d', 4), ('c', 3), ('b', 2), ('a', 1)] |
1 2 3 4 5 6 7 8 |
dict1 = {"a": 1, "b": 2} dict2 = {"b": 3, "c": 4} merged = dict1 | dict2 print(merged) # Output: {"a": 1, "b": 3, "c": 4} dict1 |= dict2 print(dict1) # Output: {"a": 1, "b": 3, "c": 4} |
移除字符串前后缀的新方法:
1 2 3 4 5 6 7 |
filename = "document.pdf" name = filename.removesuffix(".pdf") print(name) # Output: "document" name = filename.removeprefix("docu") print(name) # Output: "ment.pdf" |
1 2 3 4 5 |
from datetime import datetime from zoneinfo import ZoneInfo dt = datetime(2021, 1, 1, tzinfo=ZoneInfo("America/New_York")) print(dt) # Output: 2021-01-01 00:00:00-05:00 |
在老版本,你必须借助typing模块:
1 2 3 4 5 |
from typing import List def process_numbers(numbers: List[int]) -> None: for number in numbers: print(number * 2) |
新版本中直接使用内置类型:
1 2 3 4 5 6 7 8 9 10 11 |
def process_dict(data: dict[str, int]): for key, value in data.items(): print(key, value * 2) def process_tuple(data: tuple[int, int, int]): for value in data: print(value * 2) def process_set(data: set[int]): for value in data: print(value * 2) |
需要注意,某些库可能仍然依赖旧的语法。
不再需要Union
1 2 3 4 5 6 7 8 9 10 11 |
# Before Python 3.10 Release from typing import Union def f(list: List[Union[int, str]], param: Optional[int]): pass # In Python 3.10 Release def f(list: List[int | str], param: int | None): pass # Calling the function f([1, “abc”], None) |
1 2 3 4 5 6 7 8 9 10 11 |
def process_number(number): match number: case 1: print("One") case 2: print("Two") case _: print("Other") process_number(1) # Output: One process_number(5) # Output: Other |
使用match - case语句,可以对复杂结构进行模式匹配:
1 2 3 4 5 6 7 8 9 10 |
def process_data(data): match data: case None: print("No data") case {"status": "success", "result": int(result)}: print(f"Success: {result}") case {"status": "error", "message": str(message)}: print(f"Error: {message}") case _: print("Unknown data format") |
1 2 3 4 5 6 7 8 9 |
with open("file1.txt") as file1, open("file2.txt") as file2: # Before Python 3.10 pass # In Python 3.10 with ( open("file1.txt") as file1, open("file2.txt") as file2, ): pass |
异常组让相关的异常被一起处理。
在Python中,所有异常均是BaseException的子类型。异常具有一个message参数来提供消息:
1 |
raise SyntaxError("Just raising a syntax error") |
使用try - except块可以捕获并处理异常:
1 2 3 4 5 6 |
try: prin(34/0) except (ZeroDivisionError, NameError) as exc: print(exc) except XxError as e: pass |
这种处理方式有以下限制:
- 一次只能处理一个异常
- 仅仅会执行第一个匹配的except块
ExceptionGroup是Exception的子类,你可以像处理普通异常一样处理它:
1 2 3 4 5 6 7 8 |
print(issubclass(ExceptionGroup, Exception)) raise ExceptionGroup("exception groups", [ValueError(1), TypeError(2)]) try: raise ExceptionGroup("An exception group", [ValueError(), TypeError(1)]) except ExceptionGroup: print("I caught an exception group") |
异常组的第二参数,是组中包含的异常的列表。你可以捕获其中任何成员:
1 2 3 4 5 |
try: raise ExceptionGroup("An exception group", [ValueError(), \ TypeError(1)]) except TypeError: print("I am handling the TypeError in the exception group") |
使用特殊的 except *可以捕获多个异常成员:
1 2 3 4 5 6 7 8 9 |
try: raise ExceptionGroup("An exception group", [ValueError(), TypeError(1)]) except * TypeError: print("I am handling a Type error") except * ValueError: print("I am handling a ValueError") # I am handling a Type error # I am handling a ValueError |
1 2 3 4 5 6 7 8 |
from typing import TypeVar T0 = TypeVar("T0") T1 = TypeVar("T1") def flip(pair: tuple[T0, T1]) -> tuple[T1, T0]: first, second = pair return (second, first) |
这个特殊的类型表示当前类的类型。
1 2 3 4 5 |
from typing import Self class Article: def a_method_that_returns_an_instance(self) -> Self: ... |
这个类型允许容器类型具有任意数量的元素类型:
1 2 3 4 5 6 7 8 9 |
from typing import TypeVarTuple, TypeVar, Tuple TS = TypeVarTuple("TS") T = TypeVar("T") def example(value_1:Tuple[T, *TS]): # 必须使用*解包语法 ... example(value_1=(1, 'a number', 3.0)) |
用于限定字典具有哪些键值、值的类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from typing import TypedDict class ArticleType(TypedDict): article_id: int title: str rating: float article_1: ArticleType = { "article_id": 23, "title": "Introducing the new features in Python 3.11", "rating":4.5 } |
在3.11,你可以控制某个键值是否为必须:
1 2 3 4 5 6 |
from typing import TypedDict, Required, NotRequired class ArticleType(TypedDict): article_id: Required[int] title: NotRequired[str] rating: float |
Python的每个语句以换行符结束,如果太长,可以使用续行符(反斜杠)跨行
1 2 |
a = math.cos(3 * (x - n)) + \ math.sin(3 * (x - n)) |
使用三引号定义的字符串、列表、元组或字典分布在多行上时,不需要使用续行符。
缩进用于表示不同的代码块,如函数体、条件语句、循环和类。代码块中首条语句的缩进可以任意的,但是后续语句必须与之保持一致。
如果函数体、分支、循环等较短,可以放在一行,不需要缩进:
1 |
if a: pass |
应当使用空格,而不是制表符进行缩进。
变量标识符仅支持:数字、下划线、A-Za-z,并且数字不能作为标识符的开头。标识符区分大小写。
以下划线开始或结束的标识符具有特殊含义:
- 以单下划线开头的标识符不能通过from module import *导入
- __func__用于定义特殊方法
- __priv用于定义类私有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#Python中,整数的位数是任意的 bigInt = 15156165496484919616 #不同进制的整数 0644 #八进制 0x100fea8 #十六进制 0b1110001 #二进制 #浮点数表示 3.14 1.2334e+02 #使用等号赋值 width = 20 #整数与浮点数运算时,自动转换为浮点数 3 * 3.75 / 1.5 #实数的类型转换函数:int、float、long等 int(3.75) #支持复数,j(或者J)表示虚部 z = 1.5 + 0.5j #获取实部、虚部 z.real z.imag |
标识符True和False被解释为布尔值,其整数值分别是1和0。
字符串、列表、元组,统称为序列类型。序列类型具有共同的特征:
- 支持索引访问,例如s[0]
- 支持切片运算符,例如s[0:5],对于可变序列,还可以删除切片,例如del s[i:j]
- 使用len(s)可以返回序列长度
- 使用min(s)、max(s)可以返回序列中元素的最小最大值
- 使用all(s)、any(s)可以检查是否每个元素、存在任何元素为True
支持双引号或者单引号的字符串。Python字符串是不可变的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#使用单引号、双引号定义多行文本,必须在行尾添加换行+续行符,行首的空白符也会被识别 str = "This is a long string\n \ containing two lines os text \ " #可以用三引号对来标识字符串,不需要\n转义 """ Hello there Greetings """ #原始字符串,不进行转义 str = r"\n is just \n" #字符串可以使用+来连接,使用*来重复: str = str + str * str #字符串切片,起始索引、结束索引如果不指定,分别为0、字符串长度 #容错:如果结束索引过大,自动认为等于字符串长度,如果开始索引小于结束索引,返回空串 str = HelpA word[4] == 'A' word[0:2] == 'He' #如果索引为负数,则表示从右侧算索引 word[-1] = 'A' word[-2] = 'p' word[-2:] = 'pA' word[-0] = 'H' #-0就作为0看待 word[:] = 'HelpA' #这种特殊的写法表示返回完整的字符串 #字符串相关函数 len(s) #返回字符串的长度 str(3.4) #转换为字符串 repr(3.4)#转化为字符串,显示为对象内部精确值:3.3999999999999999 format(3.4,'0.5f') #格式化输出3.40000 |
Python 2.0以后引入Unicode,来表示Unicode字符串,必要时可以与原始字符串进行转换:
1 2 3 4 5 6 |
#使用u前缀表示Unicode字符串 str = u"蟒蛇" #使用Unicode转义\u****来插入特殊Unicode字符 str = u"Hello\u0020World" #Unicode字符串的原始模式(不进行转义) str = ur"Hello\u0020World" |
利用str()函数进行转换时,会使用默认编码(通常是ASCII):
1 2 3 4 |
>>> str(u"蟒蛇") Traceback (most recent call last): File "", line 1, in UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128) |
可以使用encode方法来获取特定编码的16进制转写:
1 2 3 |
u"蟒蛇".encode('utf-8') #内置函数unicode可以使用所有已注册的Unicode编码来解码 unicode('\xc3\xa4\xc3\xb6\xc3\xbc', 'utf-8') |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#列表的元素可以是不同的类型 a = ['spam', 'eggs', 100, 1234] #类似字符串,列表可以被切片或者连接,切片返回浅拷贝的副本 a[0] == 'spam' a[-2] == 100 a[:2] + ['bacon', 2*2] == ['spam', 'eggs', 'bacon', 4] a = [1, 2, 3] + [4, 5] #连接列表 #内置函数len可以用于获取列表的长度 len(2) #相关方法 a = list() #等价于a = [] a.append(1) #在尾部插入元素 a.insert(2,200) #插入元素到指定索引位置 |
在圆括号里面包含一组值,即为元组。元组创建后,不能修改其内容(替换、添加或者删除元素)。使用元组代替小列表,更加节约内存
1 2 3 4 5 6 7 8 |
corp = ("3203001102256", "徐州工业集团", 3200) #定义元组 corp = "3203001102256", "徐州工业集团", 3200 #这样也可以识别元组 a = () #空元组 b = (1,) #一元组,注意结尾的逗号 c = 1, #一元组 #可以使用数字索引获取元组中的值,但是更常见的做法是将元组解包为一组变量 regNo, corpName, regCapi = corp |
集合是无序的、不包含重复元素的对象组:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
s = set ([3,5,9,10]) #创建一个数值集合 t = set ("Hello") #创建包含4个字符的集合(两个l只能保存一个在集合中) #集合运算符 a = t | s #t和S的并集 a = t & s #t和s的交集 a = t - s #求差集(项在t中,但不在s中) a = t ^ s #对称差集(项在t或S中,但不会同时出现在二者中) #集合方法 t.add('x') #添加一个项 s.update([10,37,42])#添加多个项 t.remove('H') #删除一个项 |
字典就是关联数组(散列表)。字符串、元组等可以作为散列键,但是可变对象例如列表、字典则不可以作为键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
prices = { "GOOG" : 490.10, "APPL" : 123.50, "IBM" : 91.50, "MSFT" : 52.13 } #创建空字典 prices = {} prices = dict() #使用in运算符可以测试某个项是否字典成员 if "GOOG" in prices: p = prices["GOOG"] #相关函数 keys = list(prices) #获取关键字的列表 del prices["GOOG"] #删除字典元素 #相关方法 p = prices.get("GOOG", 0.0) #如果不存在,返回0.0 |
None是一个特殊的类型,用于表示null对象。
如果一个函数没有显式的返回值,则自动返回None。布尔求值时为False
1 2 3 4 5 6 7 8 9 10 11 12 13 |
x = int(raw_input("Please enter an integer: ")) if x < 0: x = 0 print 'Negative changed to zero' elif x == 0: print 'Zero' elif x == 1: print 'Single' else: print 'More' #条件表达式简写 minvalue = a if a <= b else b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
a = ['cat', 'window', 'defenestrate'] for x in a: print x, len(x) #逗号分隔的print可以连续打印不换行 #打印: #cat 3 #window 6 #defenestrate 12 #迭代列表的副本,以便安全的修改列表 for x in a[:]: if len(x) > 6: a.insert(0, x) elif False : continue elif False : break elif False : pass #什么都不做,作为语法占位符 #使用range函数可以生成一个等差级数俩表: range(10) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] range(5, 10) #[5, 6, 7, 8, 9] range(-10, -100, -30) #[-10, -40, -70] 第三个参数为步长 #迭代一个列表 a = ['Mary', 'had', 'a', 'little', 'lamb'] for i in range(len(a)): print i, a[i] #或者 for item in a: print item #解包一个序列,变量个数必须与序列元素个数一致 for x,y,z in s: pass #内置函数enumerate,迭代序列,返回索引、元素组成的元组 for i,x in enumerate(s): pass #同时迭代两个以上的序列,使用内置的zip函数 for x,y,z in zip(r,s,t): pass #迭代一个字典 c = {"GOOG": 490.10, "IBM":91.50 } for key in c: print key, c[key] #打印一个文件中所有行 f = open("foo.txt") for line in f: print line |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#关键字def引入函数定义,其后必须跟有函数名、形参列表 def reminder(a, b): #函数体必须缩进,可以添加docstring """docstring""" #使用remider.__doc__可以访问文档字符串 q = a // b #截断除法运算符 r = a - q * b return r #如果省略return语句,自动返回None #如果函数要返回多个值,可以使用元组 def divide(a, b): q = a // b r = a - q*b return (q, r) #形参可以具有默认值 def connect(host ,port, timeout = 300): h0 = host #在函数体内创建的变量,作用域是局部的,函数退出后自动销毁 global h0 = host #使用此关键字来修改全局变量的值 pass #位置参数:要使用函数,只需要传入实参列表即可,参数数量、顺序必须匹配,否则引发TypeError reminder(88, 3) #可以省略提供默认值的参数 connect('gmem.cc', 80) #关键字参数:可以按照任意顺序提供参数,但是需要提供形参名称 connect(port = 80, host = 'gmem.cc') #使用可变对象作为形参默认值,会导致意外的行为:每次调用,可能使用同一个对象 def add(x, items=[]): items.append(x) return items add(1) #返回[1] add(2) #返回[1, 2],不符合预期 #在最后一个参数前面加*,可以接受任意数量的位置参数,自动存入元组 def fprintf(file, fmt, *args): file.write(fmt % args) fprintf(out,"%d %s %f", 42, "hello world", 3.45) #args == (42, "hello world", 3.45) #在最后一个参数前加**,则所有额外的关键字参数存入字典 def make_tab(data, **params): fc = params.pop('fgcolor','black') make_tab(items, fgcolor='red') #位置参数与关键字参数可以一起使用,**必须出现在最后面 #编写代理、包装器函数时,经常需要使用*args、**kwargs def func(*args, **kwargs): pass |
Python支持类型注解:
1 2 3 4 5 6 7 8 9 |
# Python 3 类型注解写法 # 参数类型 # 返回值类型 def add(x:int, y:int) -> int: return x + y # Python 2写法 def add(x, y): return x + y |
类型注解的作用是,让开发人员直观的了解返回值类型,让IDE能够进行自动的类型推导。对解释器的行为不产生任何影响。
对于容器类型,需要从 typing包引入一些类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from typing import List, Dict, Tuple, Sequence # 可以指定容器元素类型 def list() -> List[float]: pass # 可以定义类型别名 Vector = List[float] def list() -> Vector: pass # 复杂的类型注解 ConnectionOptions = Dict[str, str] Address = Tuple[str, int] Server = Tuple[Address, ConnectionOptions] # 这样也可以 Sequence[Tuple[Tuple[str, int], Dict[str, str]]] |
属性 | 说明 |
__doc__ | 文档字符串 |
__name__ | 函数名称 |
__dict__ | 包含函数属性的字典 |
__code__ | 字节编译的代码 |
__defaults__ | 默认参数的元组 |
__globals__ | 定义函数的全局命名空间,即定义了函数的模块中所有全局变量/函数构成的字典 |
__closure__ | 嵌套作用域相关数据的元组 |
方法的属性 | |
__class__ | 定义方法的类 |
__func__ | 实现方法的函数对象 |
__self__ | 与方法相关的实例,如果是非绑定方法,则为None |
函数 | 说明 |
abs (x) | 返回绝对值 |
all(s) | 如果可迭代的s中的所有值都为True,则返回True |
any(s) | 如果可迭代的s中的任意值为True,则返回True |
ascii(x) | 类似repr(),创建对象的可打印格式,但是只使用ASCII字符,非ASCII字符使用转义序列 |
bin(x) | 返回一个字符串,其中包含整数二进制形式 |
bool([x]) | 转换为布尔型 |
bytearray ([x]) | 可变字节数组,x可能是范围从0到255的可迭代整数序列、8位字符串或字节字面量 |
bytearray(s,encoding) | 从字符串创建字节数组,使用指定的编码 |
bytes ([x]) | 表示不变字节数组 |
chr(x) | 将整数值转换为单字符的字符串。在Python 2中,x必须在0 <= x <= 255范围内 |
classmethod(func) | 用于创建类方法,@classmethod装饰器隐式调用它 |
cmp(x, y) | 比较两个对象,如果x>y返回正数,相等返回0 |
compile(string) | 编译字符串为代码 |
complex(real,img) | 创建复数 |
delattr(obj, attr) | 等同于del obj.attr |
dict([m]) | 创建字典 |
dir([object]) | 返回属性名的有序列表。用户可以通过定义__dir__()方法改变此方法的行为 |
divmod(a, b) | 返回商和余数 |
enumerate(iter) | 给定可迭代对象iter,返回新迭代器,迭代元素形式为(index,el)的元组 |
eval(expr) | 计算表达式的值 |
exec(code) | 执行指定的代码 |
filter(function, iterable) | 在Python2中,创建来自iterable的元素的列表,对这些元素调用function结果为True则包含在结果列表中 |
float([x]) | 创建浮点数 |
format (val, [,fmt_spec]) | 格式化字符串 |
frozensett[items]) | 不变集合对象 |
getattr(obj, name,default) | 返回对象的一个命名属性的信 |
globals() | 返回代表当前模块全局命名空间的字典 |
hasattr(object, name) | 如果object具有属性name,则返回True |
hash(object) | 返回对象的整数散列值 |
hex(x) | 根据整数x创建一个十六进制字符串 |
id(object) | 返回object对象的唯一标识,通常为内存地址 |
input([prompt]) | 在Python 2中,该函数打印一个提示符,读取输入行并通过eval对其进行处理 |
int(x [,base]) | 创建整数 |
isinstance(obj, cls) | 如果obj是cls或者其子类的实例 |
issubclass(class1, class2) | 如果class1是class2的子类,或者class1是基于抽象基类class2册的,则返回True |
iter(object [,sentinel]) | 返回object的迭代器,如果不指定sentinel,则object必须具有__iter__或者__getitem__方法 |
len(s) | 返回s中包含的项数,s是列表、元组、字符串、集合或字典 |
list([items]) | 根据可迭代对象items创建列表 |
locals() | 返回当前函数的本地命名空间构成的字典 |
long([x [,base]]) | 在Python 2中表示长整数的类型。为了可移植性考虑,应当避免直接使用long |
map(function, items, ...) | 在Python 2中,该函数将function应用到items的每一项并返回结果列表,Python3则返回迭代器 |
max(s [, args, ...]) |
如果只有一个参数s,该函数返回s中各项的最大值,s可以是任意可迭代的对象。如果有多个参数,它返回参数中的最大值。min(s [, args, ...])类似 |
next(s [, default]) | 返回迭代器s中的下一项。如果该迭代器没有下一项,则引发Stopiteration异常(除非指定default) |
object() | Python中所有对象的基类。可以调用它创建一个实例 |
oct (x) | 将整数转换为一个八进制字符串 |
open(file [,mode[,bufsize]]) | 在Python2中,打开文件返回一个新文件对象 |
ord(c) | 返回字符c的整数序值。如果是普通字符,返回范围在[0,255]内的值。如果是单个Unicode字符, 通常返回范围在[0,65535]的值 |
pow(x, y [, z]) | 返回x ** y。如果提供了z,则该函数返回(x ** y) % z |
property ( [fget [, fset [,fdel [,doc]]]]) | 创建类的property属性。get是返回属性值的函数,fset设置属性值,而fdel删除一个属性。doc表示文档字符 |
range([start, ] stop [, step]) | 在Python 2中,该函数创建一个完全填充的、从start到stop的整数列表 |
raw_input ([prompt]) | Python 2函数,从标准输入读取一行输入并将其作为字符串返回 |
repr(object) | 返回的字符串表示形式。在大多数情况下,返回的字符串是可以传递到eval()的表达式 |
reversed (s) | 创建序列s的逆序迭代器。只有当s实现了序列方法__len__()、__getitem()__才可用 |
round(x [, n]) | 将浮点数舍五入到最近的10的负n次幂倍数后再四舍五入 |
set([items]) | 创建一个使用从可迭代对象items得到的各项来填充的桌合 |
setattr(object, name, value) | 设置对象的属性。name是字符串。与object.name = value相同 |
slice([start,] stop [, step]) | 返回表示指定范围内整数的切片对象。等同于扩展切片语法 [ i: j: k] |
staticmethod (func) | 创建在类中使用的静态方法。通过@staticmethod装饰器隐式调用该函数 |
str([object]) | 表示字符串的类型。在Python 2中,一个字符串包含8位字符 |
sum(items,[, initial]) | 计算从可迭代对象items中得到的所有项的总数。initial是初始值,默认是0 |
super(type [,object]) | 返回表示type基类的对象。该对象的主要用途是调用基类中的方法 |
tuple([items]) | 表示元组的类型。如果提供了items,则它是用于填充该元组的可迭代对象 |
type(object) | 返回对象的类型 |
type (name, bases, dict) | 创建一个新type对象(相当于定义一个新类) |
unichr (x) | 将整数或长整数换为一个Unicode字符 |
vars([object]) | 返回object的符号表(通常在它的__dict__属性中) |
xrange([start,] stop [, step]) | 表示从start到stop的整数值范围的类型,该范围不包括start和stop。step是可选的步进值 |
zip([s1 [, s2[,..]]]) | 在Python 2中,返回一些元组的列表,其中第n个元组是(sl[n], s2[n],…)。生成的列表被截取为最短参数序列的长度。如果没有给定参数,则返回一个空列表 |
所谓方法是指在类定义中定义的函数。包含实例方法、类方法、静态方法三种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Obj(object): def instance_method(self,arg): #实例会作为第一个参数传入 pass @classmethod def class_method(cls,arg): #类对象本身会作为第一个参数传入 pass @staticmethod def static_method(arg): pass #方法的查找 o = Obj() method = o.instance_method #绑定方法,绑定了o对象 method(100) #o作为隐含的第一个参数传入 method = Obj.instance_method #非绑定方法,没有绑定对象 method(o,100) #需要手工传入作为self的对象 |
如果一个函数里面具有yield关键字,则称为生成器。调用生成器得到返回值是一个迭代器对象,这一调用本身不会执行生成器的任何代码。
如果生成器中存在return语句,则执行到return时抛出StopIteration并终止迭代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#斐波那契數列的生成器 def fab(max): n, a, b = 0, 0, 1 while n < max: #在执行时,每次遇到yield就会返回当前迭代值,并且中断执行 yield b #下一次调用next(),在上一次中断的下一行执行,上下文与之前一致 a, b = b, a + b n = n + 1 #调用,获取生成器实例 #虽然看起来与函数调用语法一致,但是不会执行任何函数代码 #直到执行其next()方法时,才会真正调用函数代码 for n in fab(5): #隐含调用next() print n #手工调用next()方法 iter = fab(5) iter.next() #判断一个函数是否为生成器 from inspect import isgeneratorfunction isgeneratorfunction(fab) |
上面的生成器,实际上是一种协程,协程与普通函数(Subroutine/function)的执行方式有很大的不同:
- 子例程的起始处是惟一的入口点,一旦退出即完成了子例程的执行,子例程的一个实例只会返回一次
- 协程可以通过yield来调用其它协程。通过yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是一种对等关系
- 协程的起始处是第一个入口点,协程返回点之后(yield之后一句)是接下来的入口点
- 子例程的生命期遵循后进先出(最后一个被调用的子例程最先返回),而协程的生命周期完全由他们的使用的需要决定
下面是一个使用协程的例子:两个吃货到餐馆用餐,每次上完菜后就马上被他们吃掉,并继续索要新的食物,一个厨师、一个侍者为他俩服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
from random import randint from time import sleep # 贪吃鬼: def Gourmand( name ): dish = [] while True: if len( dish ) == 0: logging.debug( '%s is so hungry, food, quick!' , name ) # 递上盘子,等待服务员给予食物 dish.extend( ( yield ) ) else: logging.debug( '%s ate: %s', name, dish.pop() ) # 大厨 all_food = ['Wine', 'Meat', 'Beer', 'Cheese'] def Cook(): while True: new_food = [] logging.debug( 'Preparing food, please wait...' ) time = randint( 5, 30 ) sleep( time ) for i in range( randint( 1, len( all_food ) - 1 ) ): new_food.append( all_food[randint( 0, len( all_food ) - 1 )] ) # 返回本次制作的食物 logging.debug( '%d new food finished in %d secs', len( new_food ), time ) yield new_food # 服务员 if __name__ == '__main__': c = Cook() alex = Gourmand( 'Alex' ) meng = Gourmand( 'Meng' ) alex.next() meng.next() while True: # 新菜刚上给这两吃货,他们就会马上将其吃光,并继续索要 alex.send( c.next() ) meng.send( c.next() ) |
所谓新式类是指从object或者其它新式类衍生出的类,而旧式类是Python2中没有明确指定基类的类。Python3中只有新式类。
Python中所有变量皆为对象。使用 __func__这样形式的函数名来实现特殊方法(例如list的__add__实现了+运算符)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class Stack(object): #继承语法:继承object,所有Python类的根类 fast_mode = True #类变量 def __init__(self): #特殊方法:用于在创建对象后进行初始化 self.stack =[] #实例变量 def push(self,object): #每个实例方法的第一个参数均执行对象本身,即self self.stack.append(object) #涉及对象属性的操作,均必须显式使用self变量 def pop(self): return self.stack.pop() def length(self): return len(self.stack) @staticmethod #装饰器:定义静态方法,静态方法仅仅是定义在类的命名空间 def create(): pass @classmethod #装饰器:定义类方法,类方法对类对象本身进行操作,第一个参数cls为当前类 def create(cls): return cls() #可以调用cls来创建合适类型的对象,静态方法做不到这一点 class Inner: #Python支持内部类 pass #使用类创建对象 s = Stack() del s #删除对象 #类似于C++的函数对象:定义__call__方法 class FuncObj: def __call__(arg0): pass fo = FuncObj() fo(1) |
属性 | 说明 |
__doc__ | 文档字符串 |
__name__ | 类的名称 |
__bases__ | 基类的元组 |
__dict__ | 保存类方法、变量的字典 |
__module__ | 定义类的模块名称 |
__abstractmethods__ | 抽象方法的集合 |
属性 | 说明 | ||
__new__(cls [,*args [,**kwargs]]) | 类方法,用于创建实例 | ||
__init__(self [,*args [,**kwargs]]) | 初始化对象属性,在创建对象后立即被调用 | ||
__del__(self) | 销毁实例时调用 | ||
__format__(self, format_spec) | 格式化后的表示 | ||
__repr__(self) | 字符串表示,某些类允许使用 eval(repr(o)) 创建对象 | ||
__str__(self) |
返回对象的字符串表示,该方法在Python2中返回的是“字节”,在Python3中返回的是字符 在Python2中,print语句和str()函数会调用此方法 |
||
__unicode__(self) |
返回对象的字符串表示,该方法返回的是“字符”。在Python2中,为了编码相关的兼容性,你应当把对象格式化代码放在该方法里,而把__str__创建为一个存根方法:
在Python2中,unicode()函数会调用此方法 在Python3中,此方法没有价值 |
||
__bool__(self) | 真值测试 | ||
__hash__(self) | 计算整数散列值 | ||
__lt__ |
小于,类似还有__le__、__gt__、__ge__、__eq__、__ne__ |
||
__instancecheck__(cls,object) | 修改isinstance(object,cls)的行为 | ||
__subclasscheck__(cls,sub) | 修改issubclass(sub,cls)的行为 | ||
__getattribute__(self,name) | 返回属性self.name时调用,调用此方法时,Python尚未查找对象的真实属性 | ||
__getattr__(self,name) | 仅仅在常规方式找不到属性时调用 | ||
__setattr__(self, name, value) | 设置self.name=value时调用 | ||
__delattr__(self, name) | 删除self.name时调用 | ||
__len__(self) | 返回长度 | ||
__getitem__(self,key) | 获得self[key] | ||
__setitem__(self,key,value) | 设置self[key] = value | ||
__delitem__(self,key) | 删除self[key] | ||
__contains__(self,obj) | 如果包含,则返回真 | ||
__iter__() |
如果对象支持迭代,从该方法返回迭代器对象,迭代器必须实现next()方法
|
||
__next__() | Python3的迭代器方法,在Python2中为next() | ||
__add__(self, other) | self + other | ||
__sub__(self, other) | self - other | ||
__mul__ (self, other) | self * other | ||
__div__ (self, other) | self / other | ||
__floordiv__(self, other) | self // other | ||
__mod__ (self, other) | self % other | ||
__pow__(self, other, [,modulo]) | self ** other, pow(self,other,modulo) | ||
__lshift__(self, other) | self << other | ||
__rshift__(self, other) | self >> other | ||
__and__(self, other) | self & other | ||
__or__(self, other) | self | other | ||
__xor__(self, other) | self ^ other | ||
__r**(self, other) | other ** self | ||
__i**(self, other) | self **= other | ||
__neg__(self) | -self | ||
__pos__(self) | +self | ||
__abs__(self) | abs(self) | ||
__invert__(self) | ~self | ||
__int__(self) | int(self) | ||
__long__(self) | long(self) | ||
__float__(self) | float(self) | ||
__complex(self) | complex(self) | ||
__call__(self[, *args[,*kwargs]]) | 函数对象 |
属性 | 说明 |
__class__ | 实例所属的类,可以使用tpye(o)得到 |
__dict__ | 所有实例变量构成的字典 |
如果一个Python程序出现错误,则会引发异常,打印类似下面的信息:
1 2 3 |
Traceback (most recent call last): File "foo.py", line 12, in IOError: [Errno 2] No such file or directory: 'file.txt' |
错误信息中包含了错误的类型、出错的代码位置。如果不做任何处理,异常会导致程序终止,除非使用try-except语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
try: f = open('Readme.txt') except IOError as e: #如果发生IOError,则被捕获并且存放在e变量中 print e #raise用于手工触发异常 raise RuntimeError("Error occurred") #如果raise没有指定任何参数,则再次引发最近一次生成的异常(仅正在处理前一个异常时) try: pass except RuntimeError as e: raise |
未捕获的异常将向上传递,如果到程序最顶级仍然没有处理,则导致解析器终止并打印消息。可以把未捕获的异常传递给用户自定义的 sys.excepthook() 函数进行处理。
可以使用try-except-else语句块,else在没有引发异常时执行;可以使用try-except-finally语句块,finally总是执行:
1 2 3 4 5 6 7 8 9 |
try: f = open('Readme.txt','r') except IOError as e: error_log.write('Failed to open file:%s\n' % e) else: data = f.read() f.close() finally: #如果发生异常,控制权首先交给finally代码块,执行完毕后再进行异常处理 f.close() |
Python预定义了以下异常:
异常 | 描述 |
BaseException GeneratorExit Keyboardlnterrupt SystemExit Exception StopIteration StandardError ArithmeticError FloatingPointError ZeroDivisionError AssertionError AttributeError EnvironmentError IOError OSError EOFError ImportError LookupError IndexError KeyError MemoryError NameError UnboundLocalError ReferenceError RuntimeError NotImplementedError SyntaxError IndentationError TabError SystemError TypeError ValueError UnicodeError UnicodeDecodeError UnicodeEncodeError UnicodeTranslateError |
所有异常的根类 由生成器的 close() 方法引发 由键盘中断(通常为Ctrl+C)生成 程序退出/终止 所有非退出异常的基类 引发后可停止迭代 所有内置异常的基类 算术异常的基类 浮点操作失败 对0进行除或取模操作 由assert语句引发 当属性名称无效时引发 发生在Python外部的错误 I/O或文件相关的错误 操作系统错误 到达文件结尾时引发 import语句失败 索引和键错误 超出序列索引的范围 字典键不存在 内存不足 无法找到局部或全局名称 未綁定的局部变量 销毁被引用对象后使用的弱引用 一般运行时错误 没有实现的特性解析错误 语法错误 缩进错误 使用不一致的制表符(由-tt选项生成) 解释器中的非致命系统错误 给操作传递了错误的类型 无效类型 Unicode错误 Unicoed解码错误 Unicode编码错误 Unicode转换错误 |
可以创建以Exception为父类的新类,作为自定义异常类:
1 2 3 4 5 6 7 8 9 10 11 |
#简单的例子 class NetworkError(Exception): pass #使用该异常 raise NetworkError('Cannot find host') #自定义异常可以包含多个构造参数 class DeviceError(Exception): def __init__(self, errno, msg): self.args = (errno, msg) #使用该异常 raise DeviceError(1,'Not Responding') |
assert语句用于在程序中设置断言,格式为:
1 2 |
assert test [,msg] #test为一表达式,如果为False,则触发AssertionError,并使用msg指定的消息内容 |
使用-O选项使解释器运行于最优模式时,不会执行断言代码。
除非使用-O选项,内置只读变量 __debug__ 的值均为true,可以用于程序调试。
该机制主要用于在Python中安全的进行资源(数据库连接、事务、文件句柄等)管理。
使用with语句,可以在一个“上下文管理器”对象的控制下执行一系列的语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
with context [as var]: #执行context.__enter__(self)方法,返回值存入var pass #执行context.__exit__(self, type, value, traceback)方法 #举例:锁 import threading lock = threading.Lock() with lock: pass #执行完毕后自动清除锁定 #自动关闭打开的文件 with open( sys.argv[1] ) as infile : for line in infile: print line |
上下文管理器对象必须实现:
方法 | 说明 |
__enter__(self) | 进行一个新上下文时调用此方法,返回值存入 as 后面指定的变量 |
__exit__(self, type, value, tb) | 离开一个上下文是调用此方法,如果发生异常,则type、value、tb分别为异常类型、值、跟踪信息 |
随着程序规模的扩大,有必要根据功能不同把代码分散在不同的文件中,作为单独的模块,并在需要使用时进行导入。
在Python中,模块名就是相应脚本文件的basename。当导入一个模块时,自动创建一个名字空间来存放模块定义的对象,默认此名字空间与模块名相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#file: div.py def divide(a, b): q = a/b r = a - q*b return (q, r) #file: main.py import div #导入div模块,创建了div名字空间 dir(div) #列出名字空间下的内容 q, r= div.divide(100,17) #使用模块中的函数,需要加前缀 import div as d #为导入的模块启用别名 from div import divide #把某个具体的定义导入到当前名字空间 q, r = divide(100,18) #不再需要使用名字空间作为前缀 |
属性 | 说明 |
__doc__ | 模块的文档字符串 |
__dict__ | 与模块相关的字典 |
__name__ | 模块的名称 |
__file__ | 用户加载模块的文件 |
__path__ | 完全限定的包名 |
运算符 | 说明 |
(...) 、 [...] 、 {...} | 创建元组、列表或字典 |
s[i]、s[i:j] | 索引、切片 |
s.attr | 属性导航符 |
f(...) | 函数调用 |
+x、-x、~x | 一元运算符 |
x**y | 乘方(右结合性) |
x*y、x/y、x//y、x%y | 乘、除、截断除、取余 |
x+y、x-y | 加、减 |
x<<y、x>>y | 移位 |
x&y | 按位与 |
x^y | 按位异或 |
x|y | 按位或 |
x < y、x <= y、 x > y、 x >= y、 x == y、 x != y x is y、 x is not y 、 x in s、x not in s |
比较、序列成员检查 is 用于对象同一性检查 |
not x | 逻辑非 |
x and y | 逻辑与 |
x or y | 逻辑或 |
lambda args:expr | lambda表达式 |
在其它语言里面比较少见的数学运算符有:截断除法(//)、乘方(**)。
内置函数包括:绝对值(abs)、商与余数(divmod)、四舍五入(round)等。
序列包括:字符串、列表和元组。支持以下运算符或者函数:
运算符或函数 | 说明 |
s + r | 连接两个相同类型的序列 |
s * n | 生成s的n个副本,浅复制 |
v1,v2... = s | 把序列解包为若干对象,变量个数必须和元素个数一致 |
s[i] | 索引,返回第i+1个元素 |
s[i:j] | 切片 |
s[i:j:stride] | 扩展切片,stride为步进值,可以跳过若干对象,结果索引为i, i+stride, i + 2*stride直到j |
x in s 以及 x not in s | 从属关系判断 |
for x in s | 迭代 |
all(s) | 是否所有元素均为True,不适用于字符串 |
any(s) | 是否存在元素为True,不适用于字符串 |
len(s) | 长度 |
min(s) max(s) | 最值 |
sum(s[,initial]) | 求和 |
以下为可变序列(即列表)支持的操作 | |
s[i] = x | 按索引赋值,如果i为负数,则从结尾算起 |
s[i:j] = x | 按切片赋值,x的个数必须与切片中元素个数一致 |
s[i:j:stride] = x | 扩展切片赋值,x的个数必须与切片中元素个数一致 |
del s[i] | 删除一个元素 |
del s[i:j] | 删除一个切片 |
del [i:j:stride] | 删除一个扩展切片 |
操作 | 描述 |
x = d[k] | 通过键进行查找 |
d[k] = x | 通过键进行赋值 |
del d[k] | 通过键进行删除 |
k in d | 测试某个键是否存在于字典中 |
len(d) | 字典的条目个数 |
set和frozenset用于支持常见的集合操作:
操作 | 描述 |
s | t | 并集 |
s & t | 交集 |
s - t | 差集 |
s ^ t | 对称差集 |
len(s) | 集合中条目个数 |
max(s) | 最大值 |
min(s) | 最小值 |
转换函数 | 说明 |
int(x [, base]) | 将x换为一个整数。如果x是一个字符串,base用于指定基数 |
float(x) | 将x换为一个浮点数 |
Complex (real [, imag]) | 创建一个复数 |
str(x) | 将对象x转换为字符串表示 |
repr(x) | 将对象x转换为一个表达式字符串,可以通过eval还原 |
format (x [, fmt_spec]) | 将对象x转换为格式化字符串,该函数调用x的__format__()方法 |
eval(str) | 对字符串求值并返回对象 |
tuple(s) | 将s转换为元组 |
list(s) | 将s转换为列表 |
set(s) | 将s转换为集合 |
dict(d) | 将d转换为字典,d是(key,value)形式的序列对象 |
frozenset(s) | 将s转换为不可变集合 |
chr(x) | 将整数转换为字符 |
unichr(x) | 将整数转换为Unicode字符 |
ord (x) | 将字符转换为其整数值 |
hex(x) | 将整数转换为十六进制字符串 |
bin (x) | 将整数转换为二进制字符串 |
oct (x) | 将整数转换为八进制字符串 |
- 每次执行函数时,自动创建局部命名空间,其内包括函数参数、函数体内定义的变量
- 解释器解析变量名称时,首先从局部命名空间开始;如果找不到,则搜索函数的全局命名空间(定义该函数的模块);仍然找不到,则搜索内置命名空间;再找不到则NameError
- 除非在函数里使用global语句,否则不会改变全局命名空间变量的值,但是可以访问全局变量的值
1234567891011i = 1def func():print i #打印1i = 2print i #打印2func()print i #仍然打印1def func1():global i #现在i位于全局名字空间i - 2 #修改成功 - Python支持嵌套函数定义,使用词法作用域来绑定嵌套函数中的变量——首先检查其局部作用域,然后检查外部嵌套函数的作用域,以此类推。在Python2中,只能对局部作用域、全局作用域进行变量赋值,对外部嵌套函数中定义的变量进行赋值是不支持的,Python3可以使用nonlocal语句解决此问题
- 使用尚未赋值的局部变量,导致UnboundLocalError
作为First-class对象的函数,可以当作数据传递给其他函数。把函数作为数据来处理时,它自动携带定义函数的上下文信息(变量)。
将函数主体语句、语句的执行环境上下文信息一起打包时,得到的对象称为闭包。由于任意函数都携带定义其它的模块的全局命名空间信息(__globals__),因此本质上任何Python函数都是闭包。
装饰器是一个函数,用于包装一个函数或者类,目的是修改、增强被包装对象的功能。特殊符号@用于表示装饰器语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@trace def square (x) : return x*x #上述代码等价于 def square (x) : return x*x square = trace(square) #trace函数的定义——类似于切面,它必然返回一个函数 def trace(func): def callf(*args,**kwargs): debug_log.write( "Calling %s: %s, %s\n" % (func.__name__, args, kwargs) ) #记录日志 r = func(*args, **kwargs) #调用原本的函数 debug_log.write( "%s returned %s\n" % (func.__name__, r) ) return callf |
可以声明多个装饰器,每个装饰器必须独占一行,最后定义的装饰器,最先包装到原始函数上:
1 2 3 4 5 6 |
@foo @bar def func(x): pass #等价于 func = foo(bar(func)) |
装饰器可以带有参数:
1 2 3 4 5 6 |
@eventhandler('BUTTON') def handle_button(msg): pass #带有参数的装饰器,装饰目标函数的步骤如下: temp = eventhandler('BUTTON') #使用参数调用装饰器,其应当仍然返回一个装饰器 handle_button = temp(handle_button) #使用生成的装饰器来装饰目标函数 |
装饰器也可以应用于类,这样的装饰器应当返回类对象作为结果。 下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def talking(cls: type): setattr(cls, 'talk', lambda self: print('Hello, I am %s' % str(self))) return cls @talking class person(object): def __init__(self, name: str): self.name = name def __str__(self): return self.name.upper() if __name__ == '__main__': p = person('Alex Wong') p.talk() #Hello, I am ALEX WONG |
使用yield关键字可以定义一个生成器对象,该对象本质上是一个函数,其生成一个值序列,在迭代中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def countdown(n): while n > 0: print('Counting down from %d' % n) yield n n -= 1 return #生成器不能返回除了None以外的任何值 #调用该函数,不会打印任何信息: c = countdown(10) #相反,返回值c是一个生成器函数,当c.next()被调用时,countdown函数被执行 #调用next()函数时,代码会正常执行,直到遇到yield语句为止,并返回yield指定的值 #通常不需要手工调用next()函数,而是在for、sum等使用序列的操作中自动调用: for n in countdown(10): pass a = sum(countdown(10)) |
当生成器函数遇到return语句或者StopIteration异常,则其退出。
可选的,调用 close() 方法可以关闭生成器,当yield语句遭遇GeneratorExit异常时会自动调用。
yield语句可以作为右值,通过该方式使用yield的函数称为协程:
1 2 3 4 5 6 7 8 9 10 11 12 |
def receiver(): print('Prepare to receive') while True: n = (yield) #协程的目的是对发送给它的值做出处理 print('Got %s' % n) #使用协程 r = receiver() r.next() #执行到第一条yield语句,该调用必不可少 r.send(1) #发送值给协程,导致其运行直到下一条yield语句 r.send('Greetings') |
由于协程使用时next()调用必不可少,因此可以使用类似下面的装饰器自动完成:
1 2 3 4 5 6 |
def coroutine(func): def start(*args,**kwargs): g = func(*args,**kwargs) g.next() return g return start |
协程一般是无限期运行的,可以调用close()方法显式关闭。关闭后,再调用send()会导致StopIteration异常。注意close方法会在协程内部引发GeneratorExit异常。
使用throw()方法可以在协程内部触发异常,该异常在yield语句处出现。使用throw()给协程发送异步信号是不安全的。
如果协程的yield提供值,那么将自动返回给send()方法的返回值:
1 2 3 4 5 6 7 8 9 10 |
def line_splitter(d = None): result = None while True: #读出line,返回result line = (yield result) result = line.split(d) ls = line_splitter() ls.next() #执行到第一次遇到yield ls.send('X,Y,Z') #触发继续执行,直到下一次遇到yield。因此返回['X','Y','Z'] |
当通过使用yield返回值、throw()时需要注意,send()给协程的值将作为throw()的返回值返回。
- 数据流处理程序,类似于Unix Shell管道
- 实现某种形式的并发,例如使用某个任务管理器,把数据分发给数百个执行各种具体任务的协程
Python使用“列表推导”(List comprehension)运算符,来把函数应用到列表的每个项,并根据结果创建新的列表,例如下面的操作:
1 2 3 4 |
nums = [1, 2, 3, 4, 5] squares = [] for n in nums: squares.append(n * n); |
可以使用列表推导改写为:
1 2 |
nums = [1, 2, 3, 4, 5] squares = [n * n for n in nums] #列表推导 |
列表推导的语法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
[expression for iteml in iterablel if conditionl for item2 in iterable2 if condition2 …… for itemN in iterableN if conditionN ] #上面的语法等价于 s = [] for item1 in iterable1: if condition1: for item2 in iterable1: if condition2: ... for itemN in iterableN: if conditionN: s.append(expression) #最终是为了计算结果列表 #举例 a = [-3,5,2,-10,7,8] b = 'abc' c = [2*s for s in a] #[-6,10,4,-20,14,16] d = [s for s in a if s >= 0] #[5,2,7,8] e = [(x,y) for x in a #如果推导结果元素为元组,必须放入括号内 for y in b #[(5,'a'),(5,'b'),(5,'c')......] if x > 0 ] f = [(1,2),(3,4),(5,6)] g = [x+y for x,y in f] #[3,7,11] |
注意:Python2的列表推导迭代变量在当前作用域中求值,在列表推导完毕后,其值仍然保留。在Python3中,迭代变量是私有的,推导结束即无效。
生成器表达式与列表包含非常类似,但是它只是生成获取结果的规则,而不是生成结果本身:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#语法差异仅仅是把方括号换成花括号 (expression for iteml in iterablel if conditionl for item2 in iterable2 if condition2 …… for itemN in iterableN if conditionN ) #举例 a = (1, 2, 3, 4) b = (10*i for i in a) #生成一个生成器 b.next() #10 #list函数可以把生成器表达式转换为序列 list(b) |
使用lambda语句可以创建匿名函数:
1 2 3 4 5 6 7 |
lambda args: expression #args为逗号分隔的参数列表 #expression为使用参数的表达式 a = lambda x,y : x + y r = a(1,2) #r=3 #lambda主要用于指定短小的回调函数 names.sort(key = lambda n: n.lower()) |
lambda语句中不能出现多条语句或者非表达式语句(for、while等)。作用域规则与函数相同
sys.getrecursionlimit ()返回当前解释器对递归深度的限制,默认1000。
递归不能用在生成器函数、协程中。
可以为函数指定任意属性,这些属性会包含在__dict__属性中。
1 2 3 4 |
#eval执行一个表达式字符串并返回结果 eval (str [, globals [, locals]]) #exec执行任意包含Python代码的字符串 exec (str [, globals [, locals]]) |
eval、exec均在调用者的变量作用域中执行,可选的globals、locals用于映射全局、局部名字空间。
对于反复执行的代码,最好使用compile将其编译为字节码,提高性能:
1 2 3 |
s = "for i in range(0,10): print(i)" c = compile(s, '', 'exec') #编译为代码对象 exec(c) |
- Python没有类作用域一说,如果需要访问对象的其它属性,必须以self.开头
- 派生类不会自动调用基类的__init__方法
-
super(cls, instance).*** 用于在基类上执行属性查找,Python3直接简化为
super().***
1234class MoreEvilAccount(EvilAccount):def deposit(self, amount):self.withdraw(5)super(MoreEvilAccount,self).deposit(amount) #调用父类的方法实现 - Python支持多重继承,示例:
12#使用逗号分隔的基类列表class MostEvilAccount(EvilAccount, DepositCharge, WithdrawCharge):pass - 通常应当避免使用多重继承。有时,可以使用多重继承来定义混入类(mixin)。混入类定义了需要混合到其它类中的一组方法,以添加功能,它通常假定其它方法/属性存在,并以这些方法/属性为基础构建新的逻辑
- 动态绑定(多态性):不考虑实例的类型的情况下使用实例,只要以obj.attr的形式访问属性,解释器就会按实例本身内部 - 实例的类定义 - 基类的类定义的顺序来搜索attr,返回第一个匹配。此绑定过程的关键在于它与obj的类型独立,只要它就有attr属性,就可成功绑定(鸭子类型识别)。动态绑定可以用于组件解耦,例如,可以针对具有某个方法集的对象编写代码,而不需要考虑其属于何种类型。
- 静态方法是普通的函数,只是定义在类的名字空间中;类方法的第一个参数是当前类本身;实例方法的第一个参数是当前对象本身
property是一种特殊的属性,在访问时,会计算它的值:
1 2 3 4 5 6 7 8 9 10 11 |
class Circle(object): def __init__(self,radius): self.radius = radius #属性定义 @property #该装饰器支持简单属性风格访问后续定义的方法 def area(self): return math.pi * self.radius**2 #使用 c = Circle(5) c.area #访问属性,导致area()方法被调用 |
向property添加setter、deleter方法,可以实现设置、删除属性操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Foo(object): def __init__(self,name): self.__name = name @property #属性读取 def name(self): return self.__name #实际存储的变量名任意,但是必须和property名不同 @name.setter #属性设置 def name(self,value): #setter、deleter的方法名称必须与属性原始方法名一致 if not isinstance(value,str): raise TypeError("string value required.") self.__name = value @name.deleter #属性删除 def name(self): raise TypeError("Unsupported operation") #使用 f = Foo() name = f.name #调用f.name() f.name = 50 #调用f.name(f,50),抛出TypeError del f.name #调用f.name(f),抛出TypeError |
另外,getter、setter、deleter风格的方法也是支持的,但是建议使用装饰器,更加简洁,不会显示大量的getter、setter等方法:
1 2 3 4 5 6 7 |
class Foo(object): def getname(self): return self.__name def setname(self,name): self.__name = name def delname(self): raise TypeError("Unsupported operation") |
Python约定以__开头的方法为私有方法,这些方法会自动变形:
__func 变形为
__classname__func 的形式。
这不是一种严格的信息隐藏机制。
Python使用引用计数作为基础垃圾回收算法。每个对象都具有一个引用计数,将对象赋给一个新变量、将其放入容器时,均会导致该计数增加,使用 del 语句或者超过变量作用范围、重新变量赋值时,引用计数则减小
引用计数算法具有无法处理循环引用的天生缺陷,为此Python解释器会定期执行一个“循环检测器”,搜索不可访问的对象循环引用,并删除之。
Python类实例的创建包括两个步骤:
- 使用特殊的 __new__() 方法创建实例
- 使用 __init__() 方法初始化实例
其中类方法__new__方法很少需要用户定义,除了以下两个场景:
- 在继承一个不变类型的基类时,用于修改对象的值
- 定义元类时使用
实例创建完毕后,Python将管理其引用计数,当引用计数为0时,实例立即被销毁,其 __del__() 方法被调用。通常没必要定义__del__,因为无法保证解释器在退出时调用了该方法,如果需要资源清理,自定义close()方法并手工调用是最好的。
定义了__del__() 的对象无法被Python的循环垃圾收集器回收。可以使用 weakref.ref() 引用解决此问题——在不增加引用计数的情况下创建对象的引用。
通过定义特殊变量 __slots__ ,类可以限制设置合法属性名称:
1 2 3 |
class Account(object): #定义该变量后,只能设置列出的属性,否则抛出AttributeError __slots__ = ('name','balance') |
设置__slots__的类实例不再使用字典存储实例数据,而是改为数组,因此可以减少内存占用、执行时间。
没有必要在__slots__里面添加方法、property的名字,因为他们是定义在类上而不是实例级别的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
aid = id(a) #内置函数id()用于获取对象的标识符,为一整数,通常为内存地址 if a is b: pass #is运算符用于判断两个变量是否指向同一个对象(标识符相等) if a == b: pass #如果a与b的值相等 if type(s) is list: pass #对象的类型也是一个对象,该对象是单例的 if type(a) is type(b): pass #如果a与b的类型相同 #如果对象属于类cname或派生自cname的任何类,isinstance返回True isinstance (obj, cname) #如果A是B的子类,则issubclass()返回True issubclass(A,B) #考虑到鸭子类型识别的问题,可以修改isinstance、issubclass方法的行为: class IClass(object): def __init__(self): self.implementors = set() def register(self,C): self.implementors.add(C) def __instancecheck__(self ,x): return self.__subclasscheck__(type (x)) def __subclasscheck__(self,sub): return any(c in self.implementors for c in sub.mro()) |
使用abc模块可以定义抽象基类,该模块由元类ABCMeta与一组装饰器组成,使用方法如下:
1 2 3 4 5 6 7 |
from abc import ABCMeta, abstractmethod, abstractproperty class Foo: __metaclass__ =ABCMeta # Python3使用 class Foo(metaclass=ABCMeta) @abstractmethod #声明抽象方法 def spam(self, a, b): pass @abstractproperty #声明抽象属性 def name(self): pass |
抽象类不能实例化,到导致TypeError,抽象的派生类只要没有全部实现抽象方法、属性,则同样不能实例化。
抽象基类支持对已经存在的类进行注册——使其属于该类(isinstance、issubclass返回期望的结果),但是该注册不会检查目标子类是否实现了抽象基类的任何抽象方法、属性:
1 2 |
class Bar(object): pass #既有类 Foo.register(Bar) #注册基类 |
使用抽象基类注册机制,可以重新组织已有类型的层次结构,例如numbers模块把数字类型重新整理,而默认他们都是继承object类的
元类知道如何创建、管理类:
1 2 |
class Foo(object): pass type(Foo) #对类对象取类型,即获得其元类,默认<type 'type'> |
使用class语句定义新类时,解释器内部事件序列可以使用下面的代码示意:
1 2 3 4 5 6 7 8 9 10 11 12 |
class_name = "Foo" #类名 class_parents = (object,) #基类 #类主体代码 class_body = """ def __init__(self, x): pass """ class_dict = { } #在局部字典class_dict中执行类主体代码 exec(class_body,globals(),class_dict) #调用元类创建类对象。这一步可以自行定义 Foo = type(class_name, class_parents, class_dict) |
通过指定元类,可以改变类对象创建的行为:
- 设置类变量__metaclass__可以显式指定元类
- 如果没有显示指定元类,则使用基类元组的第一个条目的元类作为新类的元类
- 如果没有指定基类,则使用默认值: types.ClassType ,即Python2.2以后的type作为元类
元类通常在框架组件中使用,通常可以继承 type 并重新实现 __init__() 、 __new__ 等方法,来扩展新的元类,下面的元类要求所有类定义必须提供文档字符串:
1 2 3 4 5 6 7 8 9 10 11 |
class DocMeta(type): def __init__(self, name, bases, dict): for key, value in dict.items(): #遍历所有类的元素 # 跳过特殊方法和私有方法 if key.startswith("__"): continue # 跳过不可调用的任何方法 if not hasattr(value, "__call__"): continue # 检查doc字符串 if not getattr(value, "__doc__"): raise TypeError("%s must have a docstring" % key) type.__init__(self, name, bases, dict) #调用默认实现来初始化类 |
大规模的Python程序通常以模块、包的形式组织。Python的包常常和目录对应,模块则和文件对应。
任何Python源文件均可以作为模块来使用,对于一个名字为util.py的源文件,可以使用import util语句来将其作为模块加载。加载模块时,Python解释器将:
- 创建新的命名空间,用作在相应源文件中定义的所有对象的容器。源文件中定义的函数、方法在使用global语句时,将访问该命名空间
- 在新创建的命名空间中执行模块中包含的代码——import导致模块中所有语句被执行
- 在调用函数中创建名称来引用模块命名空间,该名称默认与模块名称一致:
12345678#util.pyclass stringutils(object):@classmethoddef indexOf(string, search): pass#main.pyimport util #导入源文件作为模块util.stringutils.indexOf("123","2") #使用模块中定义的类,注意前缀
用于引用模块的名称可以使用as限定符修改,新名称只限定使用了import语句的源文件或者上下文:
1 2 |
import util as Util Util.stringutils.indexOf |
模块也是Python的First class对象,因此可以分配给变量,存放在列表等数据结构中。
使用from语句可以将模块中指定的具体符号引入当前命名空间中,访问这些导入的符号时,不需要模块名字空间前缀:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from util import stringutils i = stringutils.indexOf("123","2") #可以同时导入多个符号 from util import stringutils, numberutils from util import ( stringutils, numberutils ) #可以在导入的同时重命名符号 from util import stringutils as su #星号通配符可以导入所有除下划线开头的符号 #只能在模块最顶层使用,在函数内使用非法 from util import * |
在模块中定义列表 __all__ ,可以精确限制import *能导入的符号。
你也可以使用from从某个包中导入模块、从包中导入(定义在包的__init__.py中的)函数:
1 2 3 4 5 |
# 从cc/gmem/py3子包中导入HelloModule模块 from .cc.gmem.py3 import HelloModule if __name__ == '__main__': HelloModule.hello() |
从以点号开头的路径进行导入,表示相对于当前包寻找模块或符号:
1 2 3 4 5 6 7 8 9 10 |
# 从当前包的_multiprocessing模块中导入win32 from ._multiprocessing import win32 # 导入一个兄弟模块 from . import peermodule # 导入__init__.py中定义的变量v1 from . import v1 # 导入父包中的一个模块 from .. import parentpackagemodule |
模块可以在import时在自己的名字空间中运行(以库模块方式),也可以以主程序的方式运行。
每个模块均可以访问变量 __name__ ,该变量用于确定当前模块在哪个模块内部运行,解释器的顶级模块的名称为 __main__ ,在命令行指定或者直接输入的程序将在__main__模块中运行:
1 2 3 4 5 6 7 |
#检查模块是否以程序的形式运行 if __name__ == '__main__': #是 pass else: #否,我必须以模块的形式导入 pass |
加载模块时,解释器在列表sys.path中搜索字典列表,sys.path的第一项内容是空字符串,表示当前正在使用的字典,可能包括的其他条目有:字典名称、zip归档文件、.egg文件。各条目在sys.path中出现的顺序决定了模块加载时搜索的顺序。
egg文件只是添加了版本号、依赖项的zip文件,使用zip文件的示例如下:
1 2 3 4 5 6 7 |
import sys sys.path.append("modules.zip") #假设modules里包含foo.py、bar.py两个文件 import foo,bar #zip文件目录层次可以与OS文件系统层次混用 sys.path.append("/tmp/modules.zip/lib/python) |
使用归档文件时需要注意:
- python不会创建.pyc、.pyo文件,应当提前创建并放在归档文件中,避免加载模块时性能下降
- C编写的共享库、扩展模块无法从归档文件中导入
可使用import加载的模块包括四类:
- Python源代码,即.py文件
- 编译为共享库或者DLL的C/C++扩展
- 一组包含模块的包
- 使用C编写并链接到Python解释器的内置模块
在加载模块foo时,在顺序sys.path下每个目录中搜索以下文件:
- 包定义:目录foo
- 已编译扩展:foo.pyd、foo.so、foomodule.so、foomodule.dll
- foo.pyo:使用-O、-OO选项时
- foo.pyc
- foo.py、foo.pyw
py文件在首次被import时,会被编译为字节码并写入为.pyc文件,后续导入时,缺省直接使用.pyc文件,除非py的修改日期更新(会自动重新生成pyc)。
如果使用-O,则会创建pyo,并且删除行号、断言、其它调试信息。如果使用-OO,则还会删除文档字符串。
使用包可以把若干模块划分为一组,可以解决不同应用程序中模块名称的命名空间冲突问题。
通过创建与包名字一致的目录,并且在该目录下编写 __init__.py ,即可创建包。包内可以放入其它源文件、编译后的扩展、子包。
包的导入与模块导入类似,都是使用import语句。第一次导入包的任何部分,均会执行对应的__init__.py,父包的__init__.py比子包的先执行。
本节以工程my-autosizer v0.1.0为例进行阐述
你可以使用distutils模块来分发Python程序给其他人使用,步骤如下:
- 相关文件有序的组织到工程目录中,这些文件包括:模块、包、脚本、支持文档等。使用Pycharm时,这个目录就是Project的根目录。工程布局示例:
123456789├── my-autosizer├── dist│ └── myautoresizer-0.1.0.tar.gz├── ma_autoresize.py├── MANIFEST├── ma_printrect.py├── myautoresizer│ └── __init__.py└── setup.py - 在工程目录下编写安装脚本setup.py
- 可选的,编写一个安装配置文件
- 创建源码分发包(source distribution)
- 可选的,创建更多的二进制分发包(binary distributions)
安装脚本是构建、分发、安装等一系列活动的中心所在,最简单的例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from distutils.core import setup setup( name="myautoresizer", # 软件包名称 version="0.1.0", # 字符串版本号 description='Auto resize and move GDK windows', author='Alex Wong', author_email='alex@gmem.cc', url='https://py.gmem.cc/myautoresizer', py_modules = [], # 所有单一文件的Python模块列表 packages=['myautoresizer'], # 所有包目录的列表 scripts=['ma_autoresize.py', 'ma_printrect.py'] # 脚本文件的列表 ) |
调用setup.py,传入不同的相应参数,可以创建分发包,这些分发包自动保存到工程目录的dist子目录下:
1 2 3 4 5 6 7 8 9 10 |
cd /home/alex/Python/projects/pycharm/my-autosizer # 创建源码分发包,生成 dist/myautoresizer-0.1.0.tar.gz python setup.py sdist # 创建二进制分发程序 python setup.py bdist # Window下分发为安装程序.exe python setup.py bdist_wininst # Redhat下分发为安装程序.prm python setup.py bdist_rpm |
在客户机上,可以解压并安装上面的分发包:
1 2 3 4 5 6 7 8 9 10 |
tar xzf myautoresizer-0.1.0.tar.gz cd myautoresizer-0.1.0 # 执行安装 python setup.py install # 在我的机器上,以下文件被安装 # /usr/local/bin/ma_autoresize.py # /usr/local/bin/ma_printrect.py # /usr/local/lib/python2.7/dist-packages/myautoresizer/__init__.py # 注意:工程根目录只是容器,不会体现在安装树的任何地方 |
调用 install 子命令后:
- 模块、包通常安装到Python库的 site-packages 下
- 脚本在Unix下通常安装到Python解释器二进制文件所在目录
- 脚本在Windows下通常安装到 %PYTHON_HOME%\Scripts 目录
你也可以调用pip命令,直接安装压缩包:
1 |
sudo pip install myautoresizer-0.1.0.tar.gz |
Python Package Index (PyPI)存储基于distutils分发的软件包的元数据信息,如果作者愿意,软件包本身也可以存放在上面。
通过 register 和 upload 子命令,你可以把元数据推送到PyPI上去。PyPI允许你提交某个软件包的任意数量的版本,你还可以覆盖既有的版本。
执行下面的命令注册软件包的元数据:
1 2 |
python setup.py register # 你需要根据提示,提供登录身份,或者注册新用户 |
执行下面的命令上传软件包:
1 2 3 |
# 上传源码发布包、Windows二进制发布包 python setup.py sdist bdist_wininst upload -r https://gmem.cc/pypi # 指定PyPI仓库地址 |
register和upload命令会检查 $HOME/.pypirc 文件,从中得到用户名、密码、仓库URL信息,该文件内容格式如下:
1 2 3 4 5 6 7 8 |
[distutils] index-servers = pypi [pypi] repository:https://pypi.python.org/pypi username:user password:passwd |
Section头中的文字,是仓库的名称,可以作为 -r 参数使用:
1 |
python setup.py sdist upload -r pypi |
Setuptools对distutils进行了一系列的增强,特别是,它能很好的处理包之间的依赖关系。下面是Setuptools的主要特性:
- 可以在构建阶段,利用easy_install自动查找、下载、安装、升级依赖。这些依赖可以通过HTTP、HTTPS、SVN、SourceForge等方式得到
- 创建EGG——单文件的、可导入的分发格式
- 对访问位于压缩文件中的数据文件提供增强支持
- 自动包含源码树中所有包,不必在setup.py中逐个指定
- 自动包含相关的文件到源码发布包中,不需要MANIFEST.in
- 对于工程中的每一个__main__函数,自动生成包装脚本或者Windows的exe文件
- 支持上传EGG或者源码发布包到PyPI
- 创建能自动发现扩展的可扩展程序
Setuptools保证了和distutils的兼容性,包括安装脚本的文件名、API风格都是一样的。Setuptools对API进行了很多扩展:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
setup( name="myautoresizer", version="0.1.2", packages=find_packages(exclude=["tests.*"]), # 自动在源码树中寻找Python包 install_requires=['docutils>=0.3'], # 指定依赖列表 scripts=['ma_autoresize.py', 'ma_printrect.py'], package_data={ # 对于任何包,其中的*.txt、*.text文件被包含到发布树 '': ['*.txt', '*.text'], # 对于myautoresizer,其中的*.ini文件被包含到发布树 'myautoresizer': ['*.ini'], }, entry_points={ 'console_scripts': [ 'ma_printrect = myautoresizer.scripts:ma_printrect', 'ma_autoresize = myautoresizer.scripts:ma_autoresize', ], } ) |
执行同样的命令,可以打源码发布包:
1 |
python setup.py sdist |
源码发布包会以tar.gz格式存放在dist子目录,同时,根目录会出现一个文件夹:myautoresizer.egg-info
关键字 | 说明 |
include_package_data | 如果设置为True,依据MANIFEST.in,包中的所有数据文件被包含到构建树中 |
exclude_package_data | 一个字典,Key为包名称,Value为需要排除掉的文件名通配符的列表 |
package_data | 一个字典,Key为包名称,Value为需要包含在构建树中的文件名通配符的列表 如果使用include_package_data,你不需要指定该选项,除非你需要包含setup脚本运行过程中生成的文件 |
zip_safe | 布尔值,提示当前工程是否可以安全的安装并从一个压缩文件中运行,如果不指定此选项,那么bdist_egg子命令必须分析整个工程,来寻找可能的问题 |
install_requires | 字符串或者列表,指定该包所依赖的其它包及其版本 |
entry_points |
一个字典,Key为扩展点组( entry point group)的名称,Value为定义扩展点的字符串或者列表 扩展点用于支持服务/插件的自动发现 |
extras_require | 一个字典,Key为工程额外特性的名称,Value为支持此额外特性需要安装的依赖 |
python_requires | 对Python版本的要求 |
setup_requires | 一个字符串或者列表,为了能让安装脚本运行,所需要的依赖 |
dependency_links | 一组URL由于搜索setup_requires、tests_require的依赖 |
namespace_packages |
用于命名工程的“命名空间包”的字符串列表。这个命名空间包(例如倒写的域名)可能被多个工程使用 EGG运行时系统能够自动合并具有共同命名空间包的子包,只要命名空间包的__init__.py不包含任何代码 |
对于大型工程来说,手工列出packages需要很大的工作量,此时可以使用find_packages()函数:
1 2 3 4 5 6 7 8 |
# where 指定源码目录,默认setup.py所在目录 # 在Python 3.2-只有包含__init__.py才能被识别为包 # exclude 需要排除的包名通配符 # include 需要包含的包名通配符 def find( where='.', exclude=(), include=('*',)): pass # 排除掉所有测试包 find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]) |
distutils打包、安装脚本的方式比较笨拙:
- 脚本的名称不能很好的匹配Windows/Linux的扩展名习惯
- 你需要编写单独的脚本,仅仅为了容纳一个__main__函数。my-autosizer的v0.1.0中的ma_*.py脚本就是因此而存在
要让setuptools自动创建脚本,只需要:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
setup( # 其它参数 entry_points={ # 控制台脚本 'console_scripts': [ 'foo = my_package.some_module:main_func', 'bar = other_module:some_func', ], # GUI脚本 'gui_scripts': [ 'baz = my_package_gui:start_func', ] } ) |
1 2 3 4 5 6 7 |
setup( entry_points = { 'setuptools.installation': [ 'eggsecutable = my_package.some_module:main_func' ] } ) |
添加此配置后,目标EGG增加可执行权限后,即可直接在Unix-like系统上执行。
对于依赖版本的限制,你可以使用以下操作符: < > <= >= == !=
PyPI网站包含大量的第三方扩展资源。对于没有依赖的简单库,可以使用脚本 python setup.py install 安装。
对于依赖关系复杂的库,最好使用setuptools提供的easy_install脚本,只需要输入 easy_install pkgname 即可安装指定的软件包,会自动从PyPI下载合适的软件、 依赖项。
pip是Python Packaging Authority (PyPA) 推荐的Python包管理器,支持从PyPI、版本控制系统、本地工程等来源安装软件包。
从2.7.9/3.4以后,该工具集成到Python中,不需要额外安装。手工安装的步骤如下:
1 2 3 |
wget https://bootstrap.pypa.io/get-pip.py python get-pip.py rm get-pip.py |
子命令 | 说明 | ||
install |
安装Python软件包,如果Wheel可用pip默认会使用之,要改变此行为,添加参数 --no-binary 举例:
|
||
uninstall | 卸载Python软件包 | ||
list |
列出已经安装的Python软件包,举例:
|
||
show | 显示一个已安装Python软件包的信息,包括名称、版本、安装位置、依赖关系 | ||
download | 下载Python软件包 | ||
freeze | 把已安装的软件包是出为需求文件格式(requirements format) | ||
search | 从PyPI搜索软件包,目标软件包的名称或者摘要信息必须包含指定的关键字 | ||
wheel |
要使用该命令,必须先安装 pip install wheel Wheel是一种归档格式,比起从源码归档安装,它的速度很快 |
||
hash | 计算包归档文件的哈希 |
你可以使用配置文件为pip命令提供默认选项。
配置文件位置如下:
配置文件 | 说明 |
/etc/pip.conf | 全局配置文件 |
~/.config/pip/pip.conf | 个人配置文件 |
~/.pip/pip.conf | 老版本使用的个人配置文件,目前仍然支持 |
$VIRTUAL_ENV/pip.conf | virtualenv中的配置文件 |
配置文件示例如下:
1 2 3 4 5 6 7 8 9 10 |
; 这一段针对所有子命令 [global] ; 命令执行超时 timeout = 60 ; 使用镜像包索引 index-url = https://pypi.doubanio.com/simple ; 这一段仅针对freeeze子命令 [freeze] timeout = 10 |
作为新版本的pip,目前还不成熟,安装步骤如下:
1 2 3 |
git clone https://github.com/osupython/pip2.git cd pip2 python setup.py install |
使用该工具可以方便的下载、构建、安装、升级、卸载Python软件包,安装步骤如下:
1 2 |
wget https://bootstrap.pypa.io/ez_setup.py python ez_setup.py |
该工具最常用的命令是easy_install,用来安装Python模块。
Virtualenv的目的是创建隔离的Python环境,可以解决不同软件之间依赖包冲突、以及潜在的文件权限的问题。
在开发Python应用的时候,依赖包默认都会安装到Python运行时的site-packages目录下。这样,如果两个应用依赖统一个包的不同版本,就会出现冲突。要解决这类问题,可以利用Virtualenv。
从3.3版本开始,Virtualenv的一部分功能作为标准库集成到venv模块中。不包含在其中的功能有:
- 创建Bootstrap脚本
- 为其它Python版本创建虚拟环境
可以使用任何版本的pip来安装和管理Virtualenv:
1 |
pip3 install virtualenv |
执行下面的命令,可以创建一个目录,并将其作为一个虚拟环境:
1 |
virtualenv certbot-dns-aliyun |
上面命令创建的虚拟环境的名字是certbot-dns-aliyun,他相当于一个Python的$PREFIX目录,和Python标准的目录结构对应:
- lib,此虚拟环境的库文件,包被安装到lib/pythonX.X/site-packages/下
- bin,可执行文件,包括python这个文件
包括pip、setuptools在内的包,会自动安装到新创建的虚拟环境中。
要进入虚拟环境,执行其中的脚本:
1 |
source certbot-dns-aliyun/bin/activate |
你会发现命令提示符增加了前缀 (certbot-dns-aliyun)。
现在,你通过pip安装的包,都会安装到当前虚拟环境中,系统的Python环境不会受到影响。
执行命令 deactivate即可退出当前虚拟环境。
简单的删除对应目录即可。
1 |
virtualenv [OPTIONS] DEST_DIR |
选项 | 说明 | ||
-p PYTHON_EXE |
指定使用的Python解释器:
将会基于该解释器(及其安装的包)来创建新的虚拟环境 |
||
--no-site-packages |
不将系统Python环境的第三方包复制过来,也就是说创建的是一个空的干净的环境 已经废弃,目前默认行为就是如此 |
||
--system-site-packages | 允许虚拟环境访问系统Python环境的第三方包 | ||
--always-copy | 总是拷贝而非符号链接文件 | ||
--relocatable |
使一个即有的虚拟环境可重定位:
|
||
--no-setuptools --no-pip --no-wheel |
不在虚拟环境中安装这些软件 | ||
--extra-search-dir | 从此额外的目录中搜索setuptools/pip,可以指定多次 | ||
--download --no-download |
从PyPI下载预安装的包 | ||
--prompt=PROMPT | 指定命令提示符前缀 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import random corpTypes = [22, 23, 24, 25, 26, 49, 50, 180, 181, 182, 184, 185, 186, 77, 164, 165, 220] # 对序列进行随机的重新排序 random.shuffle(corpTypes) # 从序列中随机选取一个 random.choice(corpTypes) # 从序列中随机选取N个样本 random.sample([10, 20, 30, 40, 50], k=N) # 随机浮点数 # 0-1之间随机 random.random() # 2.5-10.0之间随机 random.uniform(2.5, 10.0) # 随机整数 # 0-9之间随机 random.randrange(10) # 3-9之间随机 random.randrange(3,10) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#decimal模块实现了IBM通用十进制算法标准,能够准确表示十进制值 import decimal x = decimal.Decimal('3.4') y = decimal.Decimal('4.5') a = x * y #15.30 b = x / y #0.7555555555555555555555556 #改变精度并计算 decimal.getcontext().prec = 3 #三位有效数字 a = x * y #15.3 b = x / y #0.756 #只改变语句块的精度 with decimal.localcontext(decimal.Context(prec=10) |