python语法

in 操作符:也叫成员检测符。
item in obj, 返回True或False。 obj需要是一个可迭代对象(iterable), 或者实现了__contains__方法的自定义类。

def gen_fun():
    yield 1
    yield 2
    yield 3

print(3 in [1,2,3])  # in + 列表
print(3 in (1,2,3))  # in + 元组

gen = gen_fun()
print((3 in gen))  # in + 生成器

print(3 in range(1,4)) # in + range对象

需要注意的是, range函数返回的是range对象,也是一个可迭代对象,但它不是生成器。
另外,这种表达式在计算为True时就直接返回了,不会一定要将可迭代对象的所有内容都遍历完。 以生成器为例:

def gen_fun():
    yield 1
    yield 2
    yield 3

gen = gen_fun()  # 返回生成器(其实也是一种迭代器,generator iterator)

print((2 in gen))  # 遍历迭代器两次后返回True
print(next(gen))  # 迭代器又遍历下一个元素,因为in表达式遍历到了2这个值,这次遍历会返回3这个值
print(next(gen))  # 报StopIteration异常,因为该迭代器已经在上一次执行到了最后一个yield语句。

再看一个实现了__contains__方法的自定义类:

class Foo:
    def __contains__(self, item):
        if item in [1,2,3]:
            return True
        else:
            return False

foo = Foo()
print(3 in foo)  # True
print(4 in foo)  # False

Chained Expressions: python中可以用多个比较运算符连接多个表达式成为一个语句。

number = 5
0 <= number <= 10  # True
1 < number > 4   # True
1 < number > 6  # False

可以写在一行的复杂表达式: Python 允许以下情况在一行内写复杂表达式:

列表推导式: [expression for item in iterable if condition]
expression还可以是一个三元表达式:
new_list = [true_expr if conditional else false_expr for member in iterable]
生成器表达式: (expression for item in iterable if condition)
集合推导式: {expression for item in iterable if condition}
字典推导式: {key: value for item in iterable if condition}
lambda 表达式: lambda x: expression
三元操作符: value1 if condition else value2

写个稍微复杂一点的生成器表达式的例子:

class Base:
    def __iter__(self):
        pass

class Foo(Base):
    pass

gen_obj = ("__iter__" in B.__dict__ for B in Foo.__mro__)
for item in gen_obj:
    print(item)

输出结果:

False
True
False

解析一下这个生成器表达式。 其中 for B in Foo.__mro__ 这一部分属于语法规则里的 for item in iterable
"__iter__" in B.__dict__ 这一部分属于 expression,在这个例子中它也是一个 item in iterable的写法,in操作符就是上面讲的成员检测操作符, 而它会返回一个bool型值。 所以整个生成器表达式返回的生成器对象,每次迭代都将返回一个布尔值。

上面的推导式或生成器表达式里的for循环可以是嵌套的。举个简单的例子:
print([(x, y) for x in range(2) for y in range(2)]) # [(0, 0), (0, 1), (1, 0), (1, 1)]

行的连续: 当一个语句比较长时,我们希望能换行输入。有两种方式使多行被认为是一条语句。
Implicit Line Continuation: 使用(), [], {} 时,里面的换行会被认为是一条语句。
比如下面的例子:

a = [
    [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]
]

person1_age = 42
person2_age = 16
person3_age = 71

someone_is_of_working_age = (
    (person1_age >= 18 and person1_age <= 65)
    or (person2_age >= 18 and person2_age <= 65)
    or (person3_age >= 18 and person3_age <= 65)
)

下面列举使用()来拆分一条语句到多行的情况:
Expression grouping

x = (
    1 + 2
    + 3 + 4
    + 5 + 6
)

Function call

print(
    'foo',
    'bar',
    'baz'
)

Tuple definition

t = (
    'a', 'b',
    'c', 'd'
)

花括号的情况
Dictionary definition

d = {
    'a': 1,
    'b': 2
}

Set definition

x1 = {
    'foo',
    'bar',
    'baz'
}

Explicit Line Continuation: 使用""进行显示的换行。

s = \
'Hello, World!'

x = 1 + 2 \
    + 3 + 4 \
    + 5 + 6

使用";"分隔多个assign语句: 这样可以在一行编写多个赋值语句。但这种写法很不pythonic。

x = 1; y = 2; z = 3
print(x); print(y); print(z)

封装和解构: python中,右侧用逗号分隔的多个表达式自动会求值然后组合成元组,赋值给左边变量时,如果左边变量也是用逗号分隔开的多个变量,则依次按位置顺序赋值。

a , b = 100, 200
print(a, b)
a,b = b, a
print(a, b)

输出结果:

100 200
200 100

上面例子中a,b = b,a 这句其实就是一个先封装再解构的过程。 否则按照其它编程语言,a=b, b=a这样是不可能完成两个变量值的交换的。

解包赋值(unpacking assignment)

variable_1, variable_2, ..., variable_n  = n_length_iterable

(variable_1, variable_2, ..., variable_n) = n_length_iterable

[variable_1, variable_2, ..., variable_n] = n_length_iterable

variable, *bag_variable, ..., variable_n = unknown_length_iterable

python的解包赋值语句可以写的非常灵活,下面总结了一些场景:
1. 基础解包赋值

# 基本形式
a, b = 1, 2

# 交换变量值(不需要临时变量)
a, b = b, a

# 从函数返回多个值
def get_point():
    return 3, 4
x, y = get_point()

2. 带星号的解包(Extended Unpacking)

# *收集多余的元素到列表中
first, *rest = [1, 2, 3, 4, 5]
print(first)  # 1
print(rest)   # [2, 3, 4, 5]

# 星号可以在任何位置
head, *body, tail = [1, 2, 3, 4, 5]
print(head)  # 1
print(body)  # [2, 3, 4]
print(tail)  # 5

# 如果没有多余元素,*变量会得到空列表
first, *rest = [1]
print(rest)  # []


# 使用特殊变量_占位,来忽略不感兴趣的值
x, _, y = (1, 2, 3)
print(x, y)  # 1 3

x, _, _, y = [1, 2, 3, 4]
print(x, y)  # 1 4

3. 嵌套解包

# 解包嵌套结构
(a, b), (c, d) = [(1, 2), (3, 4)]
print(a, b, c, d)  # 1 2 3 4

# 更复杂的嵌套
((a, b), (c, (d, e))) = ((1, 2), (3, (4, 5)))

4. 解包到字典和列表

# 解包键值对到字典
dict1 = {'a': 1, 'b': 2}
dict2 = {'c': 3, 'd': 4}
merged = {**dict1, **dict2}  # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# 解包到列表
lst1 = [1,2,3]
lst2 = [4,5,6]
lst_merge = [*lst1, *lst2]  # [1, 2, 3, 4, 5, 6]

5. 在函数调用中使用解包

# 解包列表作为位置参数
def add(x, y, z):
    return x + y + z
nums = [1, 2, 3]
print(add(*nums))  # 6

# 解包字典作为关键字参数
def show_info(name, age, city):
    print(f"{name} is {age} years old, from {city}")
info = {'name': 'Alice', 'age': 25, 'city': 'Beijing'}
show_info(**info)

6. 解包生成器和迭代器
任何可迭代对象都可以被解包, 包括普通可迭代对象,迭代器,生成器,及一些其它python内置的可迭代对象。

RED, YELLOW, BLUE = range(3)
print(RED, YELLOW, BLUE)  # 0 1 2

def fun():
    yield 'x'
    yield 'y'
    yield 'z'

X, Y, Z = fun()
print(X, Y, Z) # x y z

# 解包任何可迭代对象
a, b, c = map(str, [1, 2, 3])
print(a, b, c)  # '1' '2' '3'

# 解包zip对象
names = ['Alice', 'Bob']
ages = [25, 30]
for name, age in zip(names, ages):
    print(f"{name} is {age}")

列表切片赋值:

>>> letters = ["A", "b", "c", "D"]
>>> letters[1:3] = ["B", "C"]
>>> letters
['A', 'B', 'C', 'D']

>>> letters[3:] = ("F", "G")
>>> letters
['A', 'B', 'C', 'F', 'G']

>>> letters[3:3] = ["D"]
>>> letters
['A', 'B', 'C', 'D', 'F', 'G']

>>> letters[1::2] = ["b", "d", "g"]
>>> letters
['A', 'b', 'C', 'd', 'F', 'g']

即便切片的元素个数与赋值的元素个数不一样,也能成功。说明本质上是将切片的元素整体移除,并在对应位置插入传入的值。

letters = ["A", "b", "c", "D", "E"]

letters[1:3] = ("F", "G", "H")
print(letters)  # ['A', 'F', 'G', 'H', 'D', 'E']
letters = ["A", "b", "c", "D", "E"]

letters[1:3] = "F"
print(letters)  # ['A', 'F', 'D', 'E']

可以看到,不光要传入的值多于还是少于切片对应的元素个数,本质上都是先移除切片对应的元素,再在切片对应的位置插入传入的值。

多重赋值(multiple assignment)或(parallel assignment): 多重赋值的语法可以很灵活。 大部分时候列表,元组可以互换。也可以不带[]或()

variable_1, variable_2, ..., variable_n = value_1, value_2, ..., value_n

variable_1, variable_2, ..., variable_n = exp_1, exp_2, ..., exp_n

(variable_1, variable_2, ..., variable_n) = exp_1, exp_2, ..., exp_n

[variable_1, variable_2, ..., variable_n] = exp_1, exp_2, ..., exp_n

Assigment Expression: 赋值表达式,使用:=运算符,又称海象运算符(Walrus Operator)。即做为一个表达式,又能同时为一个变量赋值。
在python中,expression和statement是不同的概念。expression和statement都可以独立在一行出现。 比如,a = 1,这是一个assign statement。 我们也可以在一行直接写个变量a,就像在交互式编程环境(如ipython)中那样,也是合法有效的。
但有时我们在某些场景下,需要用到一个表达式,但是同时又希望这个表达式顺带着将其运行结果赋值给一个变量,这时就可以使用赋值表达式。
比如下面场景,我们构造字典时多计算了一遍sum和len函数。

numbers = [2, 8, 0, 1, 1, 9, 7, 7]

description = {
    "length": len(numbers),
    "sum": sum(numbers),
    "mean": sum(numbers) / len(numbers),
}

print(description)  #  {'length': 8, 'sum': 35, 'mean': 4.375}

如果列表的元素特别多的话,我们可能会像下面这样来优化:

numbers = [2, 8, 0, 1, 1, 9, 7, 7]

num_length = len(numbers)
num_sum = sum(numbers)

description = {
    "length": num_length,
    "sum": num_sum,
    "mean": num_sum / num_length,
}

print(description)

使用赋值表达式,我们可以用更好的方式来优化。

numbers = [2, 8, 0, 1, 1, 9, 7, 7]

description = {
    "length": (num_length := len(numbers)),
    "sum": (num_sum := sum(numbers)),
    "mean": num_sum / num_length,
}

print(description)

一般使用赋值表达式时使用的变量都用作短期使用。
下面的列表生成式也存在多执行一遍函数的问题。

numbers = [7, 6, 1, 4, 1, 8, 0, 6]
results = [slow(num) for num in numbers if slow(num) > 0]

同样,可以使用赋值表达式来做如下优化。

numbers = [7, 6, 1, 4, 1, 8, 0, 6]
results = [value for num in numbers if (value := slow(num)) > 0]

f-string:在字符串在插入变量。我们可以使用format specifiers来进行格式控制。 这种格式符的用法又称为Format Mini-Language
下面是所有format specifiers:

format_spec     ::=  [[fill]align][sign]["z"]["#"]["0"][width]
                     [grouping_option]["." precision][type]
fill            ::=  <any character>
align           ::=  "<" | ">" | "=" | "^"
sign            ::=  "+" | "-" | " "
width           ::=  digit+
grouping_option ::=  "_" | ","
precision       ::=  digit+
type            ::=  "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" |
                     "G" | "n" | "o" | "s" | "x" | "X" | "%"

下面看下填充字符,对齐方式,宽度这几个格式控制的示例:

text = "Hello!"
print(f"{text:=>030}")  # width前面那个["0"] 可有可无
print(f"{text:=>30}")  # "="这里面可以换成任意字符,填充用。">"左侧填充
print(f"{text:=^30}")  # "^"两侧填充
print(f"{text:*<30}")  # "<"右侧填充

输出结果:

========================Hello!
========================Hello!
============Hello!============
Hello!************************

sign(正负号)控制示例:

>>> positive = 42
>>> negative = -42

>>> f"{positive:+}"
'+42'
>>> f"{negative:+}"
'-42'

>>> f"{positive:-}"
'42'
>>> f"{negative:-}"
'-42'

>>> f"{positive: }"
' 42'
>>> f"{negative: }"
'-42'

+0000042 的表示方法,这回就用到了"="这种align方式,它会将填充字符填充在sign的后边。

# "+0000042" 的表示方法
print(f'{postive:0>+6}') #  000+42 不是正确的数字格式
print(f'{postive:0=+6}') #  +00042 是正确的数字格式

下面是小数的控制格式,可以看到会自动进行四舍五入

from math import pi

print(pi)  # 3.141592653589793
print(f"{pi:.4f}")  # 3.1416
print(f"{pi:.8f}")  # 3.14159265

千分位符只有两个,","或"_"

>>> number = 1234567890

>>> f"{number:,}"
'1,234,567,890'

>>> f"{number:_}"
'1_234_567_890'

>>> f"{number:,.2f}"
'1,234,567,890.00'

>>> f"{number:_.2f}"
'1_234_567_890.00'

还可以在format speicifer中嵌入变量,从而动态控制输出格式。

>>> total = 123456.99

>>> # Formatting values
>>> width = 30
>>> align = ">"
>>> fill = "."
>>> precision = 2
>>> sep = ","

>>> f"Total{total:{fill}{align}{width}{sep}.{precision}f}"
'Total....................123,456.99'

百分比表示形式:

dec = 0.666666
print(f'{dec:.2%}')  # 66.67%

日期表示形式:Format Specification Mini-Language并没有对日期的格式化控制符。但f-string中使用d%这样的字符会自动调用strftime方法。

>>> from datetime import datetime

>>> now = datetime.now()
>>> now
datetime.datetime(2024, 1, 31, 14, 6, 53, 251170)

>>> >>> f"Today is {now:%a %b %d, %Y} and it's {now:%H:%M} hours"
"Today is Wed Jan 31, 2024 and it's 14:06 hours"

下面是数字进制转换的格式控制符用法:

>>> number = 42

>>> f"int: {number:d},  hex: {number:x},  oct: {number:o},  bin: {number:b}"
'int: 42,  hex: 2a,  oct: 52,  bin: 101010'

下面是数字的科学计数法表示法:

>>> large = 1234567890
>>> f"{large:e}"
'1.234568e+09'
>>> f"{large:E}"
'1.234568E+09'

>>> number = 42.42
>>> f"{number:f}"
'42.420000'
>>> inf = float("inf")
>>> f"{inf:f}"
'inf'
>>> f"{inf:F}"
'INF'

>>> f"{large:g}"
'1.23457e+09'
>>> f"{large:G}"
'1.23457E+09'
>>> f"{number:g}"
'42.42'

"#"用于展示代表进制的前缀字符:

>>> number = 42

>>> f"hex: {number:#x};  oct: {number:#o};  bin: {number:#b}"
'hex: 0x2a;  oct: 0o52;  bin: 0b101010'

>>> f"float: {number:#.0f}"
'float: 42.'

f-string !r, !s的用法: !r相当于调用了对象的__repr__方法,!s相当于调用了对象的__str__方法。

name = "Fred"
print(f"He said his name is {name!r}.")
print(f"He said his name is {repr(name)}.")
print(f"He said his name is {name!s}.")
print(f"He said his name is {str(name)}.")

print('-' * 25, '分隔线', '-' * 25)

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"my name is {self.name}, I'm {self.age} years old"

    def __repr__(self):
        return f"Person({self.name},{self.age})"

harry = Person('Harry', 12)

print(f'{harry}')  # 默认使用__str__方法
print(f'{harry!s}')
print(f'{harry!r}')

输出结果:

He said his name is 'Fred'.
He said his name is 'Fred'.
He said his name is Fred.
He said his name is Fred.
------------------------- 分隔线 -------------------------
my name is Harry, I'm 12 years old
my name is Harry, I'm 12 years old
Person(Harry,12)

变量后加"="号的用法: 一般用于编写日志时比较方便

>>> variable = "Some mysterious value"

>>> print(f"{variable = }")
variable = 'Some mysterious value'

关于f-string的一些优劣:
f-string在大部分情况下都更易于使用,并且性能还比传统的format函数更高一点。但要注意,在logging模块记日志时,尽管可能跟据日志的level判断有些日志不需要记录,但f-string的表达式还是会被evaulate,这样会产生不必要的性能开销。
除此之外,f-string还不应该用在sql语句的构造上,因为可能产生sql注入的风险。
python 3.12对f-string做了一些优化:
相同的引号可以重复使用。以前,只能是双引号和单引号来区分f-string表达式的范围,现在可以使用相同引号。
3.12之前下面的代码会报错。

>>> person = {"name": "Jane", "age": 25}

>>> f"Hello, {person["name"]}!"
  File "<input>", line 1
    f"Hello, {person["name"]}!"
                      ^^^^
SyntaxError: f-string: unmatched '['

f-string可以嵌套:

>>> # Python 3.12

>>> f"{
...     f"{
...         f"{
...             f"{
...                 f"{
...                     f"Deeply nested f-string!"
...                 }"
...             }"
...         }"
...     }"
... }"
'Deeply nested f-string!'

以前不允许""出现在f-string中,3.12开始支持了:

>>> # Python 3.12

>>> words = ["Hello", "World!", "I", "am", "a", "Pythonista!"]

>>> f"{'\n'.join(words)}"
'Hello\nWorld!\nI\nam\na\nPythonista!'

>>> print(f"{'\n'.join(words)}")
Hello
World!
I
am
a
Pythonista!

f-string的{}可以占多行,支持行内注释。

# Python 3.12

employee = {
    "name": "John Doe",
    "age": 35,
    "job": "Python Developer",
}

f"Storing employee's data: {
    employee["name"].upper()  # Always uppercase name before storing
}"
posted @   RolandHe  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示