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多线程操作实例
Nov 21 Python
Python中的浮点数原理与运算分析
Oct 12 Python
PyQt5每天必学之布局管理
Apr 19 Python
一条命令解决mac版本python IDLE不能输入中文问题
May 15 Python
解决pycharm回车之后不能换行或不能缩进的问题
Jan 16 Python
python文本数据处理学习笔记详解
Jun 17 Python
解决python文件双击运行秒退的问题
Jun 24 Python
基于Python实现船舶的MMSI的获取(推荐)
Oct 21 Python
tensorflow2.0保存和恢复模型3种方法
Feb 03 Python
Python基于time模块表示时间常用方法
Jun 18 Python
浅谈tensorflow使用张量时的一些注意点tf.concat,tf.reshape,tf.stack
Jun 23 Python
Python tkinter制作单机五子棋游戏
Sep 14 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
php图片上传存储源码并且可以预览
2011/08/26 PHP
Mysql的Root密码忘记,查看或修改的解决方法(图文介绍)
2013/06/14 PHP
php+mysql删除指定编号员工信息的方法
2015/01/14 PHP
WordPress中访客登陆实现邮件提醒的PHP脚本实例分享
2015/12/14 PHP
PHP实现SMTP邮件的发送实例
2018/09/27 PHP
XRegExp 0.2: Now With Named Capture
2007/11/30 Javascript
基于jquery的当鼠标滚轮到最底端继续加载新数据思路分享(多用于微博、空间、论坛 )
2011/10/10 Javascript
js中的caller和callee属性介绍和例子
2014/06/07 Javascript
JavaScript极简入门教程(一):基础篇
2014/10/25 Javascript
深入学习AngularJS中数据的双向绑定机制
2016/03/04 Javascript
使用 Javascript 实现浏览器推送提醒功能的示例
2017/11/03 Javascript
vue、react等单页面项目应该这样子部署到服务器
2018/01/03 Javascript
解决vue路由后界面没有变化,但是链接有的问题
2018/09/01 Javascript
JS拖拽排序插件Sortable.js用法实例分析
2019/02/20 Javascript
怎样使你的 JavaScript 代码简单易读(推荐)
2019/04/16 Javascript
Vue动态修改网页标题的方法及遇到问题
2019/06/09 Javascript
解决Echarts2竖直datazoom滑动后显示数据不全的问题
2020/07/20 Javascript
[03:20]次级联赛厮杀超职业 现超级兵对拆世纪大战
2014/10/30 DOTA
Python中针对函数处理的特殊方法
2014/03/06 Python
Python中的左斜杠、右斜杠(正斜杠和反斜杠)
2016/08/30 Python
Python中的Numpy矩阵操作
2018/08/12 Python
Python随机生成身份证号码及校验功能
2018/12/04 Python
python实现全盘扫描搜索功能的方法
2019/02/14 Python
TensorFlow卷积神经网络之使用训练好的模型识别猫狗图片
2019/03/14 Python
详解python编译器和解释器的区别
2019/06/24 Python
Python 3.6 中使用pdfminer解析pdf文件的实现
2019/09/25 Python
浅谈matplotlib 绘制梯度下降求解过程
2020/07/12 Python
Django数据模型中on_delete使用详解
2020/11/30 Python
用python对excel查重
2020/12/07 Python
香港彩色隐形眼镜在线商店:Stunninglens(全球免费送货)
2019/05/10 全球购物
什么是类的返射机制
2016/02/06 面试题
保安员岗位职责
2013/11/17 职场文书
利用javaScript处理常用事件详解
2021/04/14 Javascript
Pytorch 实现变量类型转换
2021/05/17 Python
sql查询结果列拼接成逗号分隔的字符串方法
2021/05/25 SQL Server
Vue过滤器(filter)实现及应用场景详解
2021/06/15 Vue.js