Python3标准库之threading进程中管理并发操作方法


Posted in Python onMarch 30, 2020

1. threading进程中管理并发操作

threading模块提供了管理多个线程执行的API,允许程序在同一个进程空间并发的运行多个操作。

1.1 Thread对象

要使用Thread,最简单的方法就是用一个目标函数实例化一个Thread对象,并调用start()让它开始工作。

import threading
def worker():
 """thread worker function"""
 print('Worker')
threads = []
for i in range(5):
 t = threading.Thread(target=worker)
 threads.append(t)
 t.start()

输出有5行,每一行都是"Worker"。

Python3标准库之threading进程中管理并发操作方法

如果能够创建一个线程,并向它传递参数告诉它要完成什么工作,那么这会很有用。任何类型的对象都可以作为参数传递到线程。下面的例子传递了一个数,线程将打印出这个数。

import threading
def worker(num):
 """thread worker function"""
 print('Worker: %s' % num)
threads = []
for i in range(5):
 t = threading.Thread(target=worker, args=(i,))
 threads.append(t)
 t.start()

现在这个整数参数会包含在各线程打印的消息中。

Python3标准库之threading进程中管理并发操作方法

1.2 确定当前线程

使用参数来标识或命名线程很麻烦,也没有必要。每个Thread实例都有一个带有默认值的名,该默认值可以在创建线程时改变。如果服务器进程中有多个服务线程处理不同的操作,那么在这样的服务器进程中,对线程命名就很有用。

import threading
import time
def worker():
 print(threading.current_thread().getName(), 'Starting')
 time.sleep(0.2)
 print(threading.current_thread().getName(), 'Exiting')
def my_service():
 print(threading.current_thread().getName(), 'Starting')
 time.sleep(0.3)
 print(threading.current_thread().getName(), 'Exiting')
t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # use default name
w.start()
w2.start()
t.start()

调试输出的每一行中包含有当前线程的名。线程名列中有"Thread-1"的行对应未命名的线程w2。

Python3标准库之threading进程中管理并发操作方法

大多数程序并不使用print来进行调试。logging模块支持将线程名嵌入到各个日志消息中(使用格式化代码%(threadName)s)。通过把线程名包含在日志消息中,就能跟踪这些消息的来源。

import logging
import threading
import time
def worker():
 logging.debug('Starting')
 time.sleep(0.2)
 logging.debug('Exiting')
def my_service():
 logging.debug('Starting')
 time.sleep(0.3)
 logging.debug('Exiting')
logging.basicConfig(
 level=logging.DEBUG,
 format='[%(levelname)s] (%(threadName)-10s) %(message)s',
)
t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # use default name
w.start()
w2.start()
t.start()

而且logging是线程安全的,所以来自不同线程的消息在输出中会有所区分。

Python3标准库之threading进程中管理并发操作方法

1.3 守护与非守护线程

到目前为止,示例程序都在隐式地等待所有线程完成工作之后才退出。不过,程序有时会创建一个线程作为守护线程(daemon),这个线程可以一直运行而不阻塞主程序退出。

如果一个服务不能很容易地中断线程,或者即使让线程工作到一半时中止也不会造成数据损失或破坏(例如,为一个服务监控工具生成“心跳”的线程),那么对于这些服务,使用守护线程就很有用。要标志一个线程为守护线程,构造线程时便要传入daemon=True或者要调用它的setDaemon()方法并提供参数True。默认情况下线程不作为守护线程。

import threading
import time
import logging
def daemon():
 logging.debug('Starting')
 time.sleep(0.2)
 logging.debug('Exiting')
def non_daemon():
 logging.debug('Starting')
 logging.debug('Exiting')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
d = threading.Thread(name='daemon', target=daemon, daemon=True)
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()

这个代码的输出中不包含守护线程的“Exiting“消息,因为在从sleep()调用唤醒守护线程之前,所有非守护线程(包括主线程)已经退出。

Python3标准库之threading进程中管理并发操作方法

要等待一个守护线程完成工作,需要使用join()方法。

import threading
import time
import logging
def daemon():
 logging.debug('Starting')
 time.sleep(0.2)
 logging.debug('Exiting')
def non_daemon():
 logging.debug('Starting')
 logging.debug('Exiting')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
d = threading.Thread(name='daemon', target=daemon, daemon=True)
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join()
t.join()

使用join()等待守护线程退出意味着它有机会生成它的"Exiting"消息。

Python3标准库之threading进程中管理并发操作方法

默认地,join()会无限阻塞。或者,还可以传入一个浮点值,表示等待线程在多长时间(秒数)后变为不活动。即使线程在这个时间段内未完成,join()也会返回。

import threading
import time
import logging
def daemon():
 logging.debug('Starting')
 time.sleep(0.2)
 logging.debug('Exiting')
def non_daemon():
 logging.debug('Starting')
 logging.debug('Exiting')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
d = threading.Thread(name='daemon', target=daemon, daemon=True)
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join(0.1)
print('d.isAlive()', d.isAlive())
t.join()

由于传人的超时时间小于守护线程睡眠的时间,所以join()返回之后这个线程仍是"活着"。

Python3标准库之threading进程中管理并发操作方法

1.4 枚举所有线程

没有必要为所有守护线程维护一个显示句柄来确保它们在退出主进程之前已经完成。

enumerate()会返回活动 Thread实例的一个列表。这个列表也包括当前线程,由于等待当前线程终止(join)会引入一种死锁情况,所以必须跳过。

import random
import threading
import time
import logging
def worker():
 """thread worker function"""
 pause = random.randint(1, 5) / 10
 logging.debug('sleeping %0.2f', pause)
 time.sleep(pause)
 logging.debug('ending')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
for i in range(3):
 t = threading.Thread(target=worker, daemon=True)
 t.start()
main_thread = threading.main_thread()
for t in threading.enumerate():
 if t is main_thread:
 continue
 logging.debug('joining %s', t.getName())
 t.join()

由于工作线程睡眠的时间量是随机的,所以这个程序的输出可能有变化。

Python3标准库之threading进程中管理并发操作方法

1.5 派生线程

开始时,Thread要完成一些基本初始化,然后调用其run()方法,这会调用传递到构造函数的目标函数。要创建Thread的一个子类,需要覆盖run()来完成所需的工作。

import threading
import logging
class MyThread(threading.Thread):
 def run(self):
 logging.debug('running')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
for i in range(5):
 t = MyThread()
 t.start()

run()的返回值将被忽略。

Python3标准库之threading进程中管理并发操作方法

由于传递到Thread构造函数的args和kwargs值保存在私有变量中(这些变量名都有前缀),所以不能很容易地从子类访问这些值。要向一个定制的线程类型传递参数,需要重新定义构造函数,将这些值保存在子类可见的一个实例属性中。

import threading
import logging
class MyThreadWithArgs(threading.Thread):
 def __init__(self, group=None, target=None, name=None,
  args=(), kwargs=None, *, daemon=None):
 super().__init__(group=group, target=target, name=name,
  daemon=daemon)
 self.args = args
 self.kwargs = kwargs
 def run(self):
 logging.debug('running with %s and %s',
  self.args, self.kwargs)
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
for i in range(5):
 t = MyThreadWithArgs(args=(i,), kwargs={'a': 'A', 'b': 'B'})
 t.start()

MyThreadwithArgs使用的API与Thread相同,不过类似于其他定制类,这个类可以轻松地修改构造函数方法,以取得更多参数或者与线程用途更直接相关的不同参数。

Python3标准库之threading进程中管理并发操作方法

1.6 定时器线程

有时出于某种原因需要派生Thread,Timer就是这样一个例子,Timer也包含在threading中。Timer在一个延迟之后开始工作,而且可以在这个延迟期间内的任意时刻被取消。

import threading
import time
import logging
def delayed():
 logging.debug('worker running')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
t1 = threading.Timer(0.3, delayed)
t1.setName('t1')
t2 = threading.Timer(0.3, delayed)
t2.setName('t2')
logging.debug('starting timers')
t1.start()
t2.start()
logging.debug('waiting before canceling %s', t2.getName())
time.sleep(0.2)
logging.debug('canceling %s', t2.getName())
t2.cancel()
logging.debug('done')

这个例子中,第二个定时器永远不会运行,看起来第一个定时器在主程序的其余部分完成之后还会运行。由于这不是一个守护线程,所以在主线程完成时其会隐式退出。

Python3标准库之threading进程中管理并发操作方法

1.7 线程间传送信号

尽管使用多线程的目的是并发地运行单独的操作,但有时也需要在两个或多个线程中同步操作。事件对象是实现线程间安全通信的一种简单方法。Event管理一个内部标志,调用者可以用set()和clear()方法控制这个标志。其他线程可以使用wait()暂停,直到这个标志被设置,可有效地阻塞进程直至允许这些线程继续。

import logging
import threading
import time
def wait_for_event(e):
 """Wait for the event to be set before doing anything"""
 logging.debug('wait_for_event starting')
 event_is_set = e.wait()
 logging.debug('event set: %s', event_is_set)
def wait_for_event_timeout(e, t):
 """Wait t seconds and then timeout"""
 while not e.is_set():
 logging.debug('wait_for_event_timeout starting')
 event_is_set = e.wait(t)
 logging.debug('event set: %s', event_is_set)
 if event_is_set:
 logging.debug('processing event')
 else:
 logging.debug('doing other work')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
e = threading.Event()
t1 = threading.Thread(
 name='block',
 target=wait_for_event,
 args=(e,),
)
t1.start()
t2 = threading.Thread(
 name='nonblock',
 target=wait_for_event_timeout,
 args=(e, 2),
)
t2.start()
logging.debug('Waiting before calling Event.set()')
time.sleep(0.3)
e.set()
logging.debug('Event is set')

wait()方法取一个参数,表示等待事件的时间(秒数),达到这个时间后就超时。它会返回一个布尔值,指示事件是否已设置,使调用者知道wait()为什么返回。可以对事件单独地使用is_set()方法而不必担心阻塞。

在这个例子中,wait_for_event_timeout()将检查事件状态而不会无限阻塞。wait_for_event()在wait()调用的位置阻塞,事件状态改变之前它不会返回。

Python3标准库之threading进程中管理并发操作方法

1.8 控制资源访问

除了同步线程操作,还有一点很重要,要能够控制对共享资源的访问,从而避免破坏或丢失数据。Python的内置数据结构(列表、字典等)是线程安全的,这是Python使用原子字节码来管理这些数据结构的一个副作用(更新过程中不会释放保护Python内部数据结构的全局解释器锁GIL(Global Interpreter Lock))。Python中实现的其他数据结构或更简单的类型(如整数和浮点数)则没有这个保护。要保证同时安全地访问一个对象,可以使用一个Lock对象。

import logging
import random
import threading
import time
class Counter:
 def __init__(self, start=0):
 self.lock = threading.Lock()
 self.value = start
 def increment(self):
 logging.debug('Waiting for lock')
 self.lock.acquire()
 try:
 logging.debug('Acquired lock')
 self.value = self.value + 1
 finally:
 self.lock.release()
def worker(c):
 for i in range(2):
 pause = random.random()
 logging.debug('Sleeping %0.02f', pause)
 time.sleep(pause)
 c.increment()
 logging.debug('Done')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
counter = Counter()
for i in range(2):
 t = threading.Thread(target=worker, args=(counter,))
 t.start()
logging.debug('Waiting for worker threads')
main_thread = threading.main_thread()
for t in threading.enumerate():
 if t is not main_thread:
 t.join()
logging.debug('Counter: %d', counter.value)

在这个例子中,worker()函数使一个Counter实例递增,这个实例管理着一个Lock,以避免两个线程同时改变其内部状态。如果没有使用Lock,就有可能丢失一次对value属性的修改。

Python3标准库之threading进程中管理并发操作方法

要确定是否有另一个线程请求这个锁而不影响当前线程,可以向acquire()的blocking参数传入False。在下一个例子中,worker()想要分别得到3次锁,并统计为得到锁而尝试的次数。与此同时,lock_holder()在占有和释放锁之间循环,每个状态会短暂暂停,以模拟负载情况。

import logging
import threading
import time
def lock_holder(lock):
 logging.debug('Starting')
 while True:
 lock.acquire()
 try:
 logging.debug('Holding')
 time.sleep(0.5)
 finally:
 logging.debug('Not holding')
 lock.release()
 time.sleep(0.5)
def worker(lock):
 logging.debug('Starting')
 num_tries = 0
 num_acquires = 0
 while num_acquires < 3:
 time.sleep(0.5)
 logging.debug('Trying to acquire')
 have_it = lock.acquire(0)
 try:
 num_tries += 1
 if have_it:
 logging.debug('Iteration %d: Acquired',
  num_tries)
 num_acquires += 1
 else:
 logging.debug('Iteration %d: Not acquired',
  num_tries)
 finally:
 if have_it:
 lock.release()
 logging.debug('Done after %d iterations', num_tries)
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
lock = threading.Lock()
holder = threading.Thread(
 target=lock_holder,
 args=(lock,),
 name='LockHolder',
 daemon=True,
)
holder.start()
worker = threading.Thread(
 target=worker,
 args=(lock,),
 name='Worker',
)
worker.start()

worker()需要超过3次迭代才能得到3次锁。

Python3标准库之threading进程中管理并发操作方法

1.8.1 再入锁

正常的Lock对象不能请求多次,即使是由同一个线程请求也不例外。如果同一个调用链中的多个函数访问一个锁,则可能会产生我们不希望的副作用。

import threading
lock = threading.Lock()
print('First try :', lock.acquire())
print('Second try:', lock.acquire(0))

在这里,对第二个acquire()调用给定超时值为0,以避免阻塞,因为锁已经被第一个调用获得。

Python3标准库之threading进程中管理并发操作方法

如果同一个线程的不同代码需要"重新获得"锁,那么在这种情况下要使用RLock。

import threading
lock = threading.RLock()
print('First try :', lock.acquire())
print('Second try:', lock.acquire(0))

与前面的例子相比,对代码唯一的修改就是用RLock替换Lock。

Python3标准库之threading进程中管理并发操作方法

1.8.2 锁作为上下文管理器

锁实现了上下文管理器API,并与with语句兼容。使用with则不再需要显式地获得和释放锁。

import threading
import logging
def worker_with(lock):
 with lock:
 logging.debug('Lock acquired via with')
def worker_no_with(lock):
 lock.acquire()
 try:
 logging.debug('Lock acquired directly')
 finally:
 lock.release()
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
lock = threading.Lock()
w = threading.Thread(target=worker_with, args=(lock,))
nw = threading.Thread(target=worker_no_with, args=(lock,))
w.start()
nw.start()

函数worker_with()worker_no_with()用等价的方式管理锁。

Python3标准库之threading进程中管理并发操作方法

1.9 同步线程

除了使用Event,还可以通过使用一个Condition对象来同步线程。由于Condition使用了一个Lock,所以它可以绑定到一个共享资源,允许多个线程等待资源更新。在下一个例子中,consumer()线程要等待设置了Condition才能继续。producer()线程负责设置条件,以及通知其他线程继续。

import logging
import threading
import time
def consumer(cond):
 """wait for the condition and use the resource"""
 logging.debug('Starting consumer thread')
 with cond:
 cond.wait()
 logging.debug('Resource is available to consumer')
def producer(cond):
 """set up the resource to be used by the consumer"""
 logging.debug('Starting producer thread')
 with cond:
 logging.debug('Making resource available')
 cond.notifyAll()
logging.basicConfig(
 level=logging.DEBUG,
 format='%(asctime)s (%(threadName)-2s) %(message)s',
)
condition = threading.Condition()
c1 = threading.Thread(name='c1', target=consumer,
  args=(condition,))
c2 = threading.Thread(name='c2', target=consumer,
  args=(condition,))
p = threading.Thread(name='p', target=producer,
  args=(condition,))
c1.start()
time.sleep(0.2)
c2.start()
time.sleep(0.2)
p.start()

这些线程使用with来获得与Condition关联的锁。也可以显式地使用acquire()和release()方法。

Python3标准库之threading进程中管理并发操作方法

屏障(barrier)是另一种线程同步机制。Barrier会建立一个控制点,所有参与线程会在这里阻塞,直到所有这些参与“方”都到达这一点。采用这种方法,线程可以单独启动然后暂停,直到所有线程都准备好才可以继续。

import threading
import time
def worker(barrier):
 print(threading.current_thread().name,
 'waiting for barrier with {} others'.format(
 barrier.n_waiting))
 worker_id = barrier.wait()
 print(threading.current_thread().name, 'after barrier',
 worker_id)
NUM_THREADS = 3
barrier = threading.Barrier(NUM_THREADS)
threads = [
 threading.Thread(
 name='worker-%s' % i,
 target=worker,
 args=(barrier,),
 )
 for i in range(NUM_THREADS)
]
for t in threads:
 print(t.name, 'starting')
 t.start()
 time.sleep(0.1)
for t in threads:
 t.join()

在这个例子中,Barrier被配置为会阻塞线程,直到3个线程都在等待。满足这个条件时,所有线程被同时释放从而越过这个控制点。wait()的返回值指示了释放的参与线程数,可以用来限制一些线程做清理资源等动作。

Python3标准库之threading进程中管理并发操作方法

Barrier的abort()方法会使所有等待线程接收一个BrokenBarrierError。如果线程在wait()上被阻塞而停止处理,这就允许线程完成清理工作。

import threading
import time
def worker(barrier):
 print(threading.current_thread().name,
 'waiting for barrier with {} others'.format(
 barrier.n_waiting))
 try:
 worker_id = barrier.wait()
 except threading.BrokenBarrierError:
 print(threading.current_thread().name, 'aborting')
 else:
 print(threading.current_thread().name, 'after barrier',
 worker_id)
NUM_THREADS = 3
barrier = threading.Barrier(NUM_THREADS + 1)
threads = [
 threading.Thread(
 name='worker-%s' % i,
 target=worker,
 args=(barrier,),
 )
 for i in range(NUM_THREADS)
]
for t in threads:
 print(t.name, 'starting')
 t.start()
 time.sleep(0.1)
barrier.abort()
for t in threads:
 t.join()

这个例子将Barrier配置为多加一个线程,即需要比实际启动的线程再多一个参与线程,所以所有线程中的处理都会阻塞。在被阻塞的各个线程中,abort()调用会产生一个异常。

Python3标准库之threading进程中管理并发操作方法

1.10 限制资源的并发访问

有时可能需要允许多个工作线程同时访问一个资源,但要限制总数。例如,连接池支持同时连接,但数目可能是固定的,或者一个网络应用可能支持固定数目的并发下载。这些连接就可以使用Semaphore来管理。

import logging
import threading
import time
class ActivePool:
 def __init__(self):
 super(ActivePool, self).__init__()
 self.active = []
 self.lock = threading.Lock()
 def makeActive(self, name):
 with self.lock:
 self.active.append(name)
 logging.debug('Running: %s', self.active)
 def makeInactive(self, name):
 with self.lock:
 self.active.remove(name)
 logging.debug('Running: %s', self.active)
def worker(s, pool):
 logging.debug('Waiting to join the pool')
 with s:
 name = threading.current_thread().getName()
 pool.makeActive(name)
 time.sleep(0.1)
 pool.makeInactive(name)
logging.basicConfig(
 level=logging.DEBUG,
 format='%(asctime)s (%(threadName)-2s) %(message)s',
)
pool = ActivePool()
s = threading.Semaphore(2)
for i in range(4):
 t = threading.Thread(
 target=worker,
 name=str(i),
 args=(s, pool),
 )
 t.start()

在这个例子中,ActivePool类只作为一种便利方法,用来跟踪某个给定时刻哪些线程能够运行。真正的资源池会为新的活动线程分配一个连接或另外某个值,并且当这个线程工作完成时再回收这个值。在这里,资源池只是用来保存活动线程的名,以显示至少有两个线程在并发运行。

Python3标准库之threading进程中管理并发操作方法

1.11 线程特定的数据

有些资源需要锁定以便多个线程使用,另外一些资源则需要保护,以使它们对并非是这些资源的“所有者”的线程隐藏。local()函数会创建一个对象,它能够隐藏值,使其在不同线程中无法被看到。

import random
import threading
import logging
def show_value(data):
 try:
 val = data.value
 except AttributeError:
 logging.debug('No value yet')
 else:
 logging.debug('value=%s', val)
def worker(data):
 show_value(data)
 data.value = random.randint(1, 100)
 show_value(data)
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
local_data = threading.local()
show_value(local_data)
local_data.value = 1000
show_value(local_data)
for i in range(2):
 t = threading.Thread(target=worker, args=(local_data,))
 t.start()

属性local_data.value对所有线程都不可见,除非在某个线程中设置了这个属性,这个线程才能看到它。

Python3标准库之threading进程中管理并发操作方法

要初始化设置以使所有线程在开始时都有相同的值,可以使用一个子类,并在_init_()中设置这些属性。

import random
import threading
import logging
def show_value(data):
 try:
 val = data.value
 except AttributeError:
 logging.debug('No value yet')
 else:
 logging.debug('value=%s', val)
def worker(data):
 show_value(data)
 data.value = random.randint(1, 100)
 show_value(data)
class MyLocal(threading.local):
 def __init__(self, value):
 super().__init__()
 logging.debug('Initializing %r', self)
 self.value = value
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
local_data = MyLocal(1000)
show_value(local_data)
for i in range(2):
 t = threading.Thread(target=worker, args=(local_data,))
 t.start()

这会在相同的对象上调用_init_()(注意id()值),每个线程中调用一次以设置默认值。

Python3标准库之threading进程中管理并发操作方法

总结

到此这篇关于Python3标准库:threading进程中管理并发操作的文章就介绍到这了,更多相关Python3标准库:threading进程中管理并发操作内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python抓取京东商城手机列表url实例代码
Dec 18 Python
Python 2.7.x 和 3.x 版本的重要区别小结
Nov 28 Python
Python爬取读者并制作成PDF
Mar 10 Python
python基于BeautifulSoup实现抓取网页指定内容的方法
Jul 09 Python
Python3使用requests发闪存的方法
May 11 Python
python简单线程和协程学习心得(分享)
Jun 14 Python
利用python如何处理nc数据详解
May 23 Python
Python运维之获取系统CPU信息的实现方法
Jun 11 Python
python爬取微信公众号文章
Aug 31 Python
Python打开文件,将list、numpy数组内容写入txt文件中的方法
Oct 26 Python
python实现的生成word文档功能示例
Aug 23 Python
python GUI计算器的实现
Oct 09 Python
解决django xadmin主题不显示和只显示bootstrap2的问题
Mar 30 #Python
Python2 与Python3的版本区别实例分析
Mar 30 #Python
django xadmin中form_layout添加字段显示方式
Mar 30 #Python
基于virtualenv创建python虚拟环境过程图解
Mar 30 #Python
Python实现RabbitMQ6种消息模型的示例代码
Mar 30 #Python
Anconda环境下Vscode安装Python的方法详解
Mar 29 #Python
配置python的编程环境之Anaconda + VSCode的教程
Mar 29 #Python
You might like
Cannot modify header information错误解决方法
2008/10/08 PHP
php入门学习知识点五 关于php数组的几个基本操作
2011/07/14 PHP
web目录下不应该存在多余的程序(安全考虑)
2012/05/09 PHP
laravel实现按月或天或小时统计mysql数据的方法
2019/10/09 PHP
常用js脚本
2006/12/03 Javascript
JavaScript 学习笔记之变量及其作用域
2015/01/14 Javascript
js分页工具实例
2015/01/28 Javascript
jQuery解析XML与传统JavaScript方法的差别实例分析
2015/03/05 Javascript
javascript清空table表格的方法
2015/05/14 Javascript
JS建造者模式基本用法实例分析
2015/06/30 Javascript
jQuery+Ajax实现限制查询间隔的方法
2016/06/07 Javascript
input框中的name和id的区别
2016/11/16 Javascript
form+iframe解决跨域上传文件的方法
2016/11/18 Javascript
使用 Node.js 对文本内容分词和关键词抽取
2017/05/27 Javascript
JQuery模拟实现网页中自定义鼠标右键菜单功能
2018/11/14 jQuery
浅谈vue中$event理解和框架中在包含默认值外传参
2020/08/07 Javascript
vue element和nuxt的使用技巧分享
2021/01/14 Vue.js
Python实现查找字符串数组最长公共前缀示例
2019/03/27 Python
详解Python 切片语法
2019/06/10 Python
python 二维矩阵转三维矩阵示例
2019/11/30 Python
Python测试Kafka集群(pykafka)实例
2019/12/23 Python
基于pandas向csv添加新的行和列
2020/05/25 Python
python让函数不返回结果的方法
2020/06/22 Python
AmazeUI在模态框中嵌入表单形成模态输入框
2020/08/20 HTML / CSS
Notino意大利:购买香水和化妆品
2018/11/14 全球购物
波兰家居饰品和厨房配件网上商店:Maleomi
2020/12/15 全球购物
主管职责范文
2013/11/09 职场文书
2013年保送生自荐信格式
2013/11/20 职场文书
年终总结会主持词
2014/03/25 职场文书
学雷锋活动总结范文
2014/04/25 职场文书
设计顾问服务计划书
2014/05/04 职场文书
道德演讲稿
2014/05/21 职场文书
产品售后服务承诺书
2014/05/21 职场文书
优秀员工事迹材料
2014/12/20 职场文书
旅行社计调工作总结
2015/08/12 职场文书
小学班主任培训心得体会
2016/01/07 职场文书