Python操作rabbitmq 实践笔记
发布/订阅 系统
1.基本用法
生产者
import pika
import sys username = 'wt' #指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
chan = s_conn.channel() #在连接上创建一个频道 chan.queue_declare(queue='hello') #声明一个队列,生产者和消费者都要声明一个相同的队列,用来防止万一某一方挂了,另一方能正常运行
chan.basic_publish(exchange='', #交换机
routing_key='hello',#路由键,写明将消息发往哪个队列,本例是将消息发往队列hello
body='hello world')#生产者要发送的消息
print("[生产者] send 'hello world") s_conn.close()#当生产者发送完消息后,可选择关闭连接 输出:
[生产者] send 'hello world
消费者
import pika username = 'wt'#指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
chan = s_conn.channel()#在连接上创建一个频道 chan.queue_declare(queue='hello')#声明一个队列,生产者和消费者都要声明一个相同的队列,用来防止万一某一方挂了,另一方能正常运行 def callback(ch,method,properties,body): #定义一个回调函数,用来接收生产者发送的消息
print("[消费者] recv %s" % body) chan.basic_consume(callback, #调用回调函数,从队列里取消息
queue='hello',#指定取消息的队列名
no_ack=True) #取完一条消息后,不给生产者发送确认消息,默认是False的,即 默认给rabbitmq发送一个收到消息的确认,一般默认即可
print('[消费者] waiting for msg .')
chan.start_consuming()#开始循环取消息 输出:
[消费者] waiting for msg .
[消费者] recv b'hello world'
2. 实现功能:(1)rabbitmq循环调度,将消息循环发送给不同的消费者,如:消息1,3,5发送给消费者1;消息2,4,6发送给消费者2。
(2)消息确认机制,为了确保一个消息不会丢失,RabbitMQ支持消息的确认 , 一个 ack(acknowlegement) 是从消费者端发送一个确认去告诉RabbitMQ 消息已经接收了、处理了,RabbitMQ可以释放并删除掉了。如果一个消费者死掉了(channel关闭、connection关闭、或者TCP连接断开了)而没有发送ack,RabbitMQ 就会认为这个消息没有被消费者处理,并会重新发送到生产者的队列里,如果同时有另外一个消费者在线,rabbitmq将会将消息很快转发到另外一个消费者中。 那样的话你就能确保虽然一个消费者死掉,但消息不会丢失。
这个是没有超时的,当消费方(consumer)死掉后RabbitMQ会重新转发消息,即使处理这个消息需要很长很长时间也没有问题。消息的 acknowlegments 默认是打开的,在前面的例子中关闭了: no_ack = True . 现在删除这个标识 然后 发送一个 acknowledgment。
(3)消息持久化,将消息写入硬盘中。 RabbitMQ不允许你重新定义一个已经存在、但属性不同的queue。需要标记消息为持久化的 - 要通过设置 delivery_mode 属性为 2来实现。
消息持久化的注意点:
标记消息为持久化并不能完全保证消息不会丢失,尽管已经告诉RabbitMQ将消息保存到磁盘,但RabbitMQ接收到的消息在还没有保存的时候,仍然有一个短暂的时间窗口。RabbitMQ不会对每个消息都执行同步 --- 可能只是保存到缓存cache还没有写入到磁盘中。因此这个持久化保证并不是很强,但这比我们简单的任务queue要好很多,如果想要很强的持久化保证,可以使用 publisher confirms。
(4)公平调度。在一个消费者未处理完一个消息之前不要分发新的消息给它,而是将这个新消息分发给另一个不是很忙的消费者进行处理。为了解决这个问题我们可以在消费者代码中使用 channel.basic.qos ( prefetch_count = 1 ),将消费者设置为公平调度。
生产者
import pika
import sys username = 'wt' #指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道 channel.queue_declare(queue='task_queue', durable=True) #创建一个新队列task_queue,设置队列持久化,注意不要跟已存在的队列重名,否则有报错 message = "Hello World"
channel.basic_publish(exchange='',
routing_key='worker',#写明将消息发送给队列worker
body=message, #要发送的消息
properties=pika.BasicProperties(delivery_mode=2,)#设置消息持久化,将要发送的消息的属性标记为2,表示该消息要持久化
)
print(" [生产者] Send %r " % message)
消费者
import pika
import time username = 'wt'#指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel()#在连接上创建一个频道 channel.queue_declare(queue='task_queue', durable=True) #创建一个新队列task_queue,设置队列持久化,注意不要跟已存在的队列重名,否则有报错 def callback(ch, method, properties, body):
print(" [消费者] Received %r" % body)
time.sleep(1)
print(" [消费者] Done")
ch.basic_ack(delivery_tag=method.delivery_tag)# 接收到消息后会给rabbitmq发送一个确认 channel.basic_qos(prefetch_count=1) # 消费者给rabbitmq发送一个信息:在消费者处理完消息之前不要再给消费者发送消息 channel.basic_consume(callback,
queue='worker',
#这里就不用再写no_ack=False了
)
channel.start_consuming()
3.交换机
exchange:交换机。生产者不是将消息发送给队列,而是将消息发送给交换机,由交换机决定将消息发送给哪个队列。所以exchange必须准确知道消息是要送到哪个队列,还是要被丢弃。因此要在exchange中给exchange定义规则,所有的规则都是在exchange的类型中定义的。
exchange有4个类型:direct, topic, headers ,fanout
之前,我们并没有讲过exchange,但是我们仍然可以将消息发送到队列中。这是因为我们用的是默认exchange.也就是说之前写的:exchange='',空字符串表示默认的exchange。
之前的代码结构:
1 channel.basic_publish(exchange='',
2 routing_key='hello',
3 body=message)
exchange = '参数'
参数表示exchange 的名字,空字符串是默认或者没有exchange。消息被路由到某队列的根据是:routing_key.。如果routing_key的值存在的话。
现在,我们可以用我们自己命名的exchange来代替默认的exchange。
1 channel.basic_publish(exchange='logs',#自己命名exchange为logs
2 routing_key='',
3 body=message)
(1)fanout:广播类型,生产者将消息发送给所有消费者,如果某个消费者没有收到当前消息,就再也收不到了(消费者就像收音机)
生产者:(可以用作日志收集系统)
1 import pika
2 import sys
3 username = 'wt' #指定远程rabbitmq的用户名密码
4 pwd = ''
5 user_pwd = pika.PlainCredentials(username, pwd)
6 s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
7 channel = s_conn.channel() #在连接上创建一个频道
8 channel.exchange_declare(exchange='logs',
9 type='fanout')#创建一个fanout(广播)类型的交换机exchange,名字为logs。
10
11 message = "info: Hello World!"
12 channel.basic_publish(exchange='logs',#指定交换机exchange为logs,这里只需要指定将消息发给交换机logs就可以了,不需要指定队列,因为生产者消息是发送给交换机的。
13 routing_key='',#在fanout类型中,绑定关键字routing_key必须忽略,写空即可
14 body=message)
15 print(" [x] Sent %r" % message)
16 connection.close()
消费者:
import pika
import sys username = 'wt' #指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道 channel.exchange_declare(exchange='logs',
type='fanout')#消费者需再次声明一个exchange 以及类型。 result = channel.queue_declare(exclusive=True)#创建一个队列,exclusive=True(唯一性)表示在消费者与rabbitmq断开连接时,该队列会自动删除掉。
queue_name = result.method.queue#因为rabbitmq要求新队列名必须是与现存队列名不同,所以为保证队列的名字是唯一的,method.queue方法会随机创建一个队列名字,如:‘amq.gen-JzTY20BRgKO-HjmUJj0wLg‘。 channel.queue_bind(exchange='logs',
queue=queue_name)#将交换机logs与接收消息的队列绑定。表示生产者将消息发给交换机logs,logs将消息发给随机队列queue,消费者在随机队列queue中取消息 print(' [消费者] Waiting for logs. To exit press CTRL+C') def callback(ch, method, properties, body):
print(" [消费者] %r" % body) channel.basic_consume(callback,#调用回调函数从queue中取消息
queue=queue_name,
no_ack=True)#设置为消费者不给rabbitmq回复确认。 channel.start_consuming()#循环等待接收消息。
这样,开启多个消费者后,会同时从生产者接收相同的消息。
(2)direct:关键字类型。功能:交换机根据生产者消息中含有的不同的关键字将消息发送给不同的队列,消费者根据不同的关键字从不同的队列取消息
生产者:不用创建对列
import pika
import sys username = 'wt' #指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道 channel.exchange_declare(exchange='direct_logs',
type='direct')#创建一个交换机并声明exchange的类型为:关键字类型,表示该交换机会根据消息中不同的关键字将消息发送给不同的队列 severity = 'info'#severity这里只能为一个字符串,这里为‘info’表明本生产者只将下面的message发送到info队列中,消费者也只能从info队列中接收info消息
message = 'Hello World!'
channel.basic_publish(exchange='direct_logs',#指明用于发布消息的交换机、关键字
routing_key=severity,#绑定关键字,即将message与关键字info绑定,明确将消息发送到哪个关键字的队列中。
body=message)
print(" [生产者] Sent %r:%r" % (severity, message))
connection.close()
消费者:
import pika
import sys username = 'wt' #指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道 channel.exchange_declare(exchange='direct_logs',
type='direct')#创建交换机,命名为‘direct_logs’并声明exchange类型为关键字类型。 result = channel.queue_declare(exclusive=True)#创建随机队列,当消费者与rabbitmq断开连接时,这个队列将自动删除。
queue_name = result.method.queue#分配随机队列的名字。 severities = ['info','err']#可以接收绑定关键字info或err的消息,列表中也可以只有一个
if not severities:#判断如果输入有误,输出用法
sys.stderr.write("Usage: %s [info] [warning] [error]\n" % sys.argv[0])
sys.exit(1) for severity in severities:
channel.queue_bind(exchange='direct_logs',#将交换机、队列、关键字绑定在一起,使消费者只能根据关键字从不同队列中取消息
queue=queue_name,
routing_key=severity)#该消费者绑定的关键字。 print(' [消费者] Waiting for logs. To exit press CTRL+C') def callback(ch, method, properties, body):#定义回调函数,接收消息
print(" [消费者] %r:%r" % (method.routing_key, body)) channel.basic_consume(callback,
queue=queue_name,
no_ack=True)#消费者接收消息后,不给rabbimq回执确认。 channel.start_consuming()#循环等待消息接收。
(3)topics:模糊匹配类型。比较常用
生产者:
import pika
import sys username = 'wt' #指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel() #在连接上创建一个频道 channel.exchange_declare(exchange='topic_logs',
type='topic') # 创建模糊匹配类型的exchange。。 routing_key = '[warn].kern'##这里关键字必须为点号隔开的单词,以便于消费者进行匹配。引申:这里可以做一个判断,判断产生的日志是什么级别,然后产生对应的routing_key,使程序可以发送多种级别的日志
message = 'Hello World!'
channel.basic_publish(exchange='topic_logs',#将交换机、关键字、消息进行绑定
routing_key=routing_key, # 绑定关键字,将队列变成[warn]日志的专属队列
body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
s_conn.close()
消费者:
import pika
import sys username = 'wt'#指定远程rabbitmq的用户名密码
pwd = ''
user_pwd = pika.PlainCredentials(username, pwd)
s_conn = pika.BlockingConnection(pika.ConnectionParameters('192.168.1.240', credentials=user_pwd))#创建连接
channel = s_conn.channel()#在连接上创建一个频道 channel.exchange_declare(exchange='topic_logs',
type='topic') # 声明exchange的类型为模糊匹配。 result = channel.queue_declare(exclusive=True) # 创建随机一个队列当消费者退出的时候,该队列被删除。
queue_name = result.method.queue # 创建一个随机队列名字。 binding_keys = ['[warn]', 'info.*']#绑定键。‘#’匹配所有字符,‘*’匹配一个单词。这里列表中可以为一个或多个条件,能通过列表中字符匹配到的消息,消费者都可以取到
if not binding_keys:
sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
sys.exit(1) for binding_key in binding_keys:#通过循环绑定多个“交换机-队列-关键字”,只要消费者在rabbitmq中能匹配到与关键字相应的队列,就从那个队列里取消息
channel.queue_bind(exchange='topic_logs',
queue=queue_name,
routing_key=binding_key) print(' [*] Waiting for logs. To exit press CTRL+C') def callback(ch, method, properties, body):
print(" [x] %r:%r" % (method.routing_key, body)) channel.basic_consume(callback,
queue=queue_name,
no_ack=True)#不给rabbitmq发送确认 channel.start_consuming()#循环接收消息
4.远程过程调用(RPC)Remote procedure call
RPC执行过程:
代码:
- #!/usr/bin/env python
- import pika
- connection = pika.BlockingConnection(pika.ConnectionParameters(
- host='localhost'))
- channel = connection.channel()
- channel.queue_declare(queue='rpc_queue')
- def fib(n):
- if n == 0:
- return 0
- elif n == 1:
- return 1
- else:
- return fib(n-1) + fib(n-2)
- def on_request(ch, method, props, body):
- n = int(body)
- print(" [.] fib(%s)" % n)
- response = fib(n)
- ch.basic_publish(exchange='',
- routing_key=props.reply_to,
- properties=pika.BasicProperties(correlation_id = props.correlation_id),
- body=str(response))
- ch.basic_ack(delivery_tag = method.delivery_tag)
- channel.basic_qos(prefetch_count=1)
- channel.basic_consume(on_request, queue='rpc_queue')
- print(" [x] Awaiting RPC requests")
- channel.start_consuming()
- #!/usr/bin/env python
- import pika
- import uuid
- class FibonacciRpcClient(object):
- def __init__(self):
- self.connection = pika.BlockingConnection(pika.ConnectionParameters(
- host='localhost'))
- self.channel = self.connection.channel()
- result = self.channel.queue_declare(exclusive=True)
- self.callback_queue = result.method.queue
- self.channel.basic_consume(self.on_response, no_ack=True,
- queue=self.callback_queue)
- def on_response(self, ch, method, props, body):
- if self.corr_id == props.correlation_id:
- self.response = body
- def call(self, n):
- self.response = None
- self.corr_id = str(uuid.uuid4())
- self.channel.basic_publish(exchange='',
- routing_key='rpc_queue',
- properties=pika.BasicProperties(
- reply_to = self.callback_queue,
- correlation_id = self.corr_id,
- ),
- body=str(n))
- while self.response is None:
- self.connection.process_data_events()
- return int(self.response)
- fibonacci_rpc = FibonacciRpcClient()
- print(" [x] Requesting fib(30)")
- response = fibonacci_rpc.call(30)
- print(" [.] Got %r" % response)
或者 看下边一篇好理解一点
前面的例子都有个共同点,就是发送端发送消息出去后没有结果返回。如果只是单纯发送消息,当然没有问题了,但是在实际中,常常会需要接收端将收到的消息进行处理之后,返回给发送端。
处理方法描述:发送端在发送信息前,产生一个接收消息的临时队列,该队列用来接收返回的结果。其实在这里接收端、发送端的概念已经比较模糊了,因为发送端也同样要接收消息,接收端同样也要发送消息,所以这里笔者使用另外的示例来演示这一过程。
示例内容:假设有一个控制中心和一个计算节点,控制中心会将一个自然数N发送给计算节点,计算节点将N值加1后,返回给控制中心。这里用center.py模拟控制中心,compute.py模拟计算节点。
compute.py代码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
#!/usr/bin/env python #coding=utf8 import pika #连接rabbitmq服务器 connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) channel = connection.channel() #定义队列 channel.queue_declare(queue = 'compute_queue' ) print ' [*] Waiting for n' #将n值加1 def increase(n): return n + 1 #定义接收到消息的处理方法 def request(ch, method, properties, body): print " [.] increase(%s)" % (body,) response = increase( int (body)) #将计算结果发送回控制中心 ch.basic_publish(exchange = '', routing_key = properties.reply_to, body = str (response)) ch.basic_ack(delivery_tag = method.delivery_tag) channel.basic_qos(prefetch_count = 1 ) channel.basic_consume(request, queue = 'compute_queue' ) channel.start_consuming() |
计算节点的代码比较简单,值得一提的是,原来的接收方法都是直接将消息打印出来,这边进行了加一的计算,并将结果发送回控制中心。
center.py代码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
#!/usr/bin/env python #coding=utf8 import pika class Center( object ): def __init__( self ): self .connection = pika.BlockingConnection(pika.ConnectionParameters( host = 'localhost' )) self .channel = self .connection.channel() #定义接收返回消息的队列 result = self .channel.queue_declare(exclusive = True ) self .callback_queue = result.method.queue self .channel.basic_consume( self .on_response, no_ack = True , queue = self .callback_queue) #定义接收到返回消息的处理方法 def on_response( self , ch, method, props, body): self .response = body def request( self , n): self .response = None #发送计算请求,并声明返回队列 self .channel.basic_publish(exchange = '', routing_key = 'compute_queue' , properties = pika.BasicProperties( reply_to = self .callback_queue, ), body = str (n)) #接收返回的数据 while self .response is None : self .connection.process_data_events() return int ( self .response) center = Center() print " [x] Requesting increase(30)" response = center.request( 30 ) print " [.] Got %r" % (response,) |
上例代码定义了接收返回数据的队列和处理方法,并且在发送请求的时候将该队列赋值给reply_to,在计算节点代码中就是通过这个参数来获取返回队列的。
打开两个终端,一个运行代码python compute.py,另外一个终端运行center.py,如果执行成功,应该就能看到效果了。
笔者在测试的时候,出了些小问题,就是在center.py发送消息时没有指明返回队列,结果compute.py那边在计算完结果要发回数据时报错,提示routing_key不存在,再次运行也报错。用rabbitmqctl list_queues查看队列,发现compute_queue队列有1条数据,每次重新运行compute.py的时候,都会重新处理这条数据。后来使用/etc/init.d/rabbitmq-server restart重新启动下rabbitmq就ok了。
参考文章:http://www.rabbitmq.com/tutorials/tutorial-six-python.html
Python操作rabbitmq 实践笔记的更多相关文章
- Python操作RabbitMQ
RabbitMQ介绍 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现的产品,RabbitMQ是一个消息代理,从“生产者”接收消息并传递消 ...
- Python之路【第九篇】:Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy
Python之路[第九篇]:Python操作 RabbitMQ.Redis.Memcache.SQLAlchemy Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用 ...
- python - 操作RabbitMQ
python - 操作RabbitMQ 介绍 RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议.MQ全称为Mess ...
- 文成小盆友python-num12 Redis发布与订阅补充,python操作rabbitMQ
本篇主要内容: redis发布与订阅补充 python操作rabbitMQ 一,redis 发布与订阅补充 如下一个简单的监控模型,通过这个模式所有的收听者都能收听到一份数据. 用代码来实现一个red ...
- Python之路第十二天,高级(4)-Python操作rabbitMQ
rabbitMQ RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. MQ全称为Message Queue, 消息队列(M ...
- python操作RabbitMQ(不错)
一.rabbitmq RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统.他遵循Mozilla Public License开源协议. MQ全称为Message Queue, 消息队列 ...
- Python菜鸟之路:Python基础-Python操作RabbitMQ
RabbitMQ简介 rabbitmq中文翻译的话,主要还是mq字母上:Message Queue,即消息队列的意思.rabbitmq服务类似于mysql.apache服务,只是提供的功能不一样.ra ...
- Python操作 RabbitMQ、Redis、Memcache
Python操作 RabbitMQ.Redis.Memcache Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数 ...
- 使用python操作RabbitMQ,Redis,Memcache,SQLAlchemy 其二
一.概念 1.Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态 ...
随机推荐
- Linux服务器高并发实践经历
作为一个师父离职早的野生程序员,业务方面还可以达到忽悠别人的水平,但上升到性能层面那就是硬伤. 真实天上掉馅饼,公司分配了一个测试性能的任务,真是感觉我的天空星星都亮了. 高并发主要限制因素:CPU. ...
- MongoDB之分片集群与复制集
分片集群 1.1.概念 分片集群是将数据存储在多台机器上的操作,主要由查询路由mongos.分片.配置服务器组成. ●查询路由根据配置服务器上的元数据将请求分发到相应的分片上,本身不存储集群的元数据, ...
- docker搭建ros-indigo-arm交叉编译环境
ROS运行环境:ARM ubuntu14.04 + ROS indigo在arm环境下编译ros应用程序,速度极慢,无法忍受,尝试在x86机器上搭建docker+ros交叉编译环境. 交叉编译环境的搭 ...
- ruby不能识别中文的一个坑
需要安装readline,并重装ruby,挂readline编译. 后续发现总出错,然后发现brew包依赖不全,按照提示,一个一个的安装依赖.安装后使用rvm重装ruby就ok了. 多亏能够有办法上g ...
- 小巧数据库 Apache Derby 使用攻略
1. Derby 介绍 将目光放在小 Derby 的原因是纯绿色.轻巧.内存占用小,分分钟在你机子跑起来,自己做点需要连接数据库的代码实践非常方便. 虽然 Mysql 也可以,多一种选择,不是也挺好么 ...
- python基础-装饰器
一.什么是装饰器 装饰器本质就是函数,功能是为其他函数附加功能 二.装饰器遵循的原则 1.不修改被修饰函数的源代码 2.不修改被修饰函数的调用方式 三.实现装饰器的知识储备 装饰器=高阶函数+函数嵌套 ...
- 解决:error: Cannot fetch repo (TypeError: expected string or buffer)
同步源码,问题重现: Fetching project platform/external/libopus Fetching project repo error: Cannot fetch repo ...
- AutoVFL(适配)
1.添加约束(系统) a.一个约束(上下左右) +(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)att ...
- VisualStudio配色方案
最近发现一个很神奇的网站,可以方便的为VisualStudio配色:Studio Styles - Visual Studio color schemes 可以下载一份自己喜欢的配色方案 如果还不满意 ...
- NGUI 滑动特效之中间放大滚动
效果图如下: 其实很简单,在NGUI原有的滑动组件的基础上处理一下比例系数就好,每个块的位置是固定的,移动的是Panel. 所以呢用Panel的位置与块的位置做差在比几个块不就成了比例系数了么..自然 ...