python 语言类型鉴别
常用的编程语言比如 java,javascript,python 等,我们可以从两个角度对语言进行分类。一个是动态语言 vs 静态语言,另一个是强类型 vs 弱类型。
- 动态语言还是静态语言指的是编译期还是运行期确定类型;
- 强类型和弱类型指的是是否会发生类型的隐式转换;
而 python 的语言类型定义是强类型的动态语言。
python 的变量
python 的变量实质上是一个指针。
这不同于 java,在 java 中如果要声明一个字符串,那么这个字符串会复制给字符串的变量,这个变量也就不能接受其他类型的参数了,而 python 的变量仅仅是一个指针,可以接收任何类型的数据。
python 常见内置类型
常见内置类型
1. 数值类型: int, float, complex, bool
2. 迭代类型: 所有实现迭代协议的类
3. 序列类型: list, bytes, bytearray, memoryview, range, tuple, str, array
4. 映射类型: dict
5. 集合类型: set, frozenset
6. 上下文管理类型: with 语句
7. 其他类型: 模块类型, class 和实例, 函数类型, 方法类型, 代码类型, object 对象, type 类型, ellipsis 类型, notimplemented 类型
类型和值的判断
python 中常见的判断方法主要有 type
,isinstance
,==
,is
。其区别如下:
==
用来判断值是否相等
# 例子
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True,原理是调用了魔法函数 __eq__
is
用来确定是否是同一个类。
PS: python 中 None
是单例的,因此判断是否是 None
必须用 is
(比较内存地址),而不是 ==
(比较值)
type
用来判断是否是类的本身,比如 type(x)
的得到的结果为 <class '__main__.X'>
。
isinstance
判断对象是否是类的实例。
判断对象类型的时候,这个函数会同时去检查类的继承链,这也就隐含这个多态的概念。
为什么下面的结果相等?
a = 1
b = 1
print(a is b)
python 中对于“小整数”或者“短字符串”在一定范围内会使用 intern 机制,这是 python 对于性能的一种优化机制,旨在提高运行的性能,因此结果为 True。
python 的作用域
python 的作用域和 java 不一样,作用域由 def
、class
、lambda
等语句产生,if
、try
、for
、with
等语句并不会产生新的作用域。
# if 语句不会生成新的作用域
if True:
msg = 'I am from Runoob'
print(msg)
# for 语句不会生成新的作用域
for item in [1, 3, 4]:
pass
abc = 123
print(abc)
print(item)
# try…catch 语句不会生成新的作用域
try:
edf = 456
except Exception as e:
pass
print(edf)
python 参数传递
python 参数传递的方式比较特殊,和其他语言进行区别,既不是“值传递”也不是“引用传递”。
python 参数传递的方式为 Call by Object(Call by Object Reference or Call by Sharing),也就是共享传参。函数形式参数获得实参中各个引用副本。
在进行函数传参的时候,python 是根据可变对象和不可变对象的特点让传递参数的行为类似于“引用传递”和“值传递”。
不可变对象:bool/int/float/tuple/str/frozenset
可变对象:list/set/dict
def add(a, b):
a += b
return a
# 不可变类型
a = 1
b = 2
c = add(a, b)
print(a, b, c) # 1 2 3
# 可变类型
a = [1, 2]
b = [3, 4]
c = add(a, b)
print(a, b, c) # [1, 2, 3, 4] [3, 4] [1, 2, 3, 4]
# 不可变类型
a = (1, 2)
b = (3, 4)
c = add(a, b)
print(a, b, c) # (1, 2) (3, 4) (1, 2, 3, 4)
总结: python 传递参数的时候都是通过“对象引用”的方式进行传递的,也就是实参和形参都指向同一个对象,但是根据可变对象和不可变对象的特点表现出类似于“引用传递”和“值传递”的行为。
还有一个注意的点:默认参数只计算一次
# 通过函数举例
def first(l=[1]):
l.append(1)
print(l)
first()
first()
# 结果如下:
# [1, 1]
# [1, 1, 1]
# 通过对象举例
class Company:
def __init__(self, name, staffs=[])
self.name = name
self.staffs = staffs
def add(self, staff_name):
self.staffs.append(staff_name)
def remove(self, staff_name):
self.staffs.remove(staff_name)
if __name__ == '__main__':
com1 = Company('com1')
com1.add('cc1')
print(com1.staffs) # ['cc1']
com2 = Company('com2')
com2.add('cc2')
print(com2.staffs) # ['cc1', 'cc2'],这个结果和预想的不一样
# 因为两次对象实例化都用了 Company.__init__.__defaults__ 中的默认变量
总结: 默认参数不推荐使用可变参数类型
函数传递中 *args
, **kwargs
的含义是什么?
- *args 被打包成 tuple
- **kwargs 被打包成 dict
# *args 传递
print_args(*['a', 'b', 'c'])
# **kwargs 传递
print_kwargs(**dict(a=1, b=2))
异常处理
异常的继承等级
下面是 python 异常的继承等级的前几个。可以通过网址 https://docs.python.org/3/library/exceptions.html#exception-hierarchy 查看全部的异常继承等级。
在日常变成中最常用的就是 Exception,大多数的异常类都继承自这个类。
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ...
为什么要继承 Exception 而不是 BaseException?
一个简单的回答:如果继承的是 BaseException 的话,ctrl + c 难道还能结束自己的程序吗?
使用异常的常见场景
- 网络请求(超时、链接错误)
- 资源访问(权限问题、资源不存在)
- 代码逻辑(越界访问、KeyError等)
异常的捕获
对于 python 中异常的捕获是和大多数编程语言一致的。都可以通过 try…catch 语句进行异常的捕获。
try:
# func
except (Exception1, Exception2) as e:
# 异常处理代码
else:
# pass
finally:
pass
Monkey patch
得益于动态语言的特性,所谓的 monkey patch 就是运行时替换的能力,也就是程序运行的时候,我们需要将一些内置的方法,类或者变量替换成自定义的。
比如 gevent 库在运行时需要可以修改内置的 socket 或 select。
# 运行时修改内置的 socket
import socket
print(socket.socket) # <class 'socket.socket'>
print("After monkey patch")
from gevent import monkey
monkey.patch_socket()
print(socket.socket) # <class 'gevent._socket3.socket'>
# 运行时修改内置的 select
import select
print(select.select)
monkey.patch_select()
print("After monkey patch")
print(select.select) # <function select at 0x7f82e7f33560>
使用 time 演示 monkey patch 原理
import time
print(time.time())
def _time():
return 123
time.time = _time # 关键代码
print(time.time())
Introspection (内省)
得益于动态语言的特性,所谓的内省就 可以在运行时判断一个对象的类型。
ll = [1, 2, 3]
dd = dict(a=1)
# 在运行时判断对象类型的能力
print(type(ll))
print(type(dd))
print(isinstance(ll, list))
print(isinstance(dd, dict))