基本
深浅拷贝
浅拷贝只会复制最顶层的列表.
深拷贝会递归拷贝.
copy.copy对于可变类型,会进行浅拷贝
copy.copy对于不可变类型,不会拷贝,仅仅是指向
私有化
- xx: 公有变量
- _x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问
- __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
- xx:双前后下划线,用户名字空间的魔法对象或属性。例如:init , __ 不要自己发明这样的名字
-
xx_:单后置下划线,用于避免与Python关键词的冲突
-
父类中属性名为__名字的,子类不继承,子类不能访问
- 如果在子类中向__名字赋值,那么会在子类中定义的一个与父类相同名字的属性
多继承
- super().init__相对于类名.__init,在单继承上用法基本无差
- 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次.
- 多继承时,使用super方法,对父类的传参数,必须把参数全部传递,否则会报错
- 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
import
import sys
print(sys.path) # 输出当前import 搜索路径
输出
['',
'/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip',
'/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7',
'/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload',
'/Users/*/Library/Python/3.7/lib/python/site-packages',
'/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']
'' 表示当前路径
列表中的路径的先后顺序代表了python解释器在搜索模块时的先后顺序
程序执行时添加新的模块路径
sys.path.append('/**/xxx')
sys.path.insert(0, '/**/**/xxx') # 可以确保先搜索这个路径
重新导入模块
import your_moudle
from imp import reload
reload(your_moudle)
class Goods:
@property
def price(self):
print('@price.getter')
@price.setter
def price(self, value):
print('@price.setter')
@price.deleter
def price(self):
print('@price.deleter')
类属性方式
class Goods(object):
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
def get_price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
def set_price(self, value):
self.original_price = value
def del_price(self):
del self.original_price
PRICE = property(get_price, set_price, del_price, '价格属性描述...')
obj = Goods()
obj.PRICE # 获取商品价格
obj.PRICE = 200 # 修改商品原价
del obj.PRICE # 删除商品原价
特殊属性
__doc__
表示类的描述__module__
表示当前操作的对象所在的模块__class__
表示当前操作的对象所属的类__init__
初始化方法,通过类创建对象时,自动触发执行__del__
当对象在内存中被释放时,自动触发执行.__call__
对象后面加括号,触发执行.__dict__
类或对象中的所有属性__str__
如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值__getitem__
、__setitem__
、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据__getslice__
、__setslice__
、__delslice__
该三个方法用于分片操作
上下文管理器
对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源
任何实现了 __enter__()
和 __exit__()
方法的对象, 都可称之为上下文管理器, 上下文管理器对象可以使用 with 关键字. 显然,文件(file)对象也实现了上下文管理器
Python 还提供了一个 contextmanager
的装饰器,更进一步简化了上下文管理器的实现方式.通过 yield
将函数分割成两部分,yield
之前的语句在 __enter__
方法中执行,yield
之后的语句在 __exit__
方法中执行.紧跟在 yield
后面的值是函数的返回值.
from contextlib import contextmanager
@contextmanager
def my_open(path, mode):
f = open(path, mode)
yield f
f.close()
闭包
def counter(start=0):
def incr():
nonlocal start
start += 1
return start
return incr
装饰器
def test_f(func):
# do something
func()
@test_f
def foo():
print("foo")
@函数名 是一种Python的语法糖, 当发foo()
被装饰器修饰时, @test_f def foo()
, 外部调用foo()
时, 就相当于调用了 test_f(foo)
.
装饰器的一些使用场景
* 引入日志
* 函数执行时间统计
* 执行函数前预备处理
* 执行函数后清理功能
* 权限校验等场景
* 缓存
* ...
当被装饰的函数有参数, 有返回值时:
def testfun(func):
def wrapped_func(*args, **kwargs):
return func(*args, **kwargs)
return wrapped_func
@testfun
def foo(a, b, c):
return a+b+c
装饰器带参数, 设置外部变量时:
def testfun_arg(pre="hello"):
def testfun(func):
def wrapped_func():
return func()
return wrapped_func
return testfun
@testfun_arg("woaa")
def foo():
print("I am foo")
可以理解为
foo() == testfun_arg("woaa")(foo)()
类装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 __call__()
方法,那么这个对象就是callable的
class Test(object):
def __init__(self, func):
print("---初始化---")
print("func name is %s"%func.__name__)
self.__func = func
def __call__(self):
print("---装饰器中的功能---")
self.__func()
@Test
def test():
print("do test--")
调用test()
就相当于Test(test).__call__()
元类
类也是一种对象, 只不过类对象拥有创建对象的能力.
type
可以动态的创建类:
type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含类属性、实例函数、静态函数、类方法的字典(名称和值))
@staticmethod
def test_static():
pass
@classmethod
def test_class(cls):
pass
def echo_bar(self):
pass
Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "test_static": test_static, "test_class": test_class})
元类就是这样用来创建类的东西
.
type就是Python在背后用来创建所有类的元类
__metaclass__属性
你可以在定义一个类的时候为其添加__metaclass__
属性, 这样Python就会用元类来创建类
class Foo(object):
__metaclass__ = something…
...省略...
Python会做如下操作:
Foo中有__metaclass__
这个属性吗?如果是,Python会通过__metaclass__
创建一个名字为Foo的类(对象)
如果Python没有找到__metaclass__
,它会继续在Bar(父类)中寻找__metaclass__
属性,并尝试做和前面同样的操作。
如果Python在任何父类中都找不到__metaclass__
,它就会在模块层次中去寻找__metaclass__
,并尝试做同样的操作。
如果还是找不到__metaclass__
,Python就会用内置的type来创建这个类对象.
自定义元类
自定义元类的主要目的就是为了当创建类时能够自动地改变类
class UpperAttrMetaClass(type):
# __new__ 是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回之的方法
# 而__init__只是用来将传入的参数初始化给对象
# 你很少用到__new__,除非你希望能够控制对象的创建
# 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
# 如果你希望的话,你也可以在__init__中做些事情
# 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def __new__(cls, class_name, class_parents, class_attr):
# 遍历属性字典,把不是__开头的属性名字变为大写
new_attr = {}
for name, value in class_attr.items():
if not name.startswith("__"):
new_attr[name.upper()] = value
return type(class_name, class_parents, new_attr)
class Foo(object, metaclass=UpperAttrMetaClass):
bar = 'bip'
就元类本身而言,它们其实是很简单:
1. 拦截类的创建
2. 修改类
3. 返回修改之后的类
线程
使用
python中的多线程可以使用接口较为友好的threading
来实现.
可以通过指定函数指针target来使用:
def run():
print("running")
t = threading.Thread(target=run)
t.start()
也可以基于封装性的考虑, 使用子类继承threading.Thread
, 并且重写run
方法的方式来使用:
class MyThread(threading.Thread):
def run(self):
msg = "running in "+self.name #name属性中保存的是当前线程的名字
print(msg)
my_thread = MyThread()
my_thread.start()
锁
多线程程序的执行顺序是不确定的,我们无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式.
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确.
为了避免这种情况, 同其他语言一样, 使用锁来进行同步控制.
threading
模块中定义了Lock
类,可以方便的处理锁定:
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
当一个线程调用锁的acquire()
方法获得锁时,锁就进入locked
状态。
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为blocked
状态.
CPython解释器中的GIL
进程
由于iOS App都是单进程的, 进程这个概念, 在iOS并不常见.
当一个程序运行起来后,程序的代码与其用到的资源 称之为进程,它是操作系统分配资源的基本单元.
而线程是进程的一个实体,是CPU调度和分派的基本单位.
进程在执行过程中拥有独立的内存单元, 代码中局部变量和全局变量都不会在进程间共享.
使用
multiprocessing
模块是跨平台版本的多进程模块,提供了一个Process
类来代表一个进程对象,这个对象可以理解为是一个独立的进程
from multiprocessing import Process
p = Process(target=run)
p.start()
class Process():
name: str
daemon: bool
pid: Optional[int]
exitcode: Optional[int]
authkey: bytes
sentinel: int
# TODO: set type of group to None
def __init__(self,
group: Any = ...,
target: Optional[Callable] = ...,
name: Optional[str] = ...,
args: Iterable[Any] = ...,
kwargs: Mapping[Any, Any] = ...,
*,
daemon: Optional[bool] = ...) -> None: ...
def start(self) -> None: ...
def run(self) -> None: ...
def terminate(self) -> None: ...
if sys.version_info >= (3, 7):
def kill(self) -> None: ...
def close(self) -> None: ...
def is_alive(self) -> bool: ...
def join(self, timeout: Optional[float] = ...) -> None: ...
进程间通信
前面讲到进程间是不共享资源, 但是如果需要跨进程通信时, 操作系统仍然提供了许多机制来实现. 例如, 我们可以使用multiprocessing
模块的Queue
实现多进程之间的数据传递,Queue
本身是一个消息列队程序.
协程
协程是另外一种实现多任务的方式,只不过比线程更小占用更小执行单元, 需要更少的资源.
通俗的理解就是在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行, 并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定.
协程的切换只是单纯的操作CPU上下文, 所以一秒钟切换个上百万次都扛得住.
多进程、多线程可能是并行的,但是协程是在一个线程中 所以是并发的.
使用
yield
def fib(n):
current = 0
num1, num2 = 0, 1
while current < n:
num = num1
num1, num2 = num2, num1 + num2
current += 1
yield num
f = fib(10)
for i in f:
print(i) # 输出: 0 1 1 2 3 5 8 13 21 34
gevent
from gevent import monkey
import gevent
import random
import time
# 有耗时操作时需要
monkey.patch_all() # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块
def coroutine_work(coroutine_name):
for i in range(10):
print(coroutine_name, i)
time.sleep(random.random()) #模拟耗时操作.
gevent.joinall([
gevent.spawn(coroutine_work, "work1"),
gevent.spawn(coroutine_work, "work2")
])