blog's People
blog's Issues
python中的元类(metaclass)是什么
Python中的元类(metaclass)是什么?
元类是什么?如何使用元类?
类对象
在理解元类之前,你需要掌握Python里的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.
在大多数语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
但是在Python中类也是对象.
是的,对象.
每当你用到关键字class
,Python就会执行它并且建立一个对象.例如:
>>> class ObjectCreator(object):
... pass
...
上面代码在内存里创建了名叫"ObjectCreator"的对象.
这个对象(类)有生成对象(实例)的能力,这就是为什么叫做类.
它是个对象,所以:
- 你可以把它赋值给一个变量
- 你可以赋值它
- 你可以给它添加属性
- 你个以作为函数参数来传递它
e.g.:
>>> print(ObjectCreator) # 你可以打印一个类,因为它是一个对象
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # 你可以把类作为参数传递
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # 可以给一个类添加属性
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # 可以把类赋值给一个变量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
动态创建类
因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。
首先,你可以在函数中创建类,使用class关键字即可:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # 返回一个类不是一个实例
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # 返回一个类不是一个实例
<class '__main__.Foo'>
>>> print(MyClass()) # 你可以在类里创建一个对象
<__main__.Foo object at 0x89c6d4c>
但这还不够动态,因为你仍然需要自己编写整个类的代码.
既然类是对象,那么肯定有什么东西来生成它.
当你使用关键字objects
,Python自动的创建对象.像Python中大多数的东西一样,他也给你自己动手的机会.
记得函数type
吗?这个古老好用的函数能让你知道对象的类型是什么:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
这里,type
有一种完全不同的能力,它也能动态的创建类.type
可以接受一个类的描述作为参数,然后返回一个类.
(我知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)
type
这样工作:
type(类名,
父类名的元组 (针对继承情况,可以为空),
包含属性的字典(名称和值))
e.g.:
>>> class MyShinyClass(object):
... pass
可以手动创建:
>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回类对象
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 创建一个类的实例
<__main__.MyShinyClass object at 0x8997cec>
你会发现我们使用“MyShinyClass”作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。
type
可以接受一个字典来定义类的属性:
>>> class Foo(object):
... bar = True
可以写成:
>>> Foo = type('Foo', (), {'bar':True})
然后我们可以像用正常类来用它:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
当然,你也可以继承它:
>>> class FooChild(Foo):
... pass
这样:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar从Foo继承
True
要是在类中添加方法,你要做的就是把函数名写入字典就可以了,不懂可以看下面:
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
你可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。
什么是元类(终于到正题了)
元类就是创建类的东西.
你是为了创建对象才定义类的,对吧?
但是我们已经知道了Python的类是对象.
这里,元类创建类.它们是类的类,你可以把它们想象成这样:
MyClass = MetaClass()
MyObject = MyClass()
你已经看到了type
可以让你像这样做:
MyClass = type('MyClass', (), {})
这是因为type
就是一个元类.type
是Python中创建所有类的元类.
现在你可能纳闷为啥子type
用小写而不写成Type
?
我想是因为要跟str
保持一致,str
创建字符串对象,int
创建整数对象.type
正好创建类对象.
你可以通过检查__class__
属性来看到这一点.
Python中所有的东西都是对象.包括整数,字符串,函数还有类.所有这些都是对象.所有这些也都是从类中创建的:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
那么,__class__
的__class__
属性是什么?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
所以,元类就是创建类对象的东西.
如果你愿意你也可以把它叫做'类工厂'.type
是Python的内建元类,当然,你也可以创建你自己的元类.
__metaclass__
属性
当你创建一个函数的时候,你可以添加__metaclass__
属性:
class Foo(object):
__metaclass__ = something...
[...]
如果你这么做了,Python就会用元类来创建类Foo.
小心点,这里面有些技巧.
你首先写下class Foo(object
,但是类对象Foo
还没有在内存中创建.
Python将会在类定义中寻找__metaclass__
.如果找打了就用它来创建类对象Foo
.如果没找到,就会默认用type
创建类.
把下面这段话反复读几次。
当你写如下代码时 :
class Foo(Bar):
pass
Python将会这样运行:
在Foo
中有没有___metaclass__
属性?
如果有,Python会在内存中通过__metaclass__
创建一个名字为Foo
的类对象(我说的是类对象,跟紧我的思路).
如果Python没有找到__metaclass__
,它会继续在Bar(父类)中寻找__metaclass__属性
,并尝试做和前面同样的操作.
如果Python在任何父类中都找不到__metaclass__
,它就会在模块层次中去寻找__metaclass__
,并尝试做同样的操作。
如果还是找不到__metaclass__
,Python就会用内置的type
来创建这个类对象。
现在的问题就是,你可以在__metaclass__
中放置些什么代码呢?
答案就是:可以创建一个类的东西。
那么什么可以用来创建一个类呢?type
,或者任何使用到type
或者子类化type
的东东都可以。
自定义元类
元类的主要目的就是为了当创建类时能够自动地改变类.
通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类.
假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__
.
采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。
幸运的是,__metaclass__
实际上可以被任意调用,它并不需要是一个正式的类(我知道,某些名字里带有'class'的东西并不需要是一个class,画画图理解下,这很有帮助)。
所以,我们这里就先以一个简单的函数作为例子开始。
# 元类会自动将你通常传给'type'的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
返回一个将属性列表变为大写字母的类对象
"""
# 选取所有不以'__'开头的属性,并把它们编程大写
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# 用'type'创建类
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # 将会影响整个模块
class Foo(): # global __metaclass__ won't work with "object" though
# 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
bar = 'bip'
print(hasattr(Foo, 'bar'))
# 输出: False
print(hasattr(Foo, 'BAR'))
# 输出: True
f = Foo()
print(f.BAR)
# 输出: 'bip'
现在让我们再做一次,这一次用一个真正的class来当做元类。
# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaclass(type):
# __new__ 是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回它的方法
# 而__init__只是用来将传入的参数初始化给对象
# 你很少用到__new__,除非你希望能够控制对象的创建
# 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
# 如果你希望的话,你也可以在__init__中做些事情
# 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)
但是这不是真正的面向对象(OOP).我们直接调用了type,而且我们没有改写父类的__new__方法。现在让我们这样去处理:
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
# 重用 type.__new__ 方法
# 这就是基本的OOP编程,没什么魔法
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)
你可能已经注意到了有个额外的参数upperattr_metaclass
,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self
参数一样。
当然了,为了清晰起见,这里的名字我起的比较长。但是就像self
一样,所有的参数都有它们的传统名称。因此,在真实的产品代码中一个元类应该是像这样的:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type.__new__(cls, clsname, bases, uppercase_attr)
如果使用super方法的话,我们还可以使它变得更清晰一些,这会缓解继承(是的,你可以拥有元类,从元类继承,从type继承)
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
就是这样,除此之外,关于元类真的没有别的可说的了。
使用到元类的代码比较复杂,这背后的原因倒并不是因为元类本身,而是因为你通常会使用元类去做一些晦涩的事情,依赖于自省,控制继承等等。
确实,用元类来搞些“黑暗魔法”是特别有用的,因而会搞出些复杂的东西来。但就元类本身而言,它们其实是很简单的:
- 拦截类的创建
- 修改一个类
- 返回修改之后的类
为什么要用metaclass类而不是函数?
由于__metaclass__
可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?
这里有好几个原因:
- 意图会更加清晰。当你读到
UpperAttrMetaclass(type)
时,你知道接下来要发生什么。 - 你可以使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。
- 你可以把代码组织的更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
- 你可以使用
__new__
,__init__
以及__call__
这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__
里处理掉,有些人还是觉得用__init__
更舒服些。 - 哇哦,这东西的名字是metaclass,肯定非善类,我要小心!
说了这么多TMD究竟为什么要使用元类?
现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?
好吧,一般来说,你根本就用不上它:
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters
元类的主要用途是创建API。一个典型的例子是Django ORM。
它允许你像这样定义:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是如果你像这样做的话:
guy = Person(name='bob', age='35')
print(guy.age)
这并不会返回一个IntegerField
对象,而是会返回一个int,甚至可以直接从数据库中取出数据。
这是有可能的,因为models.Model
定义了__metaclass__
, 并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。
Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。
结语
首先,你知道了类其实是能够创建出类实例的对象。
好吧,事实上,类本身也是实例,当然,它们是元类的实例。
>>> class Foo(object): pass
>>> id(Foo)
142630324
Python中的一切都是对象,它们要么是类的实例,要么是元类的实例.
除了type
.type
实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。
其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:
- monkey patching
- 装饰器
当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类 :D
原文:https://github.com/taizilongxu/stackoverflow-about-python
Ubuntu下Elasticsearch安装记录
单实例安装
新建用户elastic
# 新建用户elastic
adduser elastic
# 修改密码elastic123
passwd elastic
安装JAVA
更新源
sudo apt-get update
安装java
sudo apt-get install default-jre
查看java版本
java -version
输出如下:
openjdk version "1.8.0_171"
OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-0ubuntu0.16.04.1-b11)
OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)
每一台机器上的|JAVA版本必须要一致
下载Elasticsearch
# https://www.elastic.co/downloads/elasticsearch查询最新版本
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.2.4.tar.gz
解压缩并执行
tar -xvf elasticsearch-6.2.4.tar.gz
cd elasticsearch-6.2.4
#运行es,如要把 Elasticsearch 作为一个守护进程在后台运行,在后面添加参数 -d
./bin/elasticsearch
# 查看结果
curl 'http://localhost:9200/?pretty'
参考:https://elasticsearch.cn/book/elasticsearch_definitive_guide_2.x/running-elasticsearch.html
安装elasticsearch-head插件
# 安装nodejs
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - #添加8.x的源
sudo apt-get install -y nodejs #安装
# 安装npm
sudo apt-get install npm
# npm切换淘宝源
npm config set registry http://registry.npm.taobao.org/ #切换回官方源:npm config set registry https://registry.npmjs.org/
# 下载elasticsearch-head后解压并重命名
wget https://github.com/mobz/elasticsearch-head/archive/master.zip
unzip master
mv elasticsearch-head-master elasticsearch-head
cd elasticsearch-head
# 安装包
npm install
# 启动
npm run start #启动后可以访问http://localhost:9100查看结果
# 解决elasticsearch 与 elasticsearch-head之前跨域问题
# 切换到elasticsearch目录
vim config/elasticsearch.yml
在最后添加下面两行:
http.cors.enabled: true
http.cors.allow-origin: "*"
# 最后访问http://localhost:9100查看结果
分布式安装
vim config/elasticsearch.yml
#添加
cluster.name: MyCluster
常见错误
1.es不能用root账户运行
2.JVM虚拟机内存不足
错误:“JavaHotSpot(TM) 64-Bit Server VM warning: INFO: error='Cannotallocate memory' (errno=12)”表示内存不足,其配置文件为config目录下的jvm.options,默认为2g,可以修改为1g。
3.max_map_count过小
错误“max virtual memory areas vm.max_map_count [65530]is too low, increase to at least [262144]”,max_map_count文件包含限制一个进程可以拥有的VMA(虚拟内存区域)的数量,系统默认是65530,修改成262144。解决方法是修改/etc/sysctl.conf配置文件,添加vm.max_map_count=262144,重启机器
4.max file descriptors过小
错误“max file descriptors [65535] for elasticsearchprocess is too low, increase to at least [65536]”,maxfile descriptors为最大文件描述符,设置其大于65536即可。解决方法是修改/etc/security/limits.conf文件,添加“* - nofile65536 * - memlock unlimited”,“*”表示给所有用户起作用
root soft nofile 65536
root hard nofile 65536
elastic soft memlock unlimited
elastic hard memlock unlimited
elastic soft nofile 65536
elastic hard nofile 65536
参考:
https://blog.csdn.net/qq_21387171/article/details/53577115
https://segmentfault.com/a/1190000014891856
记录一些资源
python
- python豆瓣源,用法:
pip install -i https://pypi.douban.com/simple/ scrapy
https://github.com/digi604/django-smart-selects
- haipproxy使用 Scrapy+Redis 实现的高可用分布式 IP 代理池,为大型分布式爬虫提供高可用低延迟的代理 IP 资源。
- 利用Python进行数据分析 第二版 (2017) 中文翻译笔记
- records 一个支持大多数主流关系数据库的原生 SQL 查询第三方库
- Python 速查表
- requests-html
前端
7788
- App历史版本查询
- docsify 一个精简的 GitBook
- vim-galore Vim 从入门到精通
- Windows 绝赞应用
Asyncio
多线程和多进程,IO的调度更多取决于系统,而协程的方式,调度来自用户,用户可以在函数中yield一个状态,使用协程可以实现高效的并发任务。实现协程的不仅仅是asyncio,tornado和gevent都实现了类似的功能。
-
event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
-
coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
-
task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。
-
future: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别。
-
async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
定义一个协程
import time
import asyncio
now = lambda : time.time()
async def do_some_work(x):
print('Waiting: ', x)
start = now()
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
print('TIME: ', now() - start)
通过async关键字定义一个协程(coroutine),协程也是一种对象。协程不能直接运行,需要把协程加入到事件循环(loop),由后者在适当的时候调用协程。asyncio.get_event_loop方法可以创建一个事件循环,然后使用run_until_complete将协程注册到事件循环,并启动事件循环。
创建一个task
协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程包装成为了一个任务(task)对象。所谓task对象是Future类的子类。保存了协程运行后的状态,用于未来获取协程的结果。
import asyncio
import time
now = lambda : time.time()
async def do_some_work(x):
print('Waiting: ', x)
start = now()
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
# task = asyncio.ensure_future(coroutine)
task = loop.create_task(coroutine)
print(task)
loop.run_until_complete(task)
print(task)
print('TIME: ', now() - start)
创建task后,task在加入事件循环之前是pending状态,因为do_some_work中没有耗时的阻塞操作,task很快就执行完毕了。后面打印的finished状态。
asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task,run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task,task是Future的子类。isinstance(task, asyncio.Future)将会输出True。
绑定回调
绑定回调,在task执行完毕的时候可以获取执行的结果,回调的最后一个参数是future对象,通过该对象可以获取协程返回值。如果回调需要多个参数,可以通过偏函数导入。
import time
import asyncio
now = lambda : time.time()
async def do_some_work(x):
print('Waiting: ', x)
return 'Done after {}s'.format(x)
def callback(future):
print('Callback: ', future.result())
start = now()
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
task.add_done_callback(callback)
loop.run_until_complete(task)
print('TIME: ', now() - start)
可以看到,coroutine执行结束时候会调用回调函数。并通过参数future获取协程执行的结果。我们创建的task和回调里的future对象,实际上是同一个对象。
future 与 result
回调一直是很多异步编程的恶梦,程序员更喜欢使用同步的编写方式写异步代码,以避免回调的恶梦。回调中我们使用了future对象的result方法。前面不绑定回调的例子中,我们可以看到task有fiinished状态。在那个时候,可以直接读取task的result方法。
async def do_some_work(x):
print('Waiting {}'.format(x))
return 'Done after {}s'.format(x)
start = now()
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
print('Task ret: {}'.format(task.result()))
print('TIME: {}'.format(now() - start))
可以看到输出的结果:
Waiting: 2
Task ret: Done after 2s
TIME: 0.0003650188446044922
阻塞和await
使用async可以定义协程对象,使用await可以针对耗时的操作进行挂起,就像生成器里的yield一样,函数让出控制权。协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其他的协程也挂起或者执行完毕,再进行下一个协程的执行。
耗时的操作一般是一些IO操作,例如网络请求,文件读取等。我们使用asyncio.sleep函数来模拟IO操作。协程的目的也是让这些IO操作异步化。
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
start = now()
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)
print('Task ret: ', task.result())
print('TIME: ', now() - start)
在 sleep的时候,使用await让出控制权。即当遇到阻塞调用的函数的时候,使用await方法将协程的控制权让出,以便loop调用其他的协程。现在我们的例子就用耗时的阻塞操作了。
并发和并行
并发和并行一直是容易混淆的概念。并发通常指有多个任务需要同时进行,并行则是同一时刻有多个任务执行。用上课来举例就是,并发情况下是一个老师在同一时间段辅助不同的人功课。并行则是好几个老师分别同时辅助多个学生功课。简而言之就是一个人同时吃三个馒头还是三个人同时分别吃一个的情况,吃一个馒头算一个任务。
asyncio实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。创建多个协程的列表,然后将这些协程注册到事件循环中。
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
start = now()
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
for task in tasks:
print('Task ret: ', task.result())
print('TIME: ', now() - start)
结果如下:
Waiting: 1
Waiting: 2
Waiting: 4
Task ret: Done after 1s
Task ret: Done after 2s
Task ret: Done after 4s
TIME: 4.003541946411133
总时间为4s左右。4s的阻塞时间,足够前面两个协程执行完毕。如果是同步顺序的任务,那么至少需要7s。此时我们使用了aysncio实现了并发。asyncio.wait(tasks) 也可以使用 asyncio.gather(*tasks) ,前者接受一个task列表,后者接收一堆task。
协程嵌套
使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
async def main():
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
dones, pendings = await asyncio.wait(tasks)
for task in dones:
print('Task ret: ', task.result())
start = now()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
print('TIME: ', now() - start)
如果使用的是 asyncio.gather创建协程对象,那么await的返回值就是协程运行的结果。
results = await asyncio.gather(*tasks)
for result in results:
print('Task ret: ', result)
不在main协程函数里处理结果,直接返回await的内容,那么最外层的run_until_complete将会返回main协程的结果。
async def main():
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(2)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
return await asyncio.gather(*tasks)
start = now()
loop = asyncio.get_event_loop()
results = loop.run_until_complete(main())
for result in results:
print('Task ret: ', result)
或者返回使用asyncio.wait方式挂起协程。
async def main():
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
return await asyncio.wait(tasks)
start = now()
loop = asyncio.get_event_loop()
done, pending = loop.run_until_complete(main())
for task in done:
print('Task ret: ', task.result())
也可以使用asyncio的as_completed方法
async def main():
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
for task in asyncio.as_completed(tasks):
result = await task
print('Task ret: {}'.format(result))
start = now()
loop = asyncio.get_event_loop()
done = loop.run_until_complete(main())
print('TIME: ', now() - start)
协程停止
上面见识了协程的几种常用的用法,都是协程围绕着事件循环进行的操作。future对象有几个状态:
- Pending
- Running
- Done
- Cancelled
创建future的时候,task为pending,事件循环调用执行的时候当然就是running,调用完毕自然就是done,如果需要停止事件循环,就需要先把task取消。可以使用asyncio.Task获取事件循环的task
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(2)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
start = now()
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
print(asyncio.Task.all_tasks())
for task in asyncio.Task.all_tasks():
print(task.cancel())
loop.stop()
loop.run_forever()
finally:
loop.close()
print('TIME: ', now() - start)
启动事件循环之后,马上ctrl+c,会触发run_until_complete的执行异常 KeyBorardInterrupt。然后通过循环asyncio.Task取消future。可以看到输出如下:
Waiting: 1
Waiting: 2
Waiting: 2
{<Task pending coro=<do_some_work() running at...
True
True
True
True
TIME: 0.8858370780944824
True表示cannel成功,loop stop之后还需要再次开启事件循环,最后在close,不然还会抛出异常:
Task was destroyed but it is pending!
task: <Task pending coro=<do_some_work() done,
循环task,逐个cancel是一种方案,可是正如上面我们把task的列表封装在main函数中,main函数外进行事件循环的调用。这个时候,main相当于最外出的一个task,那么处理包装的main函数即可。
import asyncio
import time
now = lambda: time.time()
async def do_some_work(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
async def main():
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(2)
tasks = [
asyncio.ensure_future(coroutine1),
asyncio.ensure_future(coroutine2),
asyncio.ensure_future(coroutine3)
]
done, pending = await asyncio.wait(tasks)
for task in done:
print('Task ret: ', task.result())
start = now()
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(main())
try:
loop.run_until_complete(task)
except KeyboardInterrupt as e:
print(asyncio.Task.all_tasks())
print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())
loop.stop()
loop.run_forever()
finally:
loop.close()
不同线程的事件循环
很多时候,我们的事件循环用于注册协程,而有的协程需要动态的添加到事件循环中。一个简单的方式就是使用多线程。当前线程创建一个事件循环,然后在新建一个线程,在新线程中启动事件循环。当前线程不会被block。
from threading import Thread
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
def more_work(x):
print('More work {}'.format(x))
time.sleep(x)
print('Finished more work {}'.format(x))
start = now()
new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.start()
print('TIME: {}'.format(time.time() - start))
new_loop.call_soon_threadsafe(more_work, 6)
new_loop.call_soon_threadsafe(more_work, 3)
启动上述代码之后,当前线程不会被block,新线程中会按照顺序执行call_soon_threadsafe方法注册的more_work方法,后者因为time.sleep操作是同步阻塞的,因此运行完毕more_work需要大致6 + 3
新线程协程
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
async def do_some_work(x):
print('Waiting {}'.format(x))
await asyncio.sleep(x)
print('Done after {}s'.format(x))
def more_work(x):
print('More work {}'.format(x))
time.sleep(x)
print('Finished more work {}'.format(x))
start = now()
new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.start()
print('TIME: {}'.format(time.time() - start))
asyncio.run_coroutine_threadsafe(do_some_work(6), new_loop)
asyncio.run_coroutine_threadsafe(do_some_work(4), new_loop)
上述的例子,主线程中创建一个new_loop,然后在另外的子线程中开启一个无限事件循环。主线程通过run_coroutine_threadsafe新注册协程对象。这样就能在子线程中进行事件循环的并发操作,同时主线程又不会被block。一共执行的时间大概在6s左右。
master-worker主从模式
对于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg,worker用户处理消息。
为了简单起见,并且协程更适合单线程的方式,我们的主线程用来监听队列,子线程用于处理队列。这里使用redis的队列。主线程中有一个是无限循环,用户消费队列。
while True:
task = rcon.rpop("queue")
if not task:
time.sleep(1)
continue
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
给队列添加一些数据:
127.0.0.1:6379[3]> lpush queue 2
(integer) 1
127.0.0.1:6379[3]> lpush queue 5
(integer) 1
127.0.0.1:6379[3]> lpush queue 1
(integer) 1
127.0.0.1:6379[3]> lpush queue 1
可以看见输出:
Waiting 2
Done 2
Waiting 5
Waiting 1
Done 1
Waiting 1
Done 1
Done 5
我们发起了一个耗时5s的操作,然后又发起了连个1s的操作,可以看见子线程并发的执行了这几个任务,其中5s awati的时候,相继执行了1s的两个任务。
停止子线程
如果一切正常,那么上面的例子很完美。可是,需要停止程序,直接ctrl+c,会抛出KeyboardInterrupt错误,我们修改一下主循环:
try:
while True:
task = rcon.rpop("queue")
if not task:
time.sleep(1)
continue
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
except KeyboardInterrupt as e:
print(e)
new_loop.stop()
可是实际上并不好使,虽然主线程try了KeyboardInterrupt异常,但是子线程并没有退出,为了解决这个问题,可以设置子线程为守护线程,这样当主线程结束的时候,子线程也随机退出。
new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.setDaemon(True) # 设置子线程为守护线程
t.start()
try:
while True:
# print('start rpop')
task = rcon.rpop("queue")
if not task:
time.sleep(1)
continue
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
except KeyboardInterrupt as e:
print(e)
new_loop.stop()
线程停止程序的时候,主线程退出后,子线程也随机退出才了,并且停止了子线程的协程任务。
aiohttp
在消费队列的时候,我们使用asyncio的sleep用于模拟耗时的io操作。以前有一个短信服务,需要在协程中请求远程的短信api,此时需要是需要使用aiohttp进行异步的http请求。大致代码如下:
server.py
import time
from flask import Flask, request
app = Flask(__name__)
@app.route('/<int:x>')
def index(x):
time.sleep(x)
return "{} It works".format(x)
@app.route('/error')
def error():
time.sleep(3)
return "error!"
if __name__ == '__main__':
app.run(debug=True)
/接口表示短信接口,/error表示请求/失败之后的报警。
async-custoimer.py
import time
import asyncio
from threading import Thread
import redis
import aiohttp
def get_redis():
connection_pool = redis.ConnectionPool(host='127.0.0.1', db=3)
return redis.Redis(connection_pool=connection_pool)
rcon = get_redis()
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(resp.status)
return await resp.text()
async def do_some_work(x):
print('Waiting ', x)
try:
ret = await fetch(url='http://127.0.0.1:5000/{}'.format(x))
print(ret)
except Exception as e:
try:
print(await fetch(url='http://127.0.0.1:5000/error'))
except Exception as e:
print(e)
else:
print('Done {}'.format(x))
new_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(new_loop,))
t.setDaemon(True)
t.start()
try:
while True:
task = rcon.rpop("queue")
if not task:
time.sleep(1)
continue
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
except Exception as e:
print('error')
new_loop.stop()
finally:
pass
有一个问题需要注意,我们在fetch的时候try了异常,如果没有try这个异常,即使发生了异常,子线程的事件循环也不会退出。主线程也不会退出,暂时没找到办法可以把子线程的异常raise传播到主线程。(如果谁找到了比较好的方式,希望可以带带我)。
对于redis的消费,还有一个block的方法:
try:
while True:
_, task = rcon.brpop("queue")
asyncio.run_coroutine_threadsafe(do_some_work(int(task)), new_loop)
except Exception as e:
print('error', e)
new_loop.stop()
finally:
pass
使用 brpop方法,会block住task,如果主线程有消息,才会消费。测试了一下,似乎brpop的方式更适合这种队列消费的模型。
127.0.0.1:6379[3]> lpush queue 5
(integer) 1
127.0.0.1:6379[3]> lpush queue 1
(integer) 1
127.0.0.1:6379[3]> lpush queue 1
可以看到结果:
Waiting 5
Waiting 1
Waiting 1
200
1 It works
Done 1
200
1 It works
Done 1
200
5 It works
Done 5
协程消费
主线程用于监听队列,然后子线程的做事件循环的worker是一种方式。还有一种方式实现这种类似master-worker的方案。即把监听队列的无限循环逻辑一道协程中。程序初始化就创建若干个协程,实现类似并行的效果。
import time
import asyncio
import redis
now = lambda : time.time()
def get_redis():
connection_pool = redis.ConnectionPool(host='127.0.0.1', db=3)
return redis.Redis(connection_pool=connection_pool)
rcon = get_redis()
async def worker():
print('Start worker')
while True:
start = now()
task = rcon.rpop("queue")
if not task:
await asyncio.sleep(1)
continue
print('Wait ', int(task))
await asyncio.sleep(int(task))
print('Done ', task, now() - start)
def main():
asyncio.ensure_future(worker())
asyncio.ensure_future(worker())
loop = asyncio.get_event_loop()
try:
loop.run_forever()
except KeyboardInterrupt as e:
print(asyncio.gather(*asyncio.Task.all_tasks()).cancel())
loop.stop()
loop.run_forever()
finally:
loop.close()
if __name__ == '__main__':
main()
这样做就可以多多启动几个worker来监听队列。一样可以到达效果。
总结
上述简单的介绍了asyncio的用法,主要是理解事件循环,协程和任务,future的关系。异步编程不同于常见的同步编程,设计程序的执行流的时候,需要特别的注意。毕竟这和以往的编码经验有点不一样。可是仔细想想,我们平时处事的时候,大脑会自然而然的实现异步协程。比如等待煮茶的时候,可以多写几行代码。
Nginx
NGINX
安装
Ubuntu
- wget http://nginx.org/keys/nginx_signing.key
- sudo apt-key add nginx_signing.key
- vim /etc/apt/sources.list 添加nginx源
deb http://nginx.org/packages/ubuntu/ codename nginx
deb-src http://nginx.org/packages/ubuntu/ codename nginx
- apt-get update # 这一步可能遇到网络问题
- apt-get install nginx
或者:
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:nginx/stable
sudo apt-get update
sudo apt-get install nginx
或下载源码安装
安装目录
# Nginx日志轮转,用于 logrotate服务的日志切割
/etc/logrotate.d/nginx
# Nginx主配置文件
/etc/nginx
/etc/nginx/nginx.conf
/etc/nginx/conf.d
/etc/nginx/conf.d/default.conf
# cgi配置相关,fastcgi配置
/etc/nginx/fastcgi_params
/etc/nginx/scgi_params
/etc/nginx/uwsgi_params
# 编码转换映射转化文件
/etc/nginx/koi-utf
/etc/nginx/koi-win
/etc/nginx/win-utf
# 设置http协议的Content-Type与扩展名对应关系
/etc/nginx/mime.types
# 用于配置出系统守护进程管理器管理方式
/etc/sysconfig/nginx
/etc/sysconfig/nginx-debug
/usr/lib/systemd/system/nginx-debug.service
/usr/lib/systemd/system/nginx.service
# Nginx的模块目录
/etc/nginx/modules
/usr/lib64/nginx/modules
# Nginx服务启动管理的终端命令
/usr/sbin/nginx
/usr/sbin/nginx-debug
# Nginx的手册和帮助文件
/usr/share/doc/nginx-1.14.0
/usr/share/doc/nginx-1.14.0/COPYRIGHT
/usr/share/man/man8/nginx.8.gz
# Nginx的缓存目录
/var/cache/nginx
# Nginx的日志目录
/var/log/nginx
基本使用
# 启动
/usr/local/nginx/sbin/nginx
# 重启
/usr/local/nginx/sbin/nginx -s reload
Pipenv&Virtualenv
Pipenv
安装
pip install pipenv
使用
# 默认安装在c盘,如果需要更改到其他盘,需设置用户变量:WORKON_HOME,每次安装都需要设置
# set WORKON_HOME=D:\venv\pipenv
$export WORKON_HOME=/project/.venvs
$pipenv --three
# python2
pipenv --two
# python3
pipenv --three
# 系统默认python
pipenv install
# 换源 https://pypi.douban.com/simple/
vim Pipfile
# 或直接执行
sed -i s/pypi.python.org/pypi.doubanio.com/g Pipfile
# 切换到项目目录,进入环境
pipenv shell
# 安装包
pipenv install package_name
# 安装包
pipenv install -r requirements.txt
# 卸载包
pipenv uninstall
# 删除环境
pipenv --rm
# 在开发环境中安装包
pipenv install --dev package_name
# 生成requirements 文件:
pipenv lock -r [--dev] > requirements.txt
pipenv --help
Usage: pipenv [OPTIONS] COMMAND [ARGS]...
Options:
--update 升级 pipenv, pip 到最新.
--where 输出项目的目录信息.
--venv 输出 virtualenv 的目录信息.
--py 输出 Python 解析器的路径.
--envs 输出环境变量的设置.
--rm 删除当前 virtualenv.
--bare Minimal output.
--completion Output completion (to be evald).
--man 显示使用手册.
--three / --two 使用 Python 3/2 来创建 virtualenv
--python TEXT 直接指定 Python 解析器.
--site-packages 拷贝系统 site-packages 到 virtualenv.
--version 显示版本信息并退出.
-h, --help 显示当前信息并退出.
Commands:
check 检查安全漏洞和反对 PEP 508 标记在Pipfile提供.
graph 显示当前依赖关系图信息.
install 安装提供的包,并加入 Pipfile 的依赖清单中
lock 生成 Pipfile.lock.
open 在编辑器(vim)查看一个特定模块.
run 在 virtualenv 中执行命令.
shell 切换到 virtualenv 中.
uninstall 删除提供的包,并清理 Pipfile 的依赖清单中.
update 卸载当前所以依赖,然后安装最新包
- 安装pip3
cd /tmp && wget https://bootstrap.pypa.io/get-pip.py python3 get-pip.py
- 配置虚拟环境
mkdir venv && cd venv mkdir cloudmon && cd cloudmon rz Pipfile Pipfile.lock pipenv --python 3.5 pipenv install
Virtualenv
安装
sudo pip3 install virtualenv
创建环境,python指定版本
virtualenv env_name --python=python3.5
进入虚拟环境
source env_name/bin/activate
退出虚拟环境
deactivate
查看帮助
virtualenv --help
Redis
安装
sudo apt install redis-server
源码安装
下载
官网地址:https://redis.io/download
cd /usr/local/src
wget http://download.redis.io/releases/redis-4.0.11.tar.gz
安装
tar xzf redis-4.0.11.tar.gz
cd redis-4.0.11
make && make install
配置
cp redis.conf /etc/redis.conf
vim /etc/redis.conf
# 配置参数
# 这几个参数需要修改
daemonize yes #yes表示后台启动;no表示前台启动
logfile "/var/log/redis.log" #定义日志存放路径
dir /data/redis #定义rdb、aof文件的存放位置,需建立目录: mkdir -p /data/redis
appendonly yes #开启aof日志,开启后会在dir定义的目录生成appendonly.aof文件
requirepass pwd # 设置密码
# 这几个参数为以下默认值
pidfile /var/run/redis_6379.pid #pid存放位置
loglevel notice #指定日志级别:debug(调试,排错)、verbose(冗长的)、notice、warning
#debug适合排查错误时使用,错误排查完毕后要换到notice,避免产生大量日志浪费系统空间
databases 16 #Redis有库的概念,默认在0库
appendonly yes #开启aof日志,开启后会在dir定义的目录生成appendonly.aof文件
appendfsync everysec #指定记录日志的规则:always(只要有变动就记录)、everysec(每秒记录一次)、no(不记录)
创建rdb、aof文件的存放文件夹
[root@yt-01 redis-4.0.9]# mkdir /data/redis
启动redis
[root@yt-01 redis-4.0.9]# redis-server /etc/redis.conf
查看redis进程
ps ef|grep redis
查看redis日志
less /var/log/redis.log
# 3条warning,提醒我们有3个内核参数需要调整
……
WARNING:The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
WARNING:overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
WARNING:you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
* Ready to accept connections
/var/log/redis.log (END)
配置内核参数,消除警告
第一二警告,vim /etc/sysctl.conf添加以下内容,然后执行sysctl -p
#对于一个经常处理新连接的高负载 web服务环境来说,默认的 128 太小了
net.core.somaxconn = 524288
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数
net.ipv4.tcp_max_syn_backlog = 8192
#网卡设备将请求放入队列的长度
net.core.netdev_max_backlog = 65536
vm.overcommit_memory = 1
第三条警告
1、执行echo never > /sys/kernel/mm/transparent_hugepage/enabled
2、vim /etc/rc.local
echo never > /sys/kernel/mm/transparent_hugepage/enabled
需要重启redis
service redis start
service redis stop
service redis restart
启动
redis-server /etc/redis.conf
#### 关闭停止
redis-cli -a 密码 shutdown
## 使用
```bash
get set keys
python部分知识点汇总
1 可变与不可变类型
1.可变类型:列表list
,set
,dict
...
2.不可变类型:Number
,tuple
,string
...
2 python中的单下划线和双下划线
>>> class MyClass():
... def __init__(self):
... self.__superprivate = "Hello"
... self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}
__foo__
:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例如__init__()
,__del__()
,__call__()
这些特殊方法
_foo
:一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.不能用from module import * 导入,其他方面和公有一样访问;
__foo
:这个有真正的意义:解析器用_classname__foo
来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随便访问,通过对象名._类名__xxx这样的方式可以访问.
或者: http://www.zhihu.com/question/19754941
3 __new__
和__init__
的区别
__new__
是一个静态方法,而__init__
是一个实例方法.__new__
方法会返回一个创建的实例,而__init__
什么都不返回.- 只有在
__new__
返回一个cls的实例时后面的__init__
才能被调用. - 当创建一个新实例时调用
__new__
,初始化一个实例时用__init__
.
4 单例模式
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
__new__()
在__init__()
之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例
这个绝对常考啊.绝对要记住1~2个方法,当时面试官是让手写的.
1 使用__new__
方法
class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls, *args, **kw)
return cls._instance
class MyClass(Singleton):
a = 1
2 共享属性
创建实例时把所有实例的__dict__
指向同一个字典,这样它们具有相同的属性和方法.
class Borg(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Borg, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
class MyClass2(Borg):
a = 1
3 装饰器版本
def singleton(cls, *args, **kw):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance
@singleton
class MyClass:
...
4 import方法
作为python的模块是天然的单例模式
# mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
# to use
from mysingleton import my_singleton
my_singleton.foo()
5 浅拷贝与深拷贝的实现方式
import copy
1.浅拷贝: 会创建一个新的对象,但是对于对象中的元素,浅拷贝只会使用原始元素的引用(内存地址),
当拷贝前的原对象或拷贝后的新对象中的元素改变时,另一方中的可变元素跟着改变(如list),不可变元素不改变(如str)
使用下面的操作的时候,会产生浅拷贝的效果:
- 使用切片[:]操作
- 使用工厂函数(如list/dir/set)
- 使用copy模块中的copy()函数
2.深拷贝: 会创建一个新的对象,对于对象中的元素,深拷贝都会重新生成一份(有特殊情况,下面会说明),而不是简单的使用原始元素的引用(内存地址)
当拷贝前的原对象或拷贝后的新对象中的元素改变时,另一方中的可变元素跟着改变(如list),不可变元素不改变(如str)
深拷贝的一些特殊情况:
- 对于非容器类型(如数字、字符串、和其他’原子’类型的对象)没有拷贝这一说,也就是说,对于这些类型,”obj is copy.copy(obj)” 、”obj is copy.deepcopy(obj)”
- 如果元祖变量只包含原子类型对象,则不能深拷贝,看下面的例子
总结:
- Python中对象的赋值都是进行对象引用(内存地址)传递
- 使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.
- 如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝
- 对于非容器类型(如数字、字符串、和其他’原子’类型的对象)没有被拷贝一说
- 如果元祖变量只包含原子类型对象,则不能深拷贝
6 可以直接作用于for循环的数据类型(Iterable):
- 一类是集合数据类型,如list、tuple、dict、set、str等
- 一类是generator,包括生成器和带yield的generator function
7 函数的返回值
- 如果未在函数中指定return,那这个函数的返回值为None
- return多个对象,解释器会把这多个对象组装成一个元组作为一个整体结果输出
8 yield yield from
yield from iterable本质上等于for item in iterable: yield item的缩写版
9 callable() 判断对象能否调用
10 shuffle() 方法将序列的所有元素随机排序。
11 对锁排序
import threading
from contextlib import contextmanager
# Thread-local state to stored information on locks already acquired
_local = threading.local()
@contextmanager
def acquire(*locks):
# Sort locks by object identifier
locks = sorted(locks, key=lambda x: id(x))
# Make sure lock order of previously acquired locks is not violated
acquired = getattr(_local,'acquired',[])
if acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
raise RuntimeError('Lock Order Violation')
# Acquire all of the locks
acquired.extend(locks)
_local.acquired = acquired
try:
for lock in locks:
lock.acquire()
yield
finally:
# Release locks in reverse order of acquisition
for lock in reversed(locks):
lock.release()
del acquired[-len(locks):]
12 数据库配置
DATABASE_OPTIONS = { "init_command": "SET storage_engine=INNODB, wait_timeout = 30, time_zone =... ", }
MongoDB
安装
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
Ubuntu 16.04
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
# 安装好后修改配置文件,注意新建好db文件夹,如/data/db/
vim /etc/mongod.conf
源码安装
创建mongodb用户组和用户
$ groupadd mongodb
$ useradd -r -g mongodb -s /sbin/nologin -M mongodb
官网下载
https://www.mongodb.com/download-center?jmp=nav#community
下载好,解压缩
tar -xvf mongodb-linux-x86_64-ubuntu1604-4.0.2.tgz
创建mongodb文件夹
mkdir -p /usr/local/mongodb/data # 数据库存放目录
mkdir -p /usr/local/mongodb/conf # 数据库配置文件存放目录
mkdir -p /var/run/mongodb # 数据库pid存放目录
mkdir -p /var/log/mongodb # 数据库日志存放目录
移动到/usr/local/mongodb/
mv mongodb-linux-x86_64-ubuntu1604-4.0.2 /usr/local/mongodb
创建/usr/local/mongodb/conf/mongodb.conf
bind_ip=0.0.0.0 #指定此mongodb的ip,用于外网通信,本地的话可以设置为127.0.0.1,或者注释掉
port = 27017 #端口
logpath = /var/log/mongodb/mongodb.log #日志文件路径
dbpath = /usr/local/mongodb/data #数据库文件路径
pidfilepath = /var/run/mongodb/mongodb.pid #pid文件路径
maxConns = 65535 #优化连接数
slowms=200 #记录profile分析的慢查询的时间,默认是100毫秒
fork = true #以守护进程运行
logappend = true #日志追加模式,否则,重启mongo,日志会被覆盖
directoryperdb=true #设置为true,修改数据目录存储模式,MongoDB将数据存储在不同的磁盘设备#上,以提高写入吞吐量或磁盘容量
#注意:要是在运行一段时间的数据库中,开启该参数,会导致原始的数据都会消失(注释参数则会回来)。
auth=true # 开启认证
YAML
方式:
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# Where and how to store data.
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
# engine:
# mmapv1:
# wiredTiger:
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
# network interfaces
net:
port: 27017
bindIp: 0.0.0.0
# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo
fork: true # fork and run in background
pidFilePath: /var/run/mongodb/mongod.pid # location of pidfile
security:
authorization: enabled # disabled
setParameter:
connPoolMaxShardedConnsPerHost: 200
connPoolMaxConnsPerHost: 200
notablescan: 0
#operationProfiling:
#replication:
#sharding:
## Enterprise-Only Options:
#auditLog:
#snmp:
修改mongodb目录权限
chown -R mongodb:mongodb /usr/local/mongodb
chown -R mongodb:mongodb /var/run/mongodb
chown -R mongodb:mongodb /var/log/mongodb
将mongodb命令加入环境变量,修改profile文件
$ vim /etc/profile , 添加到最后面
export PATH=$PATH:/usr/local/mongodb/bin/
$ source /etc/profile
添加启动脚本
# 新建文件,赋权
$ touch start.sh
$ chmod 755 start.sh
$vim start.sh
#!/bin/sh
### BEGIN INIT INFO
# Provides: mongod
# Required-Start: $local_fs $syslog
# Required-Stop: $local_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: mongodb service
### END INIT INFO
MONGOD=/usr/local/mongodb/bin/mongod
CONFIG=/usr/local/mongodb/conf/mongodb.conf
start_mongodb()
{
ps -ef | grep -v "grep" | grep "$MONGOD"
if [ $? -eq 0 ];then
echo "mongodb is in running!"
return 0
fi
$MONGOD --config $CONFIG &
}
stop_mongodb()
{
$MONGOD --config $CONFIG --shutdown
if [ $? -eq 0 ];then
echo "stop mongodb service successfully!"
else
echo "stop mongodb service failed!"
fi
}
query_status()
{
ps -ef | grep -v "grep" | grep "$MONGOD"
if [ $? -eq 0 ];then
echo "mongodb is in running!"
else
echo "mongodb is not in running!"
fi
}
case "$1" in
start)
start_mongodb
;;
stop)
stop_mongodb
;;
restart)
stop_mongodb
start_mongodb
;;
status)
query_status
;;
*)
echo "温馨提示使用方法: sh start.sh start|stop|restart|status,不涉及kill等强制命令,请放心使用"
;;
esac
exit 0
源码安装参考链接: https://my.oschina.net/zhouyuntai/blog/1826182
命令
启动
sudo service mongod start
停止
sudo service mongod stop
重启
sudo service mongod restart
使用
mongo --host 127.0.0.1:27017
使用
创建用户
use admin
db.createUser(
{
user: "myUserAdmin",
pwd: "abc123",
roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
}
)
db.createUser(
{
user: "admin",
pwd: "12345",
roles: [ { role: "readWrite", db: "libtorrent" } ],
mechanisms : ["SCRAM-SHA-1"]
}
)
JQuery
jQuery 选择器
jQuery 元素选择器
- $("p") 选取
<p>
元素。 - $("p.intro") 选取所有 class="intro" 的
<p>
元素。 - $("p#demo") 选取所有 id="demo" 的
<p>
元素。
jQuery 属性选择器
jQuery 使用 XPath 表达式来选择带有给定属性的元素。
- $("[href]") 选取所有带有 href 属性的元素。
- $("[href='#']") 选取所有带有 href 值等于 "#" 的元素。
- $("[href!='#']") 选取所有带有 href 值不等于 "#" 的元素。
-
$("[href$ ='.jpg']") 选取所有 href 值以 ".jpg" 结尾的元素。
参考:
选择器 | 实例 | 选取 |
---|---|---|
* | $("*") | 所有元素 |
#id | $("#lastname") | id="lastname" 的元素 |
.class | $(".intro") | 所有 class="intro" 的元素 |
element | $("p") | 所有 <p> 元素 |
.class.class | $(".intro.demo") | 所有 class="intro" 且 class="demo" 的元素 |
:first | $("p:first") | 第一个 <p> 元素 |
:last | $("p:last") | 最后一个 <p> 元素 |
:even | $("tr:even") | 所有偶数 <tr> 元素 |
:odd | $("tr:odd") | 所有奇数 <tr> 元素 |
:eq(index) | $("ul li:eq(3)") | 列表中的第四个元素(index 从 0 开始) |
:gt(no) | $("ul li:gt(3)") | 列出 index 大于 3 的元素 |
:lt(no) | $("ul li:lt(3)") | 列出 index 小于 3 的元素 |
:not(selector) | $("input:not(:empty)") | 所有不为空的 input 元素 |
:header | $(":header") | 所有标题元素 <h1> - <h6>
|
:animated | 所有动画元素 | |
:contains(text) | $(":contains('W3School')") | 包含指定字符串的所有元素 |
:empty | $(":empty") | 无子(元素)节点的所有元素 |
:hidden | $("p:hidden") | 所有隐藏的 <p> 元素 |
:visible | $("table:visible") | 所有可见的表格 |
s1,s2,s3 | $("th,td,.intro") | 所有带有匹配选择的元素 |
[attribute] | $("[href]") | 所有带有 href 属性的元素 |
[attribute=value] | $("[href='#']") | 所有 href 属性的值等于 "#" 的元素 |
[attribute!=value] | $("[href!='#']") | 所有 href 属性的值不等于 "#" 的元素 |
[attribute$=value] |
|
所有 href 属性的值包含以 ".jpg" 结尾的元素 |
:input | $(":input") | 所有 <input> 元素 |
:text | $(":text") | 所有 type="text" 的 <input> 元素 |
:password | $(":password") | 所有 type="password" 的 <input> 元素 |
:radio | $(":radio") | 所有 type="radio" 的 <input> 元素 |
:checkbox | $(":checkbox") | 所有 type="checkbox" 的 <input> 元素 |
:submit | $(":submit") | 所有 type="submit" 的 <input> 元素 |
:reset | $(":reset") | 所有 type="reset" 的 <input> 元素 |
:button | $(":button") | 所有 type="button" 的 <input> 元素 |
:image | $(":image") | 所有 type="image" 的 <input> 元素 |
:file | $(":file") | 所有 type="file" 的 <input> 元素 |
:enabled | $(":enabled") | 所有激活的 input 元素 |
:disabled | $(":disabled") | 所有禁用的 input 元素 |
:selected | $(":selected") | 所有被选取的 input 元素 |
:checked | $(":checked") | 所有被选中的 input 元素 |
jQuery 名称冲突
jQuery 使用名为 noConflict() 的方法来解决该问题。
var jq=jQuery.noConflict(),帮助您使用自己的名称(比如 jq)来代替 $ 符号。
jQuery 事件
jQuery 效果
隐藏和显示
- $(selector).hide(speed,callback);
- $(selector).show(speed,callback);
可选的 speed 参数规定隐藏/显示的速度,可以取以下值:"slow"、"fast" 或毫秒。
可选的 callback 参数是隐藏或显示完成后所执行的函数名称。
toggle()
- $(selector).toggle(speed,callback);
可以使用 toggle() 方法来切换 hide() 和 show() 方法。
显示被隐藏的元素,并隐藏已显示的元素:
jQuery 淡入淡出
jQuery 拥有下面四种 fade 方法:
- fadeIn() 用于淡入已隐藏的元素。
$(selector).fadeIn(speed,callback);
可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。
可选的 callback 参数是 fading 完成后所执行的函数名称。
- fadeOut() 用于淡出可见元素。
$(selector).fadeOut(speed,callback);
可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。
可选的 callback 参数是 fading 完成后所执行的函数名称。
- fadeToggle() 可以在 fadeIn() 与 fadeOut() 方法之间进行切换。
$(selector).fadeToggle(speed,callback);
如果元素已淡出,则 fadeToggle() 会向元素添加淡入效果。
如果元素已淡入,则 fadeToggle() 会向元素添加淡出效果。
- fadeTo() 允许渐变为给定的不透明度(值介于 0 与 1 之间)。(淡出最后的透明度是多少)
$(selector).fadeTo(speed,opacity,callback);
必需的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。
fadeTo() 方法中必需的 opacity 参数将淡入淡出效果设置为给定的不透明度(值介于 0 与 1 之间)。
可选的 callback 参数是该函数完成后所执行的函数名称。
jQuery 滑动
jQuery 拥有以下滑动方法:
- slideDown() 用于向下滑动元素。
$(selector).slideDown(speed,callback);
可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。
可选的 callback 参数是滑动完成后所执行的函数名称。
- slideUp() 用于向上滑动元素。
$(selector).slideUp(speed,callback);
可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。
可选的 callback 参数是滑动完成后所执行的函数名称。
- slideToggle()
可以在 slideDown() 与 slideUp() 方法之间进行切换。如果元素向下滑动,则 slideToggle() 可向上滑动它们。如果元素向上滑动,则 slideToggle() 可向下滑动它们。
$(selector).slideToggle(speed,callback);
可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。
可选的 callback 参数是滑动完成后所执行的函数名称。
jQuery 动画
jQuery animate() 方法用于创建自定义动画。
$(selector).animate({params},speed,callback);
必需的 params 参数定义形成动画的 CSS 属性。
可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。
可选的 callback 参数是动画完成后所执行的函数名称。
jQuery animate() - 使用队列功能
$("button").click(function(){
var div=$("div");
div.animate({left:'100px'},"slow");
div.animate({fontSize:'3em'},"slow");
});
jQuery 停止动画
jQuery stop() 方法用于停止动画或效果,在它们完成之前。
$(selector).stop(stopAll,goToEnd);
可选的 stopAll 参数规定是否应该清除动画队列。默认是 false,即仅停止活动的动画,允许任何排入队列的动画向后执行。
可选的 goToEnd 参数规定是否立即完成当前动画。默认是 false。
jQuery HTML
jQuery - 获得内容和属性
三个简单实用的用于 DOM 操作的 jQuery 方法:
- text() - 设置或返回所选元素的文本内容
- html() - 设置或返回所选元素的内容(包括 HTML 标记)
- val() - 设置或返回表单字段的值
jQuery attr() 方法用于获取属性值。
text()、html() 以及 val() 的回调函数
上面的三个 jQuery 方法:text()、html() 以及 val(),同样拥有回调函数。回调函数由两个参数:被选元素列表中当前元素的下标,以及原始(旧的)值。然后以函数新值返回您希望使用的字符串。
设置属性 - attr()
jQuery attr() 方法也用于设置/改变属性值。
attr() 方法也允许您同时设置多个属性。
$("button").click(function(){
$("#w3s").attr({
"href" : "http://www.w3school.com.cn/jquery",
"title" : "W3School jQuery Tutorial"
});
});
attr() 的回调函数
jQuery 方法 attr(),也提供回调函数。回调函数由两个参数:被选元素列表中当前元素的下标,以及原始(旧的)值。然后以函数新值返回您希望使用的字符串。
$("button").click(function(){
$("#w3s").attr("href", function(i,origValue){
return origValue + "/jquery";
});
});
jQuery - 添加元素
- append() - 在被选元素的结尾插入内容
- prepend() - 在被选元素的开头插入内容
- after() - 在被选元素之后插入内容
- before() - 在被选元素之前插入内容
jQuery - 删除元素
- remove() - 删除被选元素(及其子元素)
jQuery remove() 方法也可接受一个参数,允许您对被删元素进行过滤。(指定被删除的元素)
- empty() - 从被选元素中删除子元素
jQuery - 获取并设置 CSS 类
- addClass() - 向被选元素添加一个或多个类
- removeClass() - 从被选元素删除一个或多个类
- toggleClass() - 对被选元素进行添加/删除类的切换操作
- css() - 设置或返回样式属性
设置多个 CSS 属性:
css({"propertyname":"value","propertyname":"value",...});
jQuery - 尺寸
jQuery 提供多个处理尺寸的重要方法:
- width() 设置或返回元素的宽度(不包括内边距、边框或外边距)。
- height() 设置或返回元素的高度(不包括内边距、边框或外边距)。
- innerWidth() 返回元素的宽度(包括内边距)。
- innerHeight() 返回元素的高度(包括内边距)。
- outerWidth() 返回元素的宽度(包括内边距和边框)。
- outerHeight() 返回元素的高度(包括内边距和边框)。
- outerWidth(true) 返回元素的宽度(包括内边距、边框和外边距)。
- outerHeight(true) 返回元素的高度(包括内边距、边框和外边距)。
-
$(document).width(); $ (document).height(); -
$(window).width(); $ (window).height();
jQuery 遍历
向上遍历 DOM 树:
- parent() 返回被选元素的直接父元素。
- parents() 返回被选元素的所有祖先元素,它一路向上直到文档的根元素 ()。(可指定元素)
- parentsUntil() 返回介于两个给定元素之间的所有祖先元素。
$(document).ready(function(){
$("span").parentsUntil("div");
});
向下遍历 DOM 树:
- children() 返回被选元素的所有直接子元素。(可以使用可选参数来过滤对子元素的搜索。)
- find() 返回被选元素的后代元素,一路向下直到最后一个后代。
在 DOM 树中水平遍历:
- siblings() 返回被选元素的所有同胞元素。(可以使用可选参数来过滤对同胞元素的搜索。)
- next() 返回被选元素的下一个同胞元素,该方法只返回一个元素。
- nextAll() 返回被选元素的所有跟随的同胞元素。
- nextUntil() 返回介于两个给定参数之间的所有跟随的同胞元素。
- prev()
- prevAll()
- prevUntil()
缩写搜索元素的范围:
三个最基本的过滤方法是:first(), last() 和 eq(),它们允许您基于其在一组元素中的位置来选择一个特定的元素。
filter() 和 not() 允许您选取匹配或不匹配某项指定标准的元素。
filter() filter() 方法允许您规定一个标准。不匹配这个标准的元素会被从集合中删除,匹配的元素会被返回。
not() 方法返回不匹配标准的所有元素。
$(document).ready(function(){
$("p").eq(1);
});
jQuery - AJAX
$(selector).load(URL,data,callback);
$("#div1").load("/example/jquery/demo_test.txt",function(responseTxt,statusTxt,xhr){
if(statusTxt=="success")
alert("外部内容加载成功!");
if(statusTxt=="error")
alert("Error: "+xhr.status+": "+xhr.statusText);
});
});
});
jQuery $.get() 方法
$.get(URL,callback);
$("button").click(function(){
$.get("demo_test.asp",function(data,status){
alert("Data: " + data + "\nStatus: " + status);
});
});
jQuery $.post() 方法
$.post(URL,data,callback);
必需的 URL 参数规定您希望请求的 URL。
可选的 data 参数规定连同请求发送的数据。
可选的 callback 参数是请求成功后所执行的函数名。
$("button").click(function(){
$.post("demo_test_post.asp",
{
name:"Donald Duck",
city:"Duckburg"
},
function(data,status){
alert("Data: " + data + "\nStatus: " + status);
});
});
其他
(function($){...})(jQuery)用来定义一些需要预先定义好的函数
$(function(){ })则是用来在DOM加载完成之后运行\执行那些预行定义好的函数.
柒柒捌捌
先查看当前版本: uname -a
然后更新软件列表:apt-get update
接着升级软件:apt-get upgrade
升级当前系统版本:apt-get dist-upgrade
升级到新的系统版本:do-release-upgrade -d
中间过程中遇到的选择,基本一路 YES…
重启
升级成功!
WebSpider
...
Celery
通过celery实例加载配置模块
app.config_from_object('celery_app.celeryconfig')
启动
后台启动
celery multi start w1 -A apiserver -l info --pidfile=/var/run/celery/%n.pid --logfile=/var/log/celery/%n%I.log
worker和beat一起启动
celery multi start w1 -B -A apiserver -l info --pidfile=/var/run/celery/%n.pid --logfile=/var/log/celery/%n%I.log
重启
celery multi restart w1 -A apiserver -l info
停止
celery multi stop w1 -A apiserver -l info
该stop命令是异步的,因此它不会等待worker关闭。您可能希望使用该stopwait命令,这可确保在退出之前完成所有当前正在执行的任务:
celery multi stopwait w1 -A apiserver -l info
设置log和pid文件位置
celery multi start w1 -A apiserver -l info --pidfile=/var/run/celery/%n.pid --logfile=/var/log/celery/%n%I.log
启动多个worker并指定参数
celery multi start 10 -A apiserver -l info -Q:1-3 images,video -Q:4,5 data -Q default -L:4,5 debug
调用任务
您可以使用以下delay()方法调用任务:
add.delay(2, 2)
此方法实际上是另一个方法的星形参数快捷方式 apply_async():
add.apply_async((2, 2))
后者使您能够指定执行选项,如运行时间(倒计时),应该发送到的队列,等等:
add.apply_async((2, 2), queue='lopri', countdown=10)
检查任务是成功还是失败:
>>> res = add.delay(2)
>>> res.failed()
True
>>> res.successful()
False
# 查看任务状态,典型的任务阶段是:PENDING -> STARTED -> SUCCESS
>>> res.state
'FAILURE'
启动状态是一种特殊状态,仅在task_track_started启用该设置或 仅为@task(track_started=True)该任务设置选项时才会记录 。
签名
使用参数为任务创建签名,倒计时为10秒,如下所示:(2, 2)
>>> add.signature((2, 2), countdown=10)
tasks.add(2, 2)
>>> add.s(2, 2)
tasks.add(2, 2)
创建不完整的签名来创建我们称之为 partials的内容:
# incomplete partial: add(?, 2)
>>> s2 = add.s(2)
s2 现在是一个部分签名,需要另一个参数完成,这可以在调用签名时解决:
# resolves the partial: add(8, 2)
>>> res = s2.delay(8)
>>> res.get()
10
任务链接
有些任务可能需由几个子任务组成,此时调用各个子任务的方式就变的很重要,尽量不要以同步阻塞的方式调用子任务,而是用异步回调的方式进行链式任务的调用
一个任务返回后调用另一个任务:
```python
>>> from celery import chain
>>> from proj.tasks import add, mul
# (4 + 4) * 8
>>> chain(add.s(4, 4) | mul.s(8))().get()
64
或部分链:
>>> # (? + 4) * 8
>>> g = chain(add.s(4) | mul.s(8))
>>> g(4).get()
64
链也可以这样写:
>>> (add.s(4, 4) | mul.s(8))().get()
64
错误示范:
@app.task
def update_page_info(url):
page = fetch_page.delay(url).get()
info = parse_page.delay(url, page).get()
store_page_info.delay(url, info)
@app.task
def fetch_page(url):
return myhttplib.get(url)
@app.task
def parse_page(url, page):
return myparser.parse_document(page)
@app.task
def store_page_info(url, info):
return PageInfo.objects.create(url, info)
正确示范1:
def update_page_info(url):
# fetch_page -> parse_page -> store_page
chain = fetch_page.s(url) | parse_page.s() | store_page_info.s(url)
chain()
@app.task()
def fetch_page(url):
return myhttplib.get(url)
@app.task()
def parse_page(page):
return myparser.parse_document(page)
@app.task(ignore_result=True)
def store_page_info(info, url):
PageInfo.objects.create(url=url, info=info)
正确示范2:
fetch_page.apply_async((url), link=[parse_page.s(), store_page_info.s(url)])
链式任务中前一个任务的返回值默认是下一个任务的输入值之一 ( 不想让返回值做默认参数可以用 si() 或者 s(immutable=True) 的方式调用 )。
这里的 s() 是方法 celery.signature() 的快捷调用方式,signature 具体作用就是生成一个包含调用任务及其调用参数与其他信息的对象,个人感觉有点类似偏函数的概念:先不执行任务,而是把任务与任务参数存起来以供其他地方调用。
路由
通过该task_routes设置,您可以按名称路由任务,并将所有内容集中在一个位置:
app.conf.update(
task_routes = {
'proj.tasks.add': {'queue': 'hipri'},
},
)
您还可以在运行时使用以下queue参数指定队列apply_async:
>>> from proj.tasks import add
>>> add.apply_async((2, 2), queue='hipri')
然后,您可以通过指定选项使工作者从此队列中消耗:celery worker -Q
$ celery -A proj worker -Q hipri
您可以使用逗号分隔列表指定多个队列,例如,您可以使默认队列和hipri队列中的工作者使用,其中默认队列celery由于历史原因而被命名:
$ celery -A proj worker -Q hipri,celery
队列的顺序无关紧要,因为工作人员将给予队列相同的权重。
配置
配置如下app.conf:
>>> app.conf.timezone
'Europe/London'
您还可以在其中直接设置配置值:
app.conf.enable_utc = True
或使用以下update方法一次更新多个键:
>>> app.conf.update(
... enable_utc=True,
... timezone='Europe/London',
...)
配置对象由按顺序查阅的多个字典组成:
- 在运行时进行的更改。
- 配置模块(如果有的话)
- 默认配置(celery.app.defaults)。
config_from_object
从配置对象加载配置,可以是文件,模块或者是具有配置属性的任何对象,取名如:"celeryconfig"
注意,config_from_object()调用时将重置先前设置的任何配置 。如果要设置其他配置,则应在之后执行此操作。
app.config_from_envvar()从一个环境变量取所述配置模块名
Django
http://djangonote.chlinp.com
- 序列化
- 结果是QuerySet(对象):all、filter
from django.core import serializers
from django.http import JsonResponse
def get_users:
ret = {}
users = models.UserInfo.objects.all()
# 序列化QuerySet
ret['users'] = serializers.serialize('json', users)
return JsonResponse(ret)
- 结果是QuerySet(字典):values
from django.core import serializers
from django.http import JsonResponse
def get_users:
ret = {}
users = models.UserInfo.objects.all().values('id', 'username')
# 序列化QuerySet
# ret['users'] = serializers.serialize('json', users)
ret['users'] = list(users)
return JsonResponse(ret)
-
结果是QuerySet(元组):values_list
同values -
csrf_token
# 添加csrf_exempt装饰器的函数免除csrf_token验证,添加csrf_protect装饰器的函数需要csrf_token验证
from django.views.decorators.csrf import csrf_exempt, csrf_protect
# CBV中csrf_token处理,需要在dispatch方法中加装饰器将csrf_exempt, csrf_protect当作参数传入
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs)...
# 或者
@method_decorator(csrf_exempt, name='dispatch')
class ClassName()...
- 将一个渲染过后的页面当作字符串返回
from django.template import loader
from django.template.loader import render_to_string
ret = loader.render_to_string('secondPage/news_snippet.html', {'articles': index_article}, request=request)
return HttpResponse(ret)
REST framework
REST简称Representational State Transfer,“表征状态转移”
Restful API设计
-
API与用户通信的协议,总是使用HTTPS协议。
-
版本:URL:https://api.example.com/v1
-
路径,定位网络上的唯一资源,均使用名词
-
method
- GET :从服务器取出资源(一项或多项)
- POST :在服务器新建一个资源
- PUT :在服务器更新资源(客户端提供改变后的完整资源)
- PATCH :在服务器更新资源(客户端提供改变的属性)
- DELETE :从服务器删除资源
-
过滤,通过URL上传参的形式传递搜索条件
- https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
- https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置
- https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
- https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
- https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件
-
状态码
- 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
- 更多
-
serializers.Serializer, serializers.ModelSerializer, depth
from rest_framework import serializers
class UserInfoSerializer(serializers.Serializer):
xxx = serializers.CharField(source="get_user_type_display") choices
name = serializers.CharField()
age = serializers.IntegerField()
gp = serializers.Charfield(source='group.name') #ForiegnKey
rls = serializers.SerializerMethodField()
def get_rls(self, row):
role_obj_list = row.objects.all()
ret = []
for item in role_obj_list:
ret.append({'id': item.id, 'title': item.title})
return ret
# 过滤函数
def get_queryset(self):
return self.queryset.filter(end_time__gt=timezone.now())
-
错误处理,状态码是4xx时,应返回错误信息,error当做key。
{ error: "Invalid API key" }
-
返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。
- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
-
Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
Django REST framework
安装
- pip安装
pip install djangorestframework
pip install markdown # Markdown support for the browsable API.
pip install django-filter # Filtering support
- 添加
rest_framework
到INSTALLED_APPS
INSTALLED_APPS = (
...
'rest_framework',
)
- 添加到根urls.py
urlpatterns = [
...
url(r'^api-auth/', include('rest_framework.urls'))
]
- 添加全局设置到
settings.py
中
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
序列化
# http://www.django-rest-framework.org/tutorial/1-serialization/
# 序列化
snippet = Snippet(code='print "hello, world"\n')
snippet.save()
serializer = SnippetSerializer(snippet)
content = JSONRenderer().render(serializer.data)
# 反序列化
from django.utils.six import BytesIO
stream = BytesIO(content)
data = JSONParser().parse(stream)
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
ReadOnlyField
class IndexSerializers(serializers.HyperlinkedModelSerializer):
name = serializers.ReadOnlyField(source='goods.name') # 增加外健的字段
price = serializers.ReadOnlyField(source='goods.price') # 添加外健的字段
class Meta:
model = Index
fields = ('goods', 'start_at', 'end_at', 'name', 'price')
PyQuery&Xpath
PyQuery
安装
pip3 install pyquery
初始化的几种方式
html = '''
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html) # 初始化
print(doc('li'))
doc1 = pq(url='http://baidu.com') # url初始化
print(doc1('title')
doc2 = pq(filename='demo.html') # 文件初始化
print(doc2('title')
查找节点
from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
print(type(items))
print(items)
lis = items.find('li') # 查找节点的所有子孙节点
print(type(lis))
print(lis)
lis1 = items.children('.active') # 查找子节点
print(type(lis1))
print(lis1)
lis2 = items.parent('.active') # 查找父节点
print(type(lis2))
print(lis2)
lis3 = items.parents('.active') # 查找祖先节点
print(type(lis3))
print(lis3)
lis4 = items.siblings('.active') # 查找兄弟节点
print(type(lis4))
print(lis4)
遍历
对于单个节点来说,我们可以直接打印输出,也可直接转成字符串:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(str(li))
对于多个节点的结果,我们就需要遍历来获取了,例如这里我们把每一个 li 节点进行遍历,,需要调用 items() 方法:
lis = doc('li').items()
print(type(lis))
for li in lis:
print(li, type(li))
str、items()
获取信息
获取属性
html = '''
<div class="wrap">
<div id="container">
<ul class="list">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('.item-0.active a')
print(a, type(a))
print(a.attr('href')) # 等同于print(a.attr.href),如果有多个结果时,需要遍历
获取文本
获取纯文本,调用 text() 方法
获取html,调用 html() 方法
注意,如果我们得到的结果是多个节点,如果要获取每个节点的内部 HTML 文本,则需要遍历每个节点,而 text() 方法不需要遍历就可以获取,它是将所有节点取文本之后合并成一个字符串。
节点操作
addClass、removeClass、attr、text、html、remove
html = '''
<div class="wrap">
Hello, World
<p>This is a paragraph.</p>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
wrap = doc('.wrap')
print(wrap.text())
wrap.find('p').remove()
print(wrap.text())
伪类选择器
html = '''
<div class="wrap">
<div id="container">
<ul class="list">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('li:first-child') # 第一个 li 节点
print(li)
li = doc('li:last-child') # 最后一个 li 节点
print(li)
li = doc('li:nth-child(2)') # 第二个 li 节点
print(li)
li = doc('li:gt(2)') # 第三个 li 之后的 li 节点
print(li)
li = doc('li:nth-child(2n)') # 偶数位置的 li 节点
print(li)
li = doc('li:contains(second)') # 包含 second 文本的 li 节点
print(li)
Xpath
常用规则:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
栗子
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
<li class="li li-first"><a href="link.html">first item</a></li>
</ul>
</div>
'''
html = etree.HTML(text) #能自动闭合标签
result = etree.tostring(html) # 转化为字符串
print(result.decode('utf-8'))
print(html)
ret = html.xpath('//li')
print(ret)
print(ret[0])
text = html.xpath('//ul//a//text()') # text()获取文本
print(text)
href = html.xpath('//li/a/@href') # @href属性获取
print(href)
ret1 = html.xpath('//li[contains(@class, "li")]/a/text()') # 属性多值匹配用contains()
print(ret1)
ret2= html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()') # 多属性匹配用and连接
print(ret2)
运算符
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
or | 或 | price=9.80 or price=9.70 | 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。 |
and | 与 | price>9.00 and price<9.90 | 如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。 |
mod | 计算除法的余数 | 5 mod 2 | 1 |
\ | 计算两个节点集 | //book \ //cd | 返回所有拥有 book 和 cd 元素的节点集 |
+ | 加法 | 6 + 4 | 10 |
- | 减法 | 6 - 4 | 2 |
* | 乘法 | 6 * 4 | 24 |
div | 除法 | 8 div 4 | 2 |
= | 等于 | price=9.80 | 如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。 |
!= | 不等于 | price!=9.80 | 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。 |
< | 小于 | price<9.80 | 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。 |
<= | 小于或等于 | price<=9.80 | 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。 |
> | 大于 | price>9.80 | 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。 |
>= | 大于或等于 | price>=9.80 | 如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。 |
按序选择
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
# 选取了第一个 li 节点,中括号中传入数字1即可,注意这里和代码中不同,序号是以 1 开头的,不是 0 开头的。
result = html.xpath('//li[1]/a/text()')
print(result)
# 选取了最后一个 li 节点,中括号中传入 last() 即可,返回的便是最后一个 li 节点。
result = html.xpath('//li[last()]/a/text()')
print(result)
# 选取了位置小于 3 的 li 节点,也就是位置序号为 1 和 2 的节点,得到的结果就是前 2 个 li 节点。
result = html.xpath('//li[position()<3]/a/text()')
print(result)
# 选取了倒数第三个 li 节点,中括号中传入 last()-2即可,因为 last() 是最后一个,所以 last()-2 就是倒数第三个。
result = html.xpath('//li[last()-2]/a/text()')
print(result)
节点轴选择
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html"><span>first item</span></a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
# 调用了 ancestor 轴,可以获取所有祖先节点,其后需要跟两个冒号,然后是节点的选择器,这里我们直接使用了 *,表示匹配所有节点,因此返回结果是第一个 li 节点的所有祖先节点,包括 html,body,div,ul。
result = html.xpath('//li[1]/ancestor::*')
print(result)
# 这次加了限定条件,这次在冒号后面加了 div,这样得到的结果就只有 div 这个祖先节点了。
result = html.xpath('//li[1]/ancestor::div')
print(result)
# 调用了 attribute 轴,可以获取所有属性值,其后跟的选择器还是 *,这代表获取节点的所有属性,返回值就是 li 节点的所有属性值。
result = html.xpath('//li[1]/attribute::*')
print(result)
# 调用了 child 轴,可以获取所有直接子节点,在这里我们又加了限定条件选取 href 属性为 link1.html 的 a 节点。
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result)
# 调用了 descendant 轴,可以获取所有子孙节点,这里我们又加了限定条件获取 span 节点,所以返回的就是只包含 span 节点而没有 a 节点。
result = html.xpath('//li[1]/descendant::span')
print(result)
# 调用了 following 轴,可以获取当前节点之后的所有节点,这里我们虽然使用的是 * 匹配,但又加了索引选择,所以只获取了第二个后续节点。
result = html.xpath('//li[1]/following::*[2]')
print(result)
# 调用了 following-sibling 轴,可以获取当前节点之后的所有同级节点,这里我们使用的是 * 匹配,所以获取了所有后续同级节点。
result = html.xpath('//li[1]/following-sibling::*')
print(result)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.