我不会接受我不要的未来
哪怕是命中注定

Python 元类

技术搬运工阅读(19)

什么是元类

我们知道,实例对象是由类来创建,那么类又是由什么来创建的呢? 答案就是元类。

元类基本都不会用到,但是就算不用到,也应该去熟悉一下概念。

类也是对象

在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:

In [1]: class FatBoy(object): 
   ...:     pass 
   ...:                                                                                                                    

In [2]: fb = FatBoy()                                                                                                      

In [3]: print(fb)                                                                                                          
<__main__.FatBoy object at 0x7fbd18f73b38>

In [4]: id(fb)                                                                                                             
Out[4]: 140450144402232

In [5]:

 

但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。

下面的代码段:

In [1]: class FatBoy(object): 
   ...:     pass 
   ...:

 

将在内存中创建一个对象,名字就是FatBoy。这个对象(类对象FatBoy)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:

  • 你可以将它赋值给一个变量
  • 你可以拷贝它
  • 你可以为它增加属性
  • 你可以将它作为函数参数进行传递

下面是示例:

# 可以打印一个类,因为它其实也是一个对象
In [5]: print(FatBoy)                                                                                                      
<class '__main__.FatBoy'>

In [6]:         
                                                                                                           
# 可以将它作为函数参数进行传递
In [6]: def print_object(obj): 
   ...:     print(obj) 
   ...:                                                                                                                                                                                                                                     

In [7]: print_object(FatBoy)                                                                                               
<class '__main__.FatBoy'>

In [8]:                                                                                                                    

# 可以为它增加属性
In [8]: FatBoy.hobby = "肥仔可乐水"                                                                                        

# 查看该属性是否存在
In [9]: print(hasattr(FatBoy,'hobby'))                                                                                     
True

# 打印该属性
In [10]: print(FatBoy.hobby)                                                                                               
肥仔可乐水

In [11]:                                                                                                                   

# 可以将它赋值给一个变量
In [11]: FatBoss = FatBoy                                                                                                  

# 查看FatBoss是否是类
In [12]: print(FatBoss)                                                                                                    
<class '__main__.FatBoy'>

# 查看FatBoss有无hobby属性
In [13]: print(hasattr(FatBoss,'hobby'))                                                                                   
True

# 打印看看hobby属性,发现完全将FatBoy复制过来了。
In [14]: print(FatBoss.hobby)                                                                                              
肥仔可乐水

In [15]:

动态地创建类

因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可。

 

In [15]: def choose_class(name): 
    ...:     if name == 'foo': 
    ...:         class Foo(object): 
    ...:             pass 
    ...:         return Foo # 返回的是类,不是类的实例 
    ...:     else: 
    ...:         class Bar(object): 
    ...:             pass 
    ...:         return Bar 
    ...:                                                                                                                   

In [16]: MyClass = choose_class('foo')                                                                                     

## 可以看出MyClass就是一个类,Foo类
In [17]: print(MyClass)                                                                                                    
<class '__main__.choose_class.<locals>.Foo'>

## 可以通过这个类创建类实例,也就是对象
In [18]: print(MyClass())                                                                                                  
<__main__.choose_class.<locals>.Foo object at 0x7fbd19c4e550>

In [19]:   

但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。

当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。

还记得类型函数type()吗?这个古老但强大的函数能够让你知道一个对象的类型是什么,就像这样:

 

In [19]: print(type(1)) # 数值的类型                                                                                       
<class 'int'>

In [20]: type(1)                                                                                                           
Out[20]: int

In [21]: print(type("fat boy")) # 字符串类型                                                                               
<class 'str'>

In [22]: type("fat boy")                                                                                                   
Out[22]: str

In [23]: print(type(FatBoy())) # 实例对象类型                                                                              
<class '__main__.FatBoy'>

In [24]: type(FatBoy())                                                                                                    
Out[24]: __main__.FatBoy

In [25]: print(type(FatBoy)) # 类的类型                                                                                    
<class 'type'>

In [26]: type(FatBoy)                                                                                                      
Out[26]: type

In [27]:  

仔细观察上面的运行结果,发现使用type对FatBoy查看类型是,答案为type, 是不是有些惊讶。

使用type创建类

type还有一种完全不同的功能,动态的创建类。

type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

type可以像这样工作:

type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

 

In [27]: class Test: # 定义一个Test类 
    ...:     pass 
    ...:                                                                                                                   

In [28]: Test() # 创建一个Test类实例对象                                                                                   
Out[28]: <__main__.Test at 0x7fbd19bf0cf8>

In [29]:  

可以手动像这样创建:

 

In [29]: Test2 = type("Test2",(),{}) # 定义了一个Test2类                                                                   

In [30]: Test2() # 创建一个Test2类实例对象                                                                                 
Out[30]: <__main__.Test2 at 0x7fbd19b91208>

In [31]:  

我们使用”Test2″作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。

即type函数中第1个实参,也可以叫做其他的名字,这个名字表示类的名字,如下:

 

## type的第一个实参 FatBoss 就是类名,然后传递给变量 FatBoy
In [1]: FatBoy = type("FatBoss",(),{})

In [2]: FatBoy
Out[2]: __main__.FatBoss

## 查看FatBoy的类描述,就是FatBoss类
In [3]: help(FatBoy)
Help on class FatBoss in module __main__:

class FatBoss(builtins.object)
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)

使用type创建带有属性的类

创建一个胖子老板类,增加属性hobby:胖子老板卖槟榔

 

In [5]: FatBoss = type('FatBoss',(),{'hobby':"胖子老板卖槟榔"})

In [6]: FatBoss
Out[6]: __main__.FatBoss

In [7]: FatBoss.hobby
Out[7]: '胖子老板卖槟榔'

In [8]: help(FatBoss)
Help on class FatBoss in module __main__:

class FatBoss(builtins.object)
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |
 |  hobby = '胖子老板卖槟榔'


In [9]: FatBoss.__dict__
Out[9]: 
mappingproxy({'hobby': '胖子老板卖槟榔',
              '__module__': '__main__',
              '__dict__': <attribute '__dict__' of 'FatBoss' objects>,
              '__weakref__': <attribute '__weakref__' of 'FatBoss' objects>,
              '__doc__': None})

In [10]: 

注意:添加的属性是类属性,并不是实例属性

现在我们已经知道 type 的第三个参数是用来设置类属性的,那么第二个参数元组是干什么的呢?

第二个参数元组是用来填写继承的父类名称,演示如下:

 

## 定义胖子老板的女儿类,继承胖子老板类
In [10]: FatBossGril = type("FatBossGril",(FatBoss,),{})

## 那么这里来看看是否继承了胖子老板的hobby爱好,下面可以看出是正常继承的。
In [11]: FatBossGril.hobby
Out[11]: '胖子老板卖槟榔'

## 再看看FatBossGril的继承MRO
In [12]: FatBossGril.__mro__
Out[12]: (__main__.FatBossGril, __main__.FatBoss, object)

In [13]: FatBossGril.__class__
Out[13]: type

In [14]: 

使用type创建带有方法的类

类通常都会有类方法,下面来看看怎么使用type的方式来创建类方法。

 

In [14]: def sell(self): # 定义一个普通的函数,等下加入胖子老板类
    ...:     print(self.hobby)
    ...: 

In [15]: hasattr(FatBoss,'hobby')
Out[15]: True

In [16]: FatBossGril = type('FatBossGril',(FatBoss,),{'sell':sell})

In [17]: hasattr(FatBossGril,'sell')
Out[17]: True

In [18]: hasattr(FatBossGril,'hobby')
Out[18]: True

In [21]: fatbossgril = FatBossGril()

In [22]: fatbossgril.sell()
胖子老板卖槟榔

In [23]: 

上面演示的这个方法属于实例方法,那么静态方法、类方法这些该怎么创建呢?

设置静态方法

 

In [25]: @staticmethod
    ...: def static_method():
    ...:     print("static method...")
    ...: 

In [26]: FatBossGril = type('FatBossGril',(FatBoss,),{'sell':sell,'static_method':static_method})

In [27]: FatBossGril.static_method
Out[27]: <function __main__.static_method()>

In [28]: FatBossGril.static_method()
static method...

静态方法不需要实例化就可以使用,下面再来看看类方法。

设置类方法

 

In [31]: @classmethod
    ...: def class_method(cls):
    ...:     print(cls.hobby)
    ...:

In [32]: FatBossGril = type('FatBossGril',(FatBoss,),{'sell':sell,'static_method':static_method,'class_method':class_method})

In [33]: FatBossGril.class_method
Out[33]: <bound method class_method of <class '__main__.FatBossGril'>>

In [34]: FatBossGril.class_method()
胖子老板卖槟榔

In [35]: 

从上面的几个示例,基本已经知道了如何使用type来定义方法。

那么再来思考一下,到底什么是元类

元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。

元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

 

MyClass = MetaClass() # 使用元类创建出一个对象,这个对象称为“类”
my_object = MyClass() # 使用“类”来创建出实例对象

你已经看到了type可以让你像这样做:

 

MyClass = type('MyClass', (), {})

这是因为函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查class属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。

 

## 查看age整数的类属性
In [35]: age = 23

In [36]: age.__class__
Out[36]: int

## 查看name字符串的类属性
In [37]: name = '肥仔白'

In [38]: name.__class__
Out[38]: str

## 查看函数方法的类属性
In [39]: def sell():
    ...:     pass
    ...: 

In [40]: sell.__class__
Out[40]: function

## 查看实例的类属性
In [41]: class FatBoss(object):
    ...:     pass
    ...: 

In [42]: fb = FatBoss()

In [43]: fb.__class__
Out[43]: __main__.FatBoss

In [44]: 

从上面的示例,大概知道了整型、字符串等等的类属性。那么类(__class__)的类属性(__class__)又是什么呢?

 

In [44]: age.__class__
Out[44]: int

In [45]: age.__class__.__class__
Out[45]: type

In [46]: name.__class__.__class__
Out[46]: type

In [47]: sell.__class__
Out[47]: function

In [48]: sell.__class__.__class__
Out[48]: type

In [49]: fb.__class__
Out[49]: __main__.FatBoss

In [50]: fb.__class__.__class__
Out[50]: type

In [51]: fb.__class__.__class__.__class__
Out[51]: type

In [52]: 

可以看出,不管是什么类型的类,最终的创建元类都是type

因此,元类就是创建类这种对象的东西。type就是Python的内建元类,当然了,你也可以创建自己的元类。

__metaclass__属性

上面已经知道了如何使用type这个元类来创建类,那么如何创建自己的元类呢??
此情此景需要用到__metaclass__属性。

其实仔细回想一下,就是类似闭包的处理方式,下面来演示看看。

 

class FatBoss(object):
    __metaclass__ = something…
    ...省略...

如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。

大致过程:

  • 当你写如下代码时 :

 

class FatBossGril(FatBoss):
    pass
  • Python就会做如下的操作:
    • FatBossGril中有__metaclass__这个属性吗?如果有,那么Python会通过__metaclass__创建一个名字为FatBossGril的类(对象)
    • 如果Python没有找到__metaclass__,它会继续在FatBoss(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。
    • 如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
    • 如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的类都可以,反正最终还是需要type的。

自定义元类

元类的主要目的就是为了当创建类时能够自动地改变类。

假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。

python2中

 

[root@server81 test]# cat test1.py 
#-*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    # class_name 会保存类的名字 Foo
    # class_parents 会保存类的父类 object
    # class_attr 会以字典的方式保存所有的类属性

    # 遍历属性字典,把不是__开头的属性名字变为大写
    new_attr = {}
    print("="*30)
    for name, value in class_attr.items():
        print("name=%s and value=%s" % (name,value))  # 打印所有类属性出来
        if not name.startswith("__"):
            new_attr[name.upper()] = value
            print("name.upper()=",name.upper())
            print("value=",value)

    # 调用type来创建一个类
    return type(class_name, class_parents, new_attr)

class Foo(object):
    __metaclass__ = upper_attr # 设置Foo类的元类为upper_attr
    bar = 'bip'


print("="*30)
print("check Foo exist bar attr=",hasattr(Foo, 'bar'))
print("check Foo exist BAR attr=",hasattr(Foo, 'BAR'))

f = Foo()
print("print f.BAR=",f.BAR)
[root@server81 test]# 

运行如下:

 

[root@server81 test]# python test1.py 
==============================
name=bar and value=bip
('name.upper()=', 'BAR')
('value=', 'bip')
name=__module__ and value=__main__
name=__metaclass__ and value=<function upper_attr at 0x251c578>
==============================
('check Foo exist bar attr=', False)
('check Foo exist BAR attr=', True)
('print f.BAR=', 'bip')
[root@server81 test]# 

python3中

 

[root@server81 test]# cat test1.py 
#-*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    # class_name 会保存类的名字 Foo
    # class_parents 会保存类的父类 object
    # class_attr 会以字典的方式保存所有的类属性

    # 遍历属性字典,把不是__开头的属性名字变为大写
    new_attr = {}
    print("="*30)
    for name, value in class_attr.items():
        print("name=%s and value=%s" % (name,value))  # 打印所有类属性出来
        if not name.startswith("__"):
            new_attr[name.upper()] = value
            print("name.upper()=",name.upper())
            print("value=",value)

    # 调用type来创建一个类
    return type(class_name, class_parents, new_attr)

class Foo(object, metaclass=upper_attr): # python3 与 2的写法唯一区别
    bar = 'bip'


print("="*30)
print("check Foo exist bar attr=",hasattr(Foo, 'bar'))
print("check Foo exist BAR attr=",hasattr(Foo, 'BAR'))

f = Foo()
print("print f.BAR=",f.BAR)
[root@server81 test]# 

运行如下:

 

[root@server81 test]# python3 test1.py 
==============================
name=__module__ and value=__main__
name=__qualname__ and value=Foo
name=bar and value=bip
name.upper()= BAR
value= bip
==============================
check Foo exist bar attr= False
check Foo exist BAR attr= True
print f.BAR= bip
[root@server81 test]# 

从上面的例子中,使用元类的方式,将Foo类中的属性bar修改为BAR。在这是使用的def 方法来作为类似元类的做法,下面使用class来定义元类。

 

[root@server81 test]# cat test1.py 
#-*- coding:utf-8 -*-
#def UpperAttrMetaClass(class_name, class_parents, class_attr):
class UpperAttrMetaClass(type):

    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(cls, class_name, class_parents, class_attr):
        # 遍历属性字典,把不是__开头的属性名字变为大写
        new_attr = {}
        print("="*30)
        for name, value in class_attr.items():
            print("name=%s and value=%s" % (name,value))  # 打印所有类属性出来
            if not name.startswith("__"):
               new_attr[name.upper()] = value
               print("name.upper()=",name.upper())
               print("value=",value)
        
        # 调用type来创建一个类
        return type(class_name, class_parents, new_attr)

class Foo(object, metaclass=UpperAttrMetaClass):
    bar = 'bip'


print("="*30)
print("check Foo exist bar attr=",hasattr(Foo, 'bar'))
print("check Foo exist BAR attr=",hasattr(Foo, 'BAR'))

f = Foo()
print("print f.BAR=",f.BAR)
[root@server81 test]# 

运行如下:

 

[root@server81 test]# python3 test1.py 
==============================
name=__module__ and value=__main__
name=__qualname__ and value=Foo
name=bar and value=bip
name.upper()= BAR
value= bip
==============================
check Foo exist bar attr= False
check Foo exist BAR attr= True
print f.BAR= bip
[root@server81 test]# 

就元类本身而言,它们其实是很简单的:

  • 拦截类的创建
  • 修改类
  • 返回修改之后的类

究竟为什么要使用元类?

现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?好吧,一般来说,你根本就用不上它:

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters

作者:DevOps海洋的渔夫
链接:https://www.jianshu.com/p/c1ca0b9c777d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

python:configparser模块

技术搬运工阅读(75)

一、configparser介绍

configparser模块主要用于读取配置文件,导入方法:import configparser

二、configparser初始化

import configparser
# 生成ConfigParser对象
config = configparser.ConfigParser()
# 读取配置文件
filename = 'config.ini'
config.read(filename, encoding='utf-8')

三、基本操作

配置文件config.ini

[user]
user_name = Mr,X
password = 222

[connect]
ip = 127.0.0.1
port = 4723
1. 获取节点sections
  • ConfigParserObject.sections()
    以列表形式返回configparser对象的所有节点信息
# 获取所有节点
all_sections = config.sections()
print('sections: ', all_sections)   # 结果sections:  ['user', 'connect']
2. 获取指定节点的的所有配置信息
  • ConfigParserObject.items(section)
    以列表形式返回某个节点section对应的所有配置信息
# 获取指定节点的配置信息
items = config.items('user')
print(items)            # 结果 [('user_name', "'Mr,X'"), ('password', "'222'")]
3. 获取指定节点的options
  • ConfigParserObject.options(section)
    以列表形式返回某个节点section的所有key值
# 获取指定节点的options信息
options = config.options('user')
print(options)          # 结果 ['user_name', 'password']
4. 获取指定节点下指定option的值
  • ConfigParserObject.get(section, option)
    返回结果是字符串类型
  • ConfigParserObject.getint(section, option)
    返回结果是int类型
  • ConfigParserObject.getboolean(section, option)
    返回结果是bool类型
  • ConfigParserObject.getfloat(section, option)
    返回结果是float类型
# 获取指定节点指定option的值
name = config.get('user', 'user_name')
print(name, type(name))            # 结果 'Mr,X' <class 'str'>
port = config.getint('connect', 'port')
print(port, type(port))           # 结果  4723 <class 'int'>
5. 检查section或option是否存在
  • ConfigParserObject.has_section(section)
  • ConfigParserObject.has_option(section, option)
    返回bool值,若存在返回True,不存在返回False
# 检查section是否存在
result = config.has_section('user')
print(result)   # 结果 True
result = config.has_section('user1')
print(result)   # 结果 False

# 检查option是否存在
result = config.has_option('user', 'user_name')
print(result)   # 结果 True
result = config.has_option('user', 'user_name1')
print(result)   # 结果 False
result = config.has_option('user1', 'user_name')
print(result)   # 结果 False
6. 添加section
  • ConfigParserObject.add_section(section)
    如果section不存在,则添加节点section;
    若section已存在,再执行add操作会报错configparser.DuplicateSectionError: Section XX already exists
# 添加section
if not config.has_section('remark'):
    config.add_section('remark')
config.set('remark', 'info', 'ok')
config.write(open(filename, 'w'))
remark = config.items('remark')
print(remark)    # 结果 [('info', 'ok')]
7. 修改或添加指定节点下指定option的值
  • ConfigParserObject.set(section, option, value)
    若option存在,则会替换之前option的值为value;
    若option不存在,则会创建optiion并赋值为value
# 修改指定option的值
config.set('user', 'user_name', 'Mr L')
config.set('user', 'isRemember', 'True')
config.write(open(filename, 'w'))
# 重新查看修改后节点信息
items = config.items('user')
print(items)    # 结果 [('user_name', 'Mr L'), ('password', '222'), ('isremember', 'True')]
8.删除section或option
  • ConfigParserObject.remove_section(section)
    若section存在,执行删除操作;
    若section不存在,则不会执行任何操作
  • ConfigParserObject.remove_option(section, option)
    若option存在,执行删除操作;
    若option不存在,则不会执行任何操作;
    若section不存在,则会报错configparser.NoSectionError: No section: XXX
# 删除section
config.remove_section('remark')         # section存在
config.remove_section('no_section')     # section不存在

# 删除option
config.remove_option('user', 'isremember')  # option存在
config.remove_option('user', 'no_option')   # option不存在
config.write(open(filename, 'w'))

all_sections = config.sections()
print(all_sections)     # 结果 ['user', 'connect']
options = config.options('user')
print(options)      # 结果 ['user_name', 'password']
9.写入内容
  • ConfigParserObject.write(open(filename, ‘w’))
    对configparser对象执行的一些修改操作,必须重新写回到文件才可生效
config.write(open(filename, 'w'))

 

理解Python装饰器(Decorator)

技术搬运工阅读(45)

理解Python装饰器(Decorator)

Python装饰器看起来类似Java中的注解,然鹅和注解并不相同,不过同样能够实现面向切面编程。

想要理解Python中的装饰器,不得不先理解闭包(closure)这一概念。

闭包

看看维基百科中的解释:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

官方的解释总是不说人话,but–talk is cheap,show me the code:

# print_msg是外围函数
def print_msg():
    msg = "I'm closure"

    # printer是嵌套函数
    def printer():
        print(msg)

    return printer


# 这里获得的就是一个闭包
closure = print_msg()
# 输出 I'm closure
closure()

msg是一个局部变量,在print_msg函数执行之后应该就不会存在了。但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。

结合这个例子再看维基百科的解释,就清晰明了多了。闭包就是引用了自有变量的函数,这个函数保存了执行的上下文,可以脱离原本的作用域独立存在。

下面来看看Python中的装饰器。

装饰器

一个普通的装饰器一般是这样:

import functools


def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('call %s():' % func.__name__)
        print('args = {}'.format(*args))
        return func(*args, **kwargs)

    return wrapper

这样就定义了一个打印出方法名及其参数的装饰器。

调用之:

@log
def test(p):
    print(test.__name__ + " param: " + p)
    
test("I'm a param")

输出:

call test():
args = I'm a param
test param: I'm a param

装饰器在使用时,用了@语法,让人有些困扰。其实,装饰器只是个方法,与下面的调用方式没有区别:

def test(p):
    print(test.__name__ + " param: " + p)

wrapper = log(test)
wrapper("I'm a param")

@语法只是将函数传入装饰器函数,并无神奇之处。

值得注意的是@functools.wraps(func),这是python提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、name、参数列表等等。可以尝试去除@functools.wraps(func),你会发现test.__name__的输出变成了wrapper。

带参数的装饰器

装饰器允许传入参数,一个携带了参数的装饰器将有三层函数,如下所示:

import functools

def log_with_param(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('call %s():' % func.__name__)
            print('args = {}'.format(*args))
            print('log_param = {}'.format(text))
            return func(*args, **kwargs)

        return wrapper

    return decorator
    
@log_with_param("param")
def test_with_param(p):
    print(test_with_param.__name__)

看到这个代码是不是又有些疑问,内层的decorator函数的参数func是怎么传进去的?和上面一般的装饰器不大一样啊。

其实道理是一样的,将其@语法去除,恢复函数调用的形式一看就明白了:

# 传入装饰器的参数,并接收返回的decorator函数
decorator = log_with_param("param")
# 传入test_with_param函数
wrapper = decorator(test_with_param)
# 调用装饰器函数
wrapper("I'm a param")

输出结果与正常使用装饰器相同:

call test_with_param():
args = I'm a param
log_param = param
test_with_param

至此,装饰器这个有点费解的特性也没什么神秘了。

装饰器这一语法体现了Python中函数是第一公民,函数是对象、是变量,可以作为参数、可以是返回值,非常的灵活与强大。

Python中引入了很多函数式编程的特性,需要好好学习与体会。

参考:https://www.runoob.com/w3cnote/python-func-decorators.html

Python模块之命令行参数解析

技术搬运工阅读(69)

解析命令行参数模块

Python中由三个内建的模块用于处理命令行参数:
第一个:getopt,只能简单的处理命令行参数

官网资料:https://docs.python.org/2/library/getopt.html#module-getopt
第二个:optparse,功能强大,易于使用,可以方便地生成标准的、符合Unix/Posix 规范的命令行说明。(Python2.7以后弃用,不会继续发展)
官网资料:https://docs.python.org/2/library/optparse.html
第三个:argparse,使其更加容易的编写用户友好的命令行接口。它所需的程序进程了参数定义,argparse将更好的解析sys.argv。同时argparse模块还能自动生成帮助及用户输入错误参数时的提示信息。

官网资料:https://docs.python.org/2/library/argparse.html#module-argparse

getopt

在运行程序时,可能需要根据不同的条件,输入不同的命令行选项来实现不同的功能。目前常用的由短选项和长选项两种格式。短选项格式为”-“加上单个字母 选项,长选项为”–“加上一个单词。长格式实在Linux下引入的。许多Linux程序都支持这两种格式。在Python中提供了getopt模块很好 的实现了对着两种用法的支持,而且使用简单。

获取命令行参数

Python环境下可以使用sys模块得到命令行参数

import getopt
import sys

print(sys.argv)

C:\Users\Administrator\>python 222.py -o t --help cmd file1 file2
['222.py', '-o', 't', '--help', 'cmd', 'file1', 'file2']

# 由此可见,所有命令行参数以空格为分隔符,保存在sys.argv列表中,其中第一个为文件名

选项的写法要求:
短格式:”-“号后面要紧跟一个选项字母。如果还有此选项的附加参数,可以用空格分开,也可以不分开。长度任意,可以用引号。
例如:
-o
-o “a b”
长格式:”–“号后面要跟一个单词。如果还有些选项的附加参数,后面要紧跟”=”,再加上参数。”=”号前后不能有空格。
例如:
–help=file1

如何使用getopt进行参数分析及处理?

第一步:导入sys,getopt模块
第二步:分析命令行参数
第三步:处理结果

import getopt
import sys

# print(sys.argv)
def usage():
    print(
"""
Usage:sys.args[0] [option]
-h or --help:显示帮助信息
-m or --module:模块名称   例如:ansible all -m shell -a 'date'
-a or --args:模块对于的参数  例如:ansible all -m shell -a 'date'
-v or --version:显示版本
"""
    )
if len(sys.argv) == 1:
    usage()
    sys.exit()

try:
    opts, args = getopt.getopt(sys.argv[1:], "ho:m:a:", ["help", "output="])   # sys.argv[1:] 过滤掉第一个参数(它是脚本名称,不是参数的一部分)
except getopt.GetoptError:
    print("argv error,please input")

# 使用短格式分析串“ho:m:a:”
 当一个选项只表示开关状态时,即后面不带任何参数时,在分析串中写入选项字符,例如:-h表示获取帮助信息,显示完成即可,不需任何参数
 当一个选项后面需要带附加参数时,在分析串中写入选项字符同时后面加一个“:”号,例如:-m表示指定模块名称,后面需加模块名称

# 使用长格式分析串列表:["help", "output="]。
长格式串也可以有开关状态,即后面不跟"="号。如果跟一个等号则表示后面还应有一个参数。这个长格式表示"help"是一个开关选项;"output="则表示后面应该带一个参数。
# print(opts)
# print(args)
# 调用getopt函数。函数返回两个列表:opts和args。
# opts为分析出的格式信息,是一个两元组的列表。每个元素为:(选项串,附加参数)。如果没有附加参数则为空串''。
# args为不属于格式信息的剩余的命令行参数。
# 整个过程使用异常来包含,这样当分析出错时,就可以打印出使用信息来通知用户如何使用这个程序。

# 以下部分即根据分析出的结果做相应的处理,并将处理结果返回给用户
for cmd, arg in opts:  # 使用一个循环,每次从opts中取出一个两元组,赋给两个变量。cmd保存选项参数,arg为附加参数。接着对取出的选项参数进行处理。
    if cmd in ("-h", "--help"):
        print("help info")
        sys.exit()
    elif cmd in ("-o", "--output"):
        output = arg
    elif cmd in ("-m", "--module"):
        module_name = arg
    elif cmd in ("-a", "--module_args"):
        module_args = arg
    elif cmd in ("-v", "--version"):
        print("%s version 1.0" % sys.argv[0])
import getopt  
    import sys  
      
    config = {  
        "input":"",  
        "output":".",  
          
    }  
      
    #getopt三个选项,第一个一般为sys.argv[1:],第二个参数为短参数,如果参数后面必须跟值,须加:,第三个参数为长参数  
    #是一个列表,  
    opts, args = getopt.getopt(sys.argv[1:], 'hi:o:d',   
          [  
            'input=',   
            'output=',   
            'help'  
            ]  
          )  
      
    #参数的解析过程,长参数为--,短参数为-  
    for option, value in opts:  
        if  option in ["-h","--help"]:  
            print """  
            usage:%s --input=[value] --output=[value]  
            usage:%s -input value -o value  
            """  
        elif option in ['--input', '-i']:  
            config["input"] = value  
        elif option in ['--output', '-o']:  
            config["output"] = value  
        elif option == "-d":  
            print "usage -d"  
      
    print config   
# 结果
输入的参数:--input=c:\temp\aa -o c:\temp\output -d
打印的结果:
usage -d
{'input': 'c:\\temp\\aa', 'output': 'c:\\temp\\output'}

argparse

argparse简介

argparse是Python用于解析命令行参数和选项的标准模块,用于代替已经过时的optparse模块。argparse模块的作用是用于解析 命 令行参数,例如python parseTest.py input.txt output.txt –user=name –port=8080。

argparse讲解

将以下代码保存为prog.py

import argparse

parser = argparse.ArgumentParser(description='Process some integers.')   # 首先创建一个ArgumentParser对象
parser.add_argument('integers', metavar='N', type=int, nargs='+',           # 添加参数
                    help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
                    const=sum, default=max,
                    help='sum the integers (default: find the max)')     # 添加--sum则返回所有数之和,否则返回列表中的最大值

args = parser.parse_args()
print args.accumulate(args.integers)
$ python prog.py -h
usage: prog.py [-h] [--sum] N [N ...]

Process some integers.

positional arguments:
 N           an integer for the accumulator

optional arguments:
 -h, --help  show this help message and exit
 --sum       sum the integers (default: find the max)

# 输入正确的参数信息
$ python prog.py 1 2 3 4
4

$ python prog.py 1 2 3 4 --sum
10

# 输入错误的参数信息
$ python prog.py a b c
usage: prog.py [-h] [--sum] N [N ...]
prog.py: error: argument N: invalid int value: 'a'

ArgumentParser对象的参数

prog - The name of the program (default: sys.argv[0])
usage - The string describing the program usage (default: generated from arguments added to parser)
description - Text to display before the argument help (default: none)
epilog - Text to display after the argument help (default: none)
parents - A list of ArgumentParser objects whose arguments should also be included
formatter_class - A class for customizing the help output
prefix_chars - The set of characters that prefix optional arguments (default: ‘-‘)
fromfile_prefix_chars - The set of characters that prefix files from which additional arguments should be read (default: None)
argument_default - The global default value for arguments (default: None)
conflict_handler - The strategy for resolving conflicting optionals (usually unnecessary)
add_help - Add a -h/–help option to the parser (default: True)
每个参数的详细说明:
prog:即运行程序的名称,默认取sys.argv[0]的值,可以修改为指定的名称
usage:指定usage信息的内容及格式,默认是根据你添加的信息自动生成的,可以自定义修改为指定的内容
description:这个程序的功能概述
epilog:在整个帮助信息最后添加的附加信息
parents:用于定义一些公共的信息供子类调用
formatter_class:整个信息的类定义,主要包含三个类,每个类的功能如下:
  • class argparse.RawDescriptionHelpFormatter # 如何显示文本描述
  • class argparse.RawTextHelpFormatter # 如何显示文本描述
  • class argparse.ArgumentDefaultsHelpFormatter # 自定添加参数信息
prefix_chars:自定义参数个前缀类型
fromfile_prefix_chars:指定文件替代命令行输入参数的方式
argument_default:
conflict_handler:检测是否存在相同的参数选项,不加该参数直接报错,添加该参数可以兼容(conflict_handler='resolve') 
add_help:  设置是否显示帮助信息那表之类(默认显示)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo', help='foo help')
args = parser.parse_args()

$ python myprogram.py --help
usage: myprogram.py [-h] [--foo FOO]

optional arguments:
 -h, --help  show this help message and exit
 --foo FOO   foo help
>>> parser = argparse.ArgumentParser(prog='PROG', add_help=False)
>>> parser.add_argument('--foo', help='foo help')
>>> parser.print_help()
usage: PROG [--foo FOO]

optional arguments:
 --foo FOO  foo help

add_argument()方法    

格式: ArgumentParser.add_argument(name or flags…[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])

  • name or flags – Either a name or a list of option strings, e.g. foo or -f, –foo.    # 参数选项列表
  • action – The basic type of action to be taken when this argument is encountered at the command line.
  • nargs – The number of command-line arguments that should be consumed.
  • const – A constant value required by some action and nargs selections.
  • default – The value produced if the argument is absent from the command line.
  • type – The type to which the command-line argument should be converted.
  • choices – A container of the allowable values for the argument.
  • required – Whether or not the command-line option may be omitted (optionals only).
  • help – A brief description of what the argument does.
  • metavar – A name for the argument in usage messages.
  • dest – The name of the attribute to be added to the object returned by parse_args().
# 基本认识,只有就基本框架
import argparse

parser = argparse.ArgumentParser()
parser.parse_args()

# 结果
C:\Users\Administrator\Desktop>python3 getopt_help.py -h
usage: getopt_help.py [-h]

optional arguments:
  -h, --help  show this help message and exit

C:\Users\Administrator\Desktop>python3 getopt_help.py foo
usage: getopt_help.py [-h]
getopt_help.py: error: unrecognized arguments: foo

C:\Users\Administrator\Desktop>python3 getopt_help.py -f
usage: getopt_help.py [-h]
getopt_help.py: error: unrecognized arguments: -f

# 结果分析
1)不给参数而运行这个程序,将不会得到任何结果
2)没有做任何特别设置,可以得到一个很简洁的帮助信息(第二三个例子)
3)无需人为设置--help参数,就能得到一个良好的帮助信息。但是若给其他参数(比如foo)就会产生一个错误。

 其他

if len(sys.argv) == 1:
     parser.print_help()
     sys.exit(1)

 

异步使用线程池与进程池

技术搬运工阅读(101)

Concurrent.futures 这个模块可以和异步连接,具有线程池和进程池。管理并发编程,处理非确定性的执行流程,同步功能。

使用 requests 的异步

目标文章:http://www.budejie.com

代码如下:

import asyncio, requests,aiohttp
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# ThreadPoolExecutor :线程池
# ProcessPoolExecutor:进程池
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
​
headers = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36',
}
​
def crawl(i):
  url = f'http://www.budejie.com/{i}'
  try:
    html = requests.get(url, headers=headers)
    if html.status_code == 200:
      soup = BeautifulSoup(html.text, 'lxml')
      lis = soup.select(".j-r-list ul li div .u-txt a")
      for li in lis:
        print(li.get_text())
    return "ok"
  except RequestException:
    return None
​
async def main():
  loop = asyncio.get_event_loop() # 获取循环事件
  tasks = []
  with ThreadPoolExecutor(max_workers=10)as t:
    # 10 个线程,10 个任务
    for i in range(1, 10):
      tasks.append(loop.run_in_executor(t, crawl, i))
  #     task.append(loop.run_in_executor(放入你的线程,爬虫函数,爬虫函数参数)
​
  # 以下代码可以不写
  # await asyncio.wait(tasks)
  # for result in await asyncio.wait(tasks):
  #   print(result)# 当你执行的爬虫函数有返回信息时使用
  #   pass
​
if __name__ == '__main__':
  start_time = time.time()
  loop = asyncio.get_event_loop()
  loop.run_until_complete(main())
  loop.close()
  print(time.time() - start_time)

编写程序测试时间,建议不要同时运行,注释掉其他运行方法再运行:

import asyncio, requests,aiohttp
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
​
headers = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36',
}
​
def crawl(i):
  url = f'http://www.budejie.com/{i}'
  try:
    html = requests.get(url, headers=headers)
    if html.status_code == 200:
      soup = BeautifulSoup(html.text, 'lxml')
      lis = soup.select(".j-r-list ul li div .u-txt a")
      for li in lis:
        pass
      #   print(li.get_text())
    return "ok"
  except RequestException:
    return None
​
if __name__ == '__main__':
  start_time_1 = time.time()
  for i in range(1, 10):
    crawl(i)
  print("单线程时间:>>>", time.time() - start_time_1)
​
  start_time_2 = time.time()
  with ThreadPoolExecutor(max_workers=10)as t:
    for i in range(1, 10):
      t.submit(crawl, i)
  print("线程池时间:>>>", time.time() - start_time_2)
​
  start_time_3 = time.time()
  with ProcessPoolExecutor(max_workers=10)as t:
    for i in range(1, 10):
      t.submit(crawl, i)
  print("进程池时间:>>>", time.time() - start_time_3)

我们来分析一下输出结果,我们会分析进程池花费的时间会比线程池更多,这是为什么呢?

多线程非常适合 I/O 密集型,不适合 CPU 密集型;

进程池创建销毁的资源开销大,创建一个进程所耗费的资源要比创建一个线程耗费的时间大很多,销毁它也需要很长的时间。(准备工作非常多)

def rsynctask( tasks):
"""顺序执行协程任务"""
    new_ loop = asyncio.new event_ loop( )
    asyncio. set_ event_ loop( new_ 1oop)
    loop = asyncio.get_ event_ 1oop( )
    loop. run_ until_ complete( asyncio. gather( * tasks))
    loop.run_ until_ complete (1oop . shutdown_ asyncgens( ))
    loop. close( )
def rsynctaskwait(tasks):
"""并行执行协程任务"""
    new_ loop = asyncio.new event_ loop( )
    asyncio. set_ event_ 1oop(new_ 1oop)
    loop =
    asyncio.get_ event_ 1oop( )
    loop. run_ until complete( asyncio. wait(tasks))
    loop.run_ until_ complete (1oop . shutdown_ asyncgens( ))
    loop. close( )

 

python 携程

技术搬运工阅读(78)

​1. 初探
在了解异步协程之前,我们首先得了解一些基础概念,如阻塞和非阻塞、同步和异步、多进程和协程。

1.1 阻塞
阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续处理其他的事情,则称该程序在该操作上是阻塞的。

常见的阻塞形式有:

网络 I/O 阻塞

磁盘 I/O 阻塞

用户输入阻塞等。

阻塞是无处不在的,包括 CPU 切换上下文时,所有的进程都无法真正处理事情,它们也会被阻塞。如果是多核 CPU 则正在执行上下文切换操作的核不可被利用。

1.2 非阻塞
程序在等待某操作过程中,自身不被阻塞,可以继续处理其他的事情,则称该程序在该操作上是非阻塞的。

非阻塞并不是在任何程序级别、任何情况下都可以存在的。仅当程序封装的级别可以囊括独立的子程序单元时,它才可能存在非阻塞状态。

非阻塞的存在是因为阻塞存在,正因为某个操作阻塞导致的耗时与效率低下,我们才要把它变成非阻塞的。

1.3 同步
不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,我们称这些程序单元是同步执行的。

例如购物系统中更新商品库存,需要用“行锁”作为通信信号,让不同的更新请求强制排队顺序执行,那更新库存的操作是同步的。

简言之,同步意味着有序。

1.4 异步
为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式,不相关的程序单元之间可以是异步的。

例如,爬虫下载网页。调度程序调用下载程序后,即可调度其他任务,而无需与该下载任务保持通信以协调行为。不同网页的下载、保存等操作都是无关的,也无需相互通知协调。这些异步操作的完成时刻并不确定。

简言之,异步意味着无序。

1.5 多进程
多进程就是利用 CPU 的多核优势,在同一时间并行地执行多个任务,可以大大提高执行效率。

1.6 协程
协程,英文叫作 Coroutine,又称微线程、纤程,协程是一种用户态的轻量级线程。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重入时,就相当于进入上一次调用的状态。

协程本质上是个单进程,协程相对于多进程来说,无需线程上下文切换的开销,无需原子操作锁定及同步的开销,编程模型也非常简单。

我们可以使用协程来实现异步操作,比如在网络爬虫场景下,我们发出一个请求之后,需要等待一定的时间才能得到响应,但其实在这个等待过程中,程序可以干许多其他的事情,等到响应得到之后才切换回来继续处理,这样可以充分利用 CPU 和其他资源,这就是协程的优势。

1.7 协程相对于多线程的优点
多线程编程是比较困难的, 因为调度程序任何时候都能中断线程, 必须记住保留锁, 去保护程序中重要部分, 防止多线程在执行的过程中断。

而协程默认会做好全方位保护, 以防止中断。我们必须显示产出才能让程序的余下部分运行。对协程来说, 无需保留锁, 而在多个线程之间同步操作, 协程自身就会同步, 因为在任意时刻, 只有一个协程运行。总结下大概下面几点:

无需系统内核的上下文切换,减小开销;

无需原子操作锁定及同步的开销,不用担心资源共享的问题;

单线程即可实现高并发,单核 CPU 即便支持上万的协程都不是问题,所以很适合用于高并发处理,尤其是在应用在网络爬虫中。

2. 协程用法
接下来,我们来了解下协程的实现,从 Python 3.4 开始,Python 中加入了协程的概念,但这个版本的协程还是以生成器对象为基础的,在 Python 3.5 则增加了 async/await,使得协程的实现更加方便。

Python 中使用协程最常用的库莫过于 asyncio,所以本文会以 asyncio 为基础来介绍协程的使用。

首先我们需要了解下面几个概念。

event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法。

coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。

task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。

future:代表将来执行或没有执行的任务的结果,实际上和 task 没有本质区别。

另外我们还需要了解 async/await 关键字,它是从 Python 3.5 才出现的,专门用于定义协程。其中,async 定义一个协程,await 用来挂起阻塞方法的执行。

2.1 定义协程
协程就是一个函数,只是它满足以下几个特征:

依赖 I/O 操作(有 I/O 依赖的操作)

可以在进行 I/O 操作时暂停

无法直接运行

它的作用就是对有大量 I/O 操作的程序进行加速。

Python 协程属于可等待对象,因此可以在其他协程中被等待。

什么叫可等待对象?——await,如果前面被标记 await 就表明他是个协程,我们需要等待它返回一个数据。

import asyncio
async def net():
  return 11
async def main():
  # net() # error
  await net() # right
asyncio.run(main())
​
import asyncio
async def net():
  return 11
async def main():
  # net() # error
  return await net() # right
print(asyncio.run(main()))

举个例子,我从网络上下载某个数据文件下载到我的本地电脑上,这很显然是一个 I/O 操作。比方这个文件较大(2GB),可能需要耗时 30min 才能下载成功。而在这 30min 里面,它会卡在 await 后面。这个 await 标记了协程,那就意味着它可以被暂停,那既然该任务可以被暂停,我们就把它分离出去。我这个线程继续执行其它任务,它这个 30min 分出去慢慢的传输,我这个程序再运行其他操作。

上面的代码,Python 3.6 会给你报错。报错信息如下:

Traceback (most recent call last):
  File "C:/Code/pycharm_daima/爬虫大师班/14-异步编程/test.py", line 26, in <module>
    asyncio.run(main())
AttributeError: module 'asyncio' has no attribute 'run

 

普通爬虫,多进程,携程,异步结合多进程-爬虫程序

技术搬运工阅读(84)

测试普通爬虫程序

import time
import requests
from lxml import etree

urls = [
    'http://www.shuquge.com/txt/76615/14606964.html',
    'http://www.shuquge.com/txt/76615/14606965.html',
    'http://www.shuquge.com/txt/76615/14606966.html',
    'http://www.shuquge.com/txt/76615/14606967.html',
    'http://www.shuquge.com/txt/76615/14606968.html',
    'http://www.shuquge.com/txt/76615/14606969.html',
    'http://www.shuquge.com/txt/76615/14606970.html',
    'http://www.shuquge.com/txt/76615/14606971.html'
]

def get_title(url,cnt):
    response=requests.get(url)
    html=response.content
    title = etree.HTML(html).xpath('//div[@class="content"]/h1/text()')
    print('第%d个title:%s' % (cnt, ''.join(title)))

if __name__ == '__main__':
    start1 = time.time()
    i = 0
    for url in urls:
        i = i + 1
        start = time.time()
        get_title(url,i)
        print('第%d个title爬取耗时:%.5f秒' % (i,float(time.time() - start)))
    print('爬取总耗时:%.5f秒' % float(time.time()-start1))

时间大概是4秒多

测试基于协程的异步爬虫程序

import time
import aiohttp
import asyncio
from lxml import etree

urls = [
    'http://www.shuquge.com/txt/76615/14606964.html',
    'http://www.shuquge.com/txt/76615/14606965.html',
    'http://www.shuquge.com/txt/76615/14606966.html',
    'http://www.shuquge.com/txt/76615/14606967.html',
    'http://www.shuquge.com/txt/76615/14606968.html',
    'http://www.shuquge.com/txt/76615/14606969.html',
    'http://www.shuquge.com/txt/76615/14606970.html',
    'http://www.shuquge.com/txt/76615/14606971.html',
    'http://www.shuquge.com/txt/76615/14606972.html',
    'http://www.shuquge.com/txt/76615/14606973.html',
    'http://www.shuquge.com/txt/76615/14606974.html',
    'http://www.shuquge.com/txt/76615/14606975.html'
]

tilte = []
sem = asyncio.Semaphore(10)


async def get_title(url):
    with(await sem):
        async with aiohttp.ClientSession() as session:
            async with session.request('GET', url) as resp:
                html = await resp.read()
                title = etree.HTML(html).xpath('//div[@class="content"]/h1/text()')
                print(''.join(title))


def main():
    loop = asyncio.get_event_loop()
    tasks = [get_title(url) for url in urls]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()


if __name__ == '__main__':
    start = time.time()
    main()  # 调用方
    print('总耗时:%.5f秒' % float(time.time() - start))

测试结果是1秒不到

测试基于多进程的分布式爬虫程序

import multiprocessing
from multiprocessing import Pool
import time
import requests
from lxml import etree

urls = [
    'http://www.shuquge.com/txt/76615/14606964.html',
    'http://www.shuquge.com/txt/76615/14606965.html',
    'http://www.shuquge.com/txt/76615/14606966.html',
    'http://www.shuquge.com/txt/76615/14606967.html',
    'http://www.shuquge.com/txt/76615/14606968.html',
    'http://www.shuquge.com/txt/76615/14606969.html',
    'http://www.shuquge.com/txt/76615/14606970.html',
    'http://www.shuquge.com/txt/76615/14606971.html',
    'http://www.shuquge.com/txt/76615/14606972.html',
    'http://www.shuquge.com/txt/76615/14606973.html',
    'http://www.shuquge.com/txt/76615/14606974.html',
    'http://www.shuquge.com/txt/76615/14606975.html'
]

def get_title(url,cnt):
    response=requests.get(url)
    html=response.content
    title = etree.HTML(html).xpath('//div[@class="content"]/h1/text()')
    print('第%d个title:%s' % (cnt, ''.join(title)))

def main():
    print('当前环境CPU核数是:%d核' % multiprocessing.cpu_count())
    p = Pool(4)  # 进程池
    i = 0
    for url in urls:
        i += 1
        p.apply_async(get_title, args=(url, i))
    p.close()
    p.join()   # 运行完所有子进程才能顺序运行后续程序

if __name__ == '__main__':
    start = time.time()
    main()  # 调用方
    print('总耗时:%.5f秒' % float(time.time()-start))

 

dedecms网站如何进行安全设置

技术搬运工阅读(82)

1、修改默认后台名。
打开网站根目录,找到[dede],这个文件夹就是后台的路径,可以随意修改,比如修改为[adminbuy],此时后台登陆的路径为:http://www.*****.com/adminbuy/
2、删除member文件夹(如果你没有会员功能)
Member文件夹就是会员系统,织梦本身是自带里会员系统的,大家也可以在后台找到,但是很多用户都是做了企业站,并不需要会员功能,就像AB模板网发布的基本都是企业站,所以并不需要会员系统,这时,大家就可以删除这个文件夹,删除他,不但可以防止攻击,还可减省了空间容量。
3、删除special文件夹
Special文件夹是专题的意思,AB模板网上下载的源码都没有用到这个专题页面,所以大家放心删掉好了。
4、打开plus文件夹
留下这么几个文件,其他全部删除,参考下图;

下面我们对这几个文件做下解释,

Img 文件夹,这个是主要是CSS样式在里面,所以保留,如果删除了,会造成发布文章时界面有点乱,所以要保留

ad_js.php  这个文件时广告位,因为有些模板用到了后台调用广告位,如果你不确定,建议保留

Diy.php  这个是留言系统,有些模板上有用户在线留言功能,用到的就是这个,如果你不确定,建议保留

Search.php 这个是搜索功能,也就是网站上的搜索,如果你不确定,建议保留

List.php 这动态栏目,AB模板网上下载的都是生成静态栏目了,但是有些用户喜欢动态栏目,即使你使用的是静态栏目,这个保留也没影响,所以建议保留

View.php 这个是动态文章,道理和list.php一样,建议保留。

count.php 这个是文章浏览次数,建议保留。

如果实在看不懂,就按照截图保留,其他的都删除,删除前建议备份一份。

DEDE文件夹下 删除以下文件

ad_add.php

ad_edit.php
ad_main.php
adtype_main.php
这几个是后台广告设置的文件 如果模版上没有广告位的 删除这几个(只有少数新闻博客模版有广告位)

———————————————————————————————————————————–

cards_make.php
cards_manage.php
cards_type.php
这几个是会员点卡功能  所有模版都没用到这个  直接删除

———————————————————————————————————————————–

feedback_edit.php
feedback_main.php
这是评论功能  所有模版都没用到这个  直接删除———————————————————————————————————————————–

file_class.php
file_manage_control.php
file_manage_main.php
file_manage_view.php
file_pic_view.php
这几个是附件管理 文件式管理器 这个是影响安全,很多用户在用这个功能 必须删除,改成FTP上传文件图片等

———————————————————————————————————————————–

freelist_add.php
freelist_edit.php
freelist_main.php
这几个是自由列表管理  所有模版都没用到这个  直接删除

———————————————————————————————————————————–

getdedesysmsg.php
这个是织梦官方广告   直接删除

———————————————————————————————————————————–

group_edit.php
group_guestbook.php
group_main.php
group_notice.php
group_store.php
group_threads.php
group_user.php
这几个是圈子功能  所有模版都没用到这个  直接删除

———————————————————————————————————————————–

mail_file_manage.php
mail_getfile.php
mail_send.php
mail_title.php
mail_title_send.php
mail_type.php
这几个邮件管理功能  所有模版都没用到这个  直接删除

———————————————————————————————————————————–

mda_main.php
这个是织梦官方广告   直接删除

———————————————————————————————————————————–

media_add.php
media_edit.php
media_main.php
这几个是上传文件 这个是影响安全,很多用户在用这个功能 必须删除,改成FTP上传文件图片等

———————————————————————————————————————————–

member_do.php
member_feed_edit.php
member_guestbook.php
….
所有的 member_开头的  这些是会员注册等  模版中只有二个是带会员的  其他的都没带,如果不是那两个  这些都删除  带会员的模版:模板一模板二

———————————————————————————————————————————–

mynews_add.php
mynews_edit.php
mynews_main.php
这几个是站内新闻 所有模版都没用到这个  直接删除

———————————————————————————————————————————–

mytag_add.php
mytag_edit.php
mytag_main.php
mytag_tag_guide.php
mytag_tag_guide_ok.php
这几个是自定义标记 所有模版都没用到这个  直接删除

———————————————————————————————————————————–

shops_delivery.php
shops_operations.php
shops_operations_cart.php
shops_operations_userinfo.php
订单功能  也是带会员的才会用到  模版中只有二个是带会员的  其他的都没带,如果不是那两个  这些都删除  带会员的模版:模板一模板二

———————————————————————————————————————————–

spec_add.php
spec_edit.php
这几个专题功能 所有模版都没用到这个  直接删除

———————————————————————————————————————————–

story_add.php
story_add_action.php
….
所有的 story_ 开头的  这些都是小说功能  所有模版都没用到这个  直接删除

———————————————————————————————————————————–

vote_add.php
vote_getcode.php
vote_edit.php
vote_main.php
这几个是投票功能  所有模版都没用到这个  直接删除

———————————————————————————————————————————–

再有:
不需要SQL命令运行器的将dede/sys_sql_query.php 文件删除。

三、删除根目录下的install 文件夹,这是安装目录,因为我们都安装好了,所以这个没用了,删除即可。

这里要提醒一下大家,由于我们修改了内容,所以当你网站要搬家换服务器时,这时,你把网站传到了新的空间,这时你需要再次安装,发现安装文件[install]上次删除了,不用担心,直接去官方下载一个网站的系统,复制的他的install 传到你的空间,进行安装即可,但是你要知道自己网站的编码(gbk、utf)。不然安装界面会乱码。

另外,由于我们修改后台名等其他内容,所以安装时候会出现这样的情况,这个不用担心,只是系统没找到我们这个文件夹,因为我们修改了,大家直接下一步即可,但是如果你发现全部都是红色叉叉,那就是你空间权限问题了。

四、修改用户名和密码,修改密码很简单,后台-系统-系统用户管理,点更改,然后修改即可,但是用户名默认的是admin,大家发现不能修改用户名,其实是可以的,大家按下图操作:

找到功能地图,

(如果你是VIP,AB模板网隐藏了这一链接,大家只要直接输入链接即可:sys_data_replace.php    如:http://www.adminbuy.cn/dede/sys_data_replace.php)

找到数据库内容替换,
新的用户名小心填错,以免不知道用户名,进不去后台。修改后,在后台退出,然后重新进后台,用新的用户名登录。

ubuntu 18.04 netplan yaml配置固定IP地址

技术搬运工阅读(72)

ubuntu从17.10开始,已放弃在/etc/network/interfaces里固定IP的配置,即使配置也不会生效,而是改成netplan方式 ,配置写在/etc/netplan/01-netcfg.yaml或者类似名称的yaml文件里,18.04的server版本安装好以后,配置文件是:/etc/netplan/50-cloud-init.yaml,修改配置以后不用重启,执行 netplan apply 命令可以让配置直接生效。以前的重启网络服务命令/etc/init.d/networking restart或者services network restrart也都会提示为无效命令。
$sudo nano /etc/netplan/50-cloud-init.yaml,配置文件可按如下内容修改。
network:
  version: 2
  renderer: networkd
  ethernets:
    ens33:   #配置的网卡名称
      dhcp4: no    #dhcp4关闭
      dhcp6: no    #dhcp6关闭
      addresses: [192.168.1.55/24]   #设置本机IP及掩码
      gateway4: 192.168.1.254   #设置网关
      nameservers:
          addresses: [114.114.114.114, 8.8.8.8]   #设置DNS

 

注意点:
1.以上配置文件共11行,其中第2,3,6,7四行可以不写,测试过没有这四行,网络也能工作正常,第5行的ens33为虚拟网卡,可以使用ifconfig -a查看本机的网卡。
2.配置文件里在冒号:号出现的后面一定要空一格,不空格则在运行netplan apply时提示出错。
3.关键之关键是看清配置总共分为五个层次,逐层向后至少空一格,
第一层-network:
第二层-- ethernets:
第三层--- ens33:
第四层----addresses:  [192.168.1.55/24]
第四层----gateway4:  192.168.1.254
第四层----nameservers:
第五层-----addresses: [114.114.114.114, 8.8.8.8]
出现类似错误:line8 column 6:cloud not find expected ‘:’  #提示是冒号:后面没加空格
出现类似错误:netplan found character that cannot start any token,#提示是没有按五个层次写配置文档,一定要下一层比上一层多空一格或以上。

重启网络:

sudo netplan apply

 

批处理查找畸形文件并删除

技术搬运工阅读(69)

::查找指定目录下畸形文件和目录,并删除
@echo off
setlocal enabledelayedexpansion
title 畸形文件查杀
::文件匹配正则
set "reg=\\nul \\nul\.[^\\]* \\com[1-9] \\com[1-9]\.[^\\]* \\aux \\aux\.[^\\]* \\con \\con\.[^\\]* \\prn \\prn\.[^\\]* \."
::设置窗口颜色
color 6b

::检测路径是否存在
:checkexist
set /p dir=请输入需要查杀的路径:
if "%dir%" =="" (
echo 输入为空,请重新输入
call :checkexist
)else (
call :listdir
)


::列出畸形文件名和所在目录
:listdir
cd /d %dir%
for /f "delims=" %%i in ('dir /b /a /s ^| findstr /i /r /e "%reg%"') do (
    echo  "%%i"
    rd /s /q "\\?\%%i"
    del /q /f /a "\\?\%%i"
    echo 已经删除
    pause
)







 

我们不生产技术 我们只是技术的搬运工