Python是一门开箱即用的动态语言,结合搜索引擎和cv大法其威力不可小觑。但很多人(包括我)的Python写的像Java或者C++,他们知道如何用Python,却不知道如何Pythonic地写Python。本文不赘述常识性的Python或编程基础,聚焦于Python特有的,和其他语言不太一样的部分,无法面面俱到还请原谅。
本文基于Python3.7
- 格式化输出
first = 'kevin'
last = 'xue'
full = f'{len(first)} {last}' # f或者F表示这是一个格式化字符串,通过计算得出
print(full) # 大括号内可以是任意表达式
- 字符串函数
course = ' python '
print(course.upper())
print(course.lower())
print(course.title()) # 首字母大写
print(course.strip())
print(course.find('p'))
print(course.replace('p', 'j'))
print('py' in course)
print('swift' not in course)
- Number类型函数
import math # 该模块内置很多代数函数
print(round(2.6)) # 四舍五入
print(abs(-2.9))
print(math.ceil(2.2)) # 向上取整
- 三元运算符
val = 2 if 3 > 2 else 1
print(val)
- Pyhon中的逻辑运算符
Python中的逻辑运算符不再是&&,||,!而是and,or和not:
if 3 > 2 and 2 > 1:
print('yes')
if 3 > 2 or 5 > 6:
print('yes')
if not 3 > 6:
print('yes')
- 神奇的链式比较
Python提供了一种更加代数友好更加优雅的比较表达式:
x = 6
if 3 < x < 7: # 等价于 x>3 and x<7
print('yes')
是不是很漂亮?
- Python的类型转换
Python是一门强类型语言,意味着变量的类型确定之后很多操作会受到限制
比如Python中不能把字符串和int相加,也不能大小比较
同时,Python中基本没有隐式类型转换,很多操作之前需要进行强制类型转换
x = int(input('x:'))
y = x+1
print(f"y:{y}")
- Python独有的For..else语法糖
for item in container:
if search_something(item):
# Found it!
process(item)
break
else:
# Didn't find anything..
not_found_in_container()
值得注意的是,break的存在是必要的,否则此处的else将没有任何意义
- 关于None
Python中并没有null,取而代之的是None
所有的函数默认返回None,除非你显式return - 默认参数
def increment(number, by=1): #默认参数要放在位置参数后面
return number + by
print(increment(2))
- 可变参数
def multiply(*numbers):
total = 1
for num in numbers:
total *= num
return total
print(multiply(2, 3, 4, 5))
- 关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
def save_user(**user):
print(user)
save_user(id=1, name='kevin', age=22) #{'id': 1, 'name': 'kevin', 'age': 22}
- 尽量不要使用全局变量,更不要试图在函数中通过global修改全局变量
- 列表list
Python最重要的数据结构之一
letters = ['a', 'b', 'c']
matrix = [[0, 1], [2, 3]]
zeros = [0]*5
combined = zeros+letters
numbers = list(range(10))
chars = list("kevin")
chars[::-1] #倒序
- 列表/元组拆箱(list/tuple unpack)
numbers = [1, 2, 3]
first, second, third = numbers # 变量数量需要等于列表长度
first, second, *others = numbers # 多余的元素会被放置到others列表
# 等价于
first = numbers[0]
second = numbers[1]
third = numbers[2]
- list遍历和增删改查
letters = ['a', 'b', 'c', 'd']
for letter in letters:
print(letter)
for index, letter in enumerate(letters): # 需要序号
print(f"{index}:{letter}")
# Add
letters.append('e')
letters.insert(0, '-')
print(letters)
# Remove
letters.pop() # 删除末尾元素
letters.pop(0)
letters.remove('b') # 删除第一个'b'
del letters[0:3] # 批量删除元素
letters.clear()
# Find
letters.count('a')
if 'd' in letters:
print(letters.index('d')) # 不存在的元素不会返回-1,而是直接报错
- list排序
l = [2, 3, 1, 10, 4, 5, 6, 7]
l.sort(reverse=True) # 简单list的排序
sorted(l, reverse=True) # 不会改变原list,返回排好序的副本
# 复杂list排序
items = [
('product1', 20),
('product2', 22),
('product3', 43),
('product4', 10),
]
def item_key(item):
return item[1]
items.sort(key=item_key, reverse=True)
items.sort(key=lambda item: item[1], reverse=True) # 通过lambada匿名函数改写
- list过滤和映射
items = [
('product1', 20),
('product2', 22),
('product3', 43),
('product4', 10),
]
x = list(map(lambda x: x[1], items))
y = list(filter(lambda x: x[1] >= 20, items))
- 列表推导式(list comprehension)
无疑是Python强大的特性之一
z = [item[1] for item in items if item[1] >= 20]
- zip打包函数
x = [1, 2, 3]
y = ['a', 'b', 'c']
print(list(zip(x, y))) # [(1, 'a'), (2, 'b'), (3, 'c')]
- tuple和list的操作非常相似,不同之处在于tuple是不可变的:
t = 1, 2 # 不需要括号也是tuple
x = 10
y = 11
x, y = y, x # 利用tuple交换两个变量的值
a, b = 0, 1 # 利用tuple赋值
- 当处理大量数字时,array比list在性能的表现上更优秀,如果不考虑性能则无脑list即可
from array import array
numbers = array('i', [1, 2, 3]) # 所有元素类型需要一致
- 集合(set)
numbers = [1, 1, 2, 2, 4, 3]
first = set(numbers)
second = {1, 5}
print(first | second) # 并集
print(first & second) # 交集
print(first - second) # 差集
- 字典(dictionary)
key只能是不可变类型,值得注意的是,set和dict都是无序的,意味着它们不能随机访问也不能排序
point = {'x': 1, 'y': 2, 'z': 4}
point = dict(x=1, y=2, z=4) # 构造dict的方法
if 'a' in point:
print(point['a']) # 访问不存在的key会报错
print(point.get('a'), 0) # 不存在的key返回0,默认返回None
del point['x']
for key, val in point.items(): # 返回tuple,利用拆箱赋值
print(key, val)
- 再探推导式(comprehension expression)
推导式对dict和set同样起作用
nums = {x for x in range(10)}
vals = {x: x * 2 for x in range(5)}
- 生成器(generator)
使用列表推导式时,会一次性生成整个数据序列,当序列非常大甚至是无穷尽的时候,我们需要生成器,它是iterable的,同时数据会在被需要时动态生成,有点流的感觉,不是吗?
vals = (x*2 for x in range(10)) # 使用圆括号包围推导式时,我们得到一个generator
for item in vals:
print(item)
- 拆箱操作符(unpacking operator)
又一个强大的Python特性
所谓拆箱的意思就是把任意iterable拆分成多个其内元素
l = [*range(10), 'w', *'kevin']
print(*l) # 通过拆箱传入多个可选参数
x = {'x': 10, 'y': 20}
y = {'z': 30}
z = {**x, **y} # dict也可以拆箱
- 异常处理
简单来说异常处理就是捕获程序可能抛出的甚至很多时候不可避免的异常,给用户提供错误信息的同时不打断程序执行流,由于机制与Java等语言大同小异,此处不赘述
try:
age = int(input("age:"))
except (ValueError, ZeroDivisionError) as error: # 同时捕获多种异常
print('you didn\'t enter a valid age.')
print(error)
else: # optional 如果没有异常则执行
print('no exceptions were thrown')
finally: # 总会执行
pass
- with语句
通过with打开外部资源,会自动释放资源
with open('app.py') as f:
f.read()
- 抛出异常(raise exception)
一般不建议手动抛出异常,时间花费较大
def calculate(age):
if age <= 0:
raise ValueError('age cannot be 0 or less')
return 10/age
- 类名的一般命名规则:驼峰命名首字母大写
class Point:
default_color = "red" # 类属性
def __init__(self, x, y): # 构造器:需要显式定义self
self.x = x
self.y = y
def draw(self):
print(f"Point ({self.x},{self.y})")
def __str__(self): # 用于将对象转换为字符串
return f"Point ({self.x},{self.y})"
point = Point(1, 2)
point.z = 10 # Python可以动态添加实例属性
print(point)
print(type(point))
print(isinstance(point, Point)) # 判断对象的归属类
形如__init__和__str__这种有双下划线的函数,称为魔法方法(magic method),它们一般由Python解释器在合适的时间自动调用
- 对象的比较:
默认情况下,==比较符比较的是对象的引用是否相等(或者说对象的地址是否相同),我们需要重写魔法方法来改写比较规则
def __eq__(self, other): # 判断对象是否相等
return self.x == other.x and self.y == other.y
def __gt__(self, other): # 判断对象大小 greater than
return self.x > other.x and self.y > other.y
point = Point(2, 3)
other = Point(1, 2)
print(point > other) # True
print(point == other) # False
重写了>(__gt__)之后就不用再重写<了。
- 对象的计算
def __add__(self, other):
return Point(self.x+other.x, self.y+other.y)
34.定制容器
class TagCloud:
def __init__(self):
self.__tags = {} # 前双下划线使得属性私有
def add(self, tag):
self.__tags[tag.lower()] = self.__tags.get(tag.lower(), 0)+1
def __setitem__(self, tag, count): # 激活[]赋值
self.__tags[tag.lower()] = count
def __getitem__(self, tag): # 激活[]访问
return self.__tags.get(tag.lower(), 0)
def __len__(self): # 激活len()
return len(self.__tags)
def __iter__(self): # 使得iterable,需要返回一个iterator
return iter(self.__tags)
cloud = TagCloud()
cloud.add('python')
cloud['c#'] = 10
cloud['java'] = 21
print(len(cloud))
for i in cloud:
print(f"{i}:{cloud[i]}")
值得一提的是,前双下划线并不能使属性完全私有,它更多是一种惯例和规范上的预防和提示。
-
@property
又一个非常方便的Python语法特性
class Product:
def __init__(self, value):
self.price = value
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value < 0:
raise ValueError
self.__price = value
我们使用@property修饰方法,此时会把方法直接变成同名属性。当我们获取属性的值时,实际上就是调用的此方法。
此时,本身又自动创建了另一个装饰器,负责把一个方法变成属性赋值,即:price的setter方法。于是,修饰的其实是,当我们给price赋值时,实际上就是调用的此方法。此时,price对外表现得就像一个普通的attribute
- 继承
class Animal:
def __init__(self):
self.age = 1
def eat(self):
print('eat')
class Mammal(Animal):
def walk(self):
print('walk')
m = Mammal()
print(m.age) # 1
m.eat() # eat
print(isinstance(m, object)) # True
print(issubclass(Mammal, Animal)) # True
所有的类都直接或者间接继承自object类
除此以外,还可以非常方便地通过继承扩展内置类型(比如str,list等)
- 继承中的构造器
不同于Java或者C++的构造器机制(类同名,默认构造器,从父到子),Python中的构造器只有一个名字__init__,这也造成了一个神奇的结果:Python的构造器是可以覆盖的,而且子类的实例化可以完全不执行父类的构造方法。如果需要在子类的构造器中执行父类的构造器,需要显式指明:
def __init__(self):
super().__init__()
self.weight = 2
另外,如果子类没有自己的构造器,则只会执行父类的构造器,也就是说不存在所谓的默认构造器。
- Python中的抽象类和抽象方法
机制大同小异,不再赘述:
from abc import ABC, abstractmethod
class Stream(ABC): # 抽象类
@abstractmethod
def read(self): # 抽象方法
pass
class MemoryStream(Stream):
def read(self):
print('read')
stream = MemoryStream()
值得注意的是,Python并不支持函数重载,这也意味着所谓的重写(覆盖)的函数签名不包括形参,而只是函数名。
- 多态
由于Python中不存在所谓的上转型对象,所以实现多态的方式也有所变化。
可以简单的通过一个函数实现
def operate(control):
control.do_something()
显然,只要对象存在该方法,即使不在同一继承体系也可以成功执行,即“is-a“变成了“like-a"
- 纯数据类
from collections import namedtuple
Point = namedtuple("Point", ['x', 'y'])
p1 = Point(x=1, y=2)
p2 = Point(x=1, y=2)
print(p1 == p2)
我们不必在重写魔法方法来定义比较规则,同时这种方法创建的数据类是不可变的
- 引入模块的方式
from sales import calc_shipping # 此时calc_shipping就好像在该文件定义一样直接使用
import sales # 此时sales可以看作一个对象,通过sale.doSomething访问模块成员
- Python编译
一旦我在main里import了app并运行,就会产生如图的文件
[站外图片上传中...(image-14bf3c-1553528837227)]
实际上,pycache里存放了app的编译结果作为缓存,加快下次引入app模块的速度。
同时,Python不会编译作为入口的文件(此处是main.py) - 模块路径
就如同Java的类路径,Python也有模块路径用于在程序import时搜寻相应的模块,当前目录就是其中之一。
import sys
print(sys.path) # 打印所有模块路径
- 包(package)
包简单来说就是模块的集合,对应到文件层面包就是一个目录,模块就是一个py文件。
新建一个目录,并在其中新建一个名为__init__.py的文件,这个目录就变成了一个包,同时包还可以有子包,同样创建一个子目录和init文件,以此类推。
那么如何import同级子包中的模块呢?
from 最顶级包.目标包 import 目标模块
from ..目标包 import 目标模块 # 两个点表示上级目录(包)
两种方法分别对应绝对和相对寻址
import app
print(dir(app)) #返回scope的所有属性名
print(app.__name__) # 模块名
print(app.__file__) # 路径
print(app.__package__)
值得注意的是,如果直接执行某个模块,模块名(__name__)总会是__main__,所以也就有了常见的:
if __name__ == "__main__":
pass