目录
1.1 类似字典的操作方式,__getitem__,__setitem__,__delitem__
1.3 __iter__,next(python2),__next__(python3)
1.7 __setattr__,__getattr__,__delattr__
1.8 __le__,__lt__,__ge__,__gt__,__ne__,__eq__
1 内建函数(__xxx__)
背景:为什么要有这种带下划线的内建函数,个人认为这种内建函数开放了很多Python的特殊用法,只要详尽掌握,就会理解平时用到的数据结构是复写了什么方法,自己也可以写出类似set,dqueue,dict,list的数据类型方法。
1.1 类似字典的操作方式,__getitem__,__setitem__,__delitem__
我们熟悉的字典是可以像如下方式操作赋值取值:
d = dict()
d["a"] = 1
d["b"] = 2
print d
del d["a"]
print d
其实像这种赋值方式就是复写了__setitem__,__getitem__,__delitem__请见如下类
class TestDict(object):
def __getitem__(self,key):
return self.__dict__.get(key)
def __setitem__(self,key,value):
self.__dict__[key] = value
def __delitem__(self,key):
self.__dict__.pop(key)
td = TestDict()
td["a"] = 1
td["b"] = 2
print td["a"]
print td.__dict__
del td["a"]
print td.__dict__
为了更加灵活,如上代码也可以在对象执行赋值方法(__init__)的时候声明个变量d = dict(),然后对这个变量进行赋值和取值操作也可以模拟上面的操作。
1.2 __new__和__init__
__new__: 在类实例化的时候调用,用来创建实例,如果不返回实例那么__init__将不会执行,第一个参数是class对象,在创建实例的时候需要有返回值
__init__: 在初始化实例的时候调用,比如说实例属性赋值,第一个参数是实例对象,一般都重写__init__方法,在执行的时候不需要返回值
class TestNew(object):
def __new__(cls, *args, **kwargs):
print '__new__ called.'
return super(TestNew,cls).__new__(cls,*args,**kwargs)
def __init__(self):
print '__init__ called.'
self.a = 1
tn = TestNew()
print tn.a
可以用__new__来实现单例模式
class Singleton(object):
def __new__(cls):
# 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
obj1 = Singleton()
obj2 = Singleton()
obj1.attr1 = 'value1'
print obj1.attr1, obj2.attr1
print obj1 is obj2
Tips: 单例模式有很多种实现方式,也可以通过类变量+静态方法的方式实现。
可以通过重载__new__来实现很多创建实例时的功能。
1.3 __iter__,next(python2),__next__(python3)
__iter__: 复写这个函数的对象是可迭代对象
next/__next__: 复写这个函数的对象都是一个迭代器
class TestIterNext(object):
def __init__(self,data=1):
self.data = data
def next(self):
if self.data > 5:
raise StopIteration
else:
self.data+=1
return self.data
def __iter__(self):
print "iter"
return self
当for循环去迭代tin = TestIterNext()对象的时候第一步会去看__iter__是否返回一个生成器(generator),如果返回的是对象本身才会去执行next函数。
1.4 __call__
把一个类实例的对象当做函数一样调用就是复写了__call__方法,如下:
class TestCall(object):
def __call__(self):
print "call it"
tc = TestCall()
tc()
复写__call__方法大多数用在装饰器类中(第5章)和继承Type类(第3章)的时候。
1.5 __repr__,__str__
__repr__和__str__没有太大区别,在Python交互模式下才能发现。复写改方法后输出对象就是按照该方法里的内容进行输出。在类里可以这样用__repr__ = __str__
1.6 __all__
可用于模块导入时限制,,当我们from module import *的时候这个__all__就起作用了,__all__=["bar","sar"] ,[]里定义函数或者变量类等,有些模块内部一些函数不对外开放,此时把一些对外开放的函数变量放入到__all__里就可以了,这样避免了一些多余的导入。如果在__init__.py里定义则在导入模块的时候只导入__all__里定义的各个文件,此时无法定义到文件里具体哪个类或方法,如果需要细化则需要在具体的类里写入__all__。如果是from module import Test这种使用不受__all__限制
1.7 __setattr__,__getattr__,__delattr__
__setattr__: 对变量赋值时调用。
__getattr__:默认查找对象属性是在一个字典里(__dict__),这里没有要查找的对象则去__getattr__方法里查找,如果我们复写__getattr__则可以根据实际需求来返回值。
__delattr__:删除属性时调用。
class TestAttr(object):
def __init__(self):
self.name = "abc"
def __getattr__(self, item):
print "item:" + str(item)
print "getattr"
return 10
def __setattr__(self, *args, **kwargs):
print "set attr"
object.__setattr__(self,*args,**kwargs)
def __delattr__(self, *args, **kwargs):
print "delete attr"
object.__delattr__(self,*args, **kwargs)
ta = TestAttr()
print ta.__dict__
print ta.names
del ta.name
print ta.__dict__
1.8 __le__,__lt__,__ge__,__gt__,__ne__,__eq__
一个对象和另一个对象比较大小,返回的并不一定是True和False,返回值有可能是我们定义的任何值,这里就是复写上面这些方法。如下:
class TestCompare(object):
def __lt__(self, other):
return "aaa"
t = TestCompare()
print(t<1)
这里打印出的就不是True和False,打印的是我们再复写的方法里定义的"aaa",其实有一些常用的orm,比如说sqlalchemy里查询条件可以这样写
g.pg_db.query(Company.id).filter(Company.level_id == level)
这里的Company.level_id == level返回的就是筛选条件,因为对Company的level_id对象复写了__eq__
1.9 __slots__
优点:
1,更快的属性访问速度
2,减少内存消耗
每个类里都维护一个字典__dict__,这个字典维护了对象的所有属性,但如果成千上万个对象则就会创建很多个__dict__来存放对象属性,为了性能我们可以不用Python帮我们维护这个字典。此时我们在类里定义__slots__ = ["name","age"]的时候就表示禁用了__dict__,并限定name和age为类的属性(类里只能有name和age属性),这样做的好处能大大节省内存开支,对象越多节省的就越多,大概能节省40%以上。
class TestSlots(object):
__slots__ = ["name","age"]
def __init__(self, name, age):
self.name = name
self.age = age
ts = TestSlots("a",1)
ts.name = 1
print ts.name
1.10 __metaclass__
在理解元类之前,你需要先掌握Python中的类。Python中类的概念借鉴于Smalltalk,这显得有些奇特。在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立
1.10.1 元类是什么?
但就元类本身而言,它们其实是很简单的:
1) 拦截类的创建
2) 修改类
3) 返回修改之后的类
但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。下面的代码段:
class ObjectCreator(object):
pass
将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:
1) 你可以将它赋值给一个变量
2) 你可以拷贝它
3) 你可以为它增加属性
4) 你可以将它作为函数参数进行传递
下面是示例:
print ObjectCreator
# 你可以打印一个类,因为它其实也是一个对象
def echo(o):
print o
echo(ObjectCreator)
# 你可以将类做为参数传给函数
print hasattr(ObjectCreator, 'new_attribute')
ObjectCreator.new_attribute = 'foo'
# 你可以为类增加属性
print hasattr(ObjectCreator, 'new_attribute')
print ObjectCreator.new_attribute
ObjectCreatorMirror = ObjectCreator
# 你可以将类赋值给一个变量
print ObjectCreatorMirror()
1.10.2 动态地创建类
因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用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
# 函数返回的是类,不是类的实例>>> print MyClass()
# 你可以通过这个类创建类实例,也就是对象
# <__main__.foo object="" at="" 0x89c6d4c="">
但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。还记得内建函数type吗?这个古老但强大的函数能够让你知道一个对象的类型是什么,就像这样:
print type(1)
print type("1")
print type(ObjectCreator)
print type(ObjectCreator())
这里,type有一种完全不同的能力,它也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。(我知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)
type可以像这样工作:
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
比如下面的代码:
class MyShinyClass(object):
pass
MyShinyClass = type('MyShinyClass', (), {})
# 返回一个类对象
print MyShinyClass
print MyShinyClass()
# 创建一个该类的实例
# <__main__.myshinyclass object="" at="" 0x8997cec="">
你会发现我们使用“MyShinyClass”作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。
type 接受一个字典来为类定义属性,因此
class Foo(object):
bar = True
# 可以翻译为:
Foo = type('Foo', (), {'bar':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在幕后做的事情,而这就是通过元类来实现的。
1.10.3 到底什么是元类
元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。好吧,元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:
MyClass = MetaClass()
MyObject = MyClass()
你已经看到了type可以让你像这样做:
MyClass = type('MyClass', (), {})
这是因为函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查__class__属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来。
>>> age = 35
>>> age.__class__
>>> name = 'bob'
>>> name.__class__
>>> def foo(): pass
>>>foo.__class__
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
现在,对于任何一个__class__的__class__属性又是什么呢?
>>> a.__class__.__class__
>>> age.__class__.__class__
>>> foo.__class__.__class__
>>> b.__class__.__class__
因此,元类就是创建类这种对象的东西。如果你喜欢的话,可以把元类称为“类工厂”(不要和工厂类搞混了:D) type就是Python的内建元类,当然了,你也可以创建自己的元类。
1.10.4 metaclass__属性
你可以在写一个类的时候为其添加__metaclass__属性。
class Foo(object):
__metaclass__ = something…
[…]
如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类对象Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类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的东东都可以。
1.10.5 自定义元类
元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。
幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类(我知道,某些名字里带有‘class’的东西并不需要是一个class,画画图理解下,这很有帮助)。所以,我们这里就先以一个简单的函数作为例子开始。
# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
'''返回一个类对象,将属性都转为大写形式'''
# 选择所有不以'__'开头的属性
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr
# 这会作用到这个模块中的所有类
class Foo(object):
# 我们也可以只在这里定义__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):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
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):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 复用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, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return type.__new__(cls, name, bases, uppercase_attr)
如果使用super方法的话,我们还可以使它变得更清晰一些,这会缓解继承(是的,你可以拥有元类,从元类继承,从type继承)
class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
用元类实现单例模式
class Singleone(type):
def __init__(cls, name,bases,dct):
super(Singleone, cls).__init__(name,bases,dct)
cls._instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(Singleone,cls).__call__(*args,**kwargs)
return cls._instance
class One(object):
__metaclass__ = Singleone
def __init__(self,*args, **kwargs):
self.name = args[0]
o = One("a")
print (o.name)
b = One("b")
print(b.name)
执行步骤从上到下当执行用Singleone创建One类,One = Singleone(...),当实例化Singleone的时候执行其内部的__init__()方法,代码
继续解析到o = One("a")的时候示例化了One类,相当于Singleone()(),此时执行Singleone里面的__call__方法,在call方法里需要有
返回值。也可以在__call__里主动实例化(__new__)One并调用里面的__init__方法。
就是这样,除此之外,关于元类真的没有别的可说的了。使用到元类的代码比较复杂,这背后的原因倒并不是因为元类本身,而是因为你通常会使用元类去做一些晦涩的事情,依赖于自省,控制继承等等。确实,用元类来搞些“黑暗魔法”是特别有用的,因而会搞出些复杂的东西来。
为什么要用metaclass类而不是函数?
由于__metaclass__可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?这里有好几个原因:
1) 意图会更加清晰。当你读到UpperAttrMetaclass(type)时,你知道接下来要发生什么。
2) 你可以使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。
3) 你可以把代码组织的更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
4) 你可以使用__new__, __init__以及__call__这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__里处理掉,有些人还是觉得用__init__更舒服些。
5) 哇哦,这东西的名字是metaclass,肯定非善类,我要小心!
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters
元类的主要用途是创建API。一个典型的例子是Django ORM。它允许你像这样定义:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
这并不会返回一个IntegerField对象,而是会返回一个int,甚至可以直接从数据库中取出数据。这是有可能的,因为models.Model定义了__metaclass__, 并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。
结语
首先,你知道了类其实是能够创建出类实例的对象。好吧,事实上,类本身也是实例,当然,它们是元类的实例。
Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:
1) Monkey patching(详情请查看28)
2) class decorators
当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类
2 私有变量(__xx)
python类里的私有变量就是前面加两个下划线这样用,但是这只是在使用上的私有变量,不像Java那种只能通过内部函数修改,python的私有变量可以通过 对象._类名__参数来从外部引用。
3 type
请查看 1.20
4 推导式
推导式又称解析式,有三种
1,列表推导式
multiples = [ i for i in range(30) if i % 3 is 0 ]
2,字典推导式
mcase = {"a":10,"b":2,"c":3}
{k:v for k,v in mcase.items()}
3,集合推导式
其实大括号里扩着的就是集合(set),例:
{"a","b",1}
squared = {x*2 for x in [1,2,3]}
5 装饰器(@decorate)
装饰器是python特色代表之一,非常好用,先介绍一下如何用装饰器。
5.1函数是可以返回函数
在if/else里面我们返回greet和welcome,而不是greet()和welcome(),为什么? 是因为当把小括号放到后面的时候这个函数就会执行,如果不放小括号这个函数就可以到处传递,并且可以赋给变量而不去执行。
将函数作为参数传递给另一个函数
def hi():
return "hi yasoob"
def doSomethingBefore(func):
print "I am doing something before"
print (func())
doSomethingBefore(hi)
输出:
I am doing something before
hi yasoob
装饰器就是在一个函数前后执行代码
上个例子里我们相当于创建了装饰器,现在我们稍加修改并编写一个更有用的程序。
def a_new_decorator(a_func):
def wrapTheFunction():
print "I am doing some before"
a_func()
print "I am doing some after"
return wrapTheFunction
def a_function_requiring_decoration():
print "I am in the function"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
a_function_requiring_decoration()
明白了吗? 这正是python装饰器做的事情,它们封装一个函数,并且用这样或者那样的方式修改它的行为,现在你可能疑惑,我们的代码里并没有使用@符号?那只是一个简短的方式来生成一个被装饰的函数。请见如下例子
@a_new_decorator
def a_function_requiring_decoration():
print "I am in the function"
a_function_requiring_decoration()
现在对装饰器的理解差不多了吧!但如果我们运行如下代码会存在一个问题:
print(a_function_requiring_decoration.__name__)
输出:wrapTheFunction
这并不是我们想要看到的,我们想看到的是a_function_requiring_decoration,这里的函数被wrapTheFunction替代了,它重写了我们函数的名字和注释文档(docstring)。幸运的是python提供给我们一个简单的函数来解决这个问题
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print "I am doing some before"
a_func()
print "I am doing some after"
return wrapTheFunction
下面我们看一下蓝本规范:
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return "Function is running"
can_run = True
print(func())
can_run = False
print(func())
注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称,注释文档,参数列表等等的功能。这可以让我们在装饰器里面访问在装饰器之前的函数的属性。
5.2 装饰器的使用场景:
5.2.1 授权(Authorization)
装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django框架中。这里是一个例子来使用基于装饰器的授权:
from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username,auth.password):
authenticate()
return f(*args,**kwargs)
return decorated
5.2.2 日志(Logging)
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
return x+x
addition_func(2)
5.3 在函数中嵌入装饰器
我们回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件。
from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
with open(logfile, 'wb') as f:
f.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit
def myfunc():
pass
5.4 装饰器类
现在我们有了能用于正式环境的logit装饰器,但当我们的应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。比方说有时候你只想打日志到一个文件,而有时你想把引起你注意的问题发送到一个email,同事也保留日志,留个记录。这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。
The lucky is! 类也可以构建装饰器,现在我们用类重新构建logit
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
with open(self.logfile, 'wb') as f:
f.write(log_string + '\n')
self.notify()
return func(*args,**kwargs)
return wrapped_function
def notify(self):
# 可以做一些其它行为
pass
@logit()
def my_func():
pass
现在我们给logit创建子类,来添加email等功能
class email_logit(logit):
def __init__(self, email='admin@123.com', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# 发送一封email
pass
从现在起,@email_logit会在logit基础上多发送一封邮件。
注意:从以上方法中我们就可以发现__call__这种用法的好处,它在装饰器类和新写元类的时候起到了很大作用。
6 容器
python附带一个模块,它包含许多容器数据类型,名字叫做collections。我们将讨论它的作用和用法。
6.1 defaultdict:
defaultdict不需要检查key是否存在,我们一般这样用
from collections import defaultdict
ddl = defaultdict(list)
ddl["x"].append(1)
print ddl
ddd = defaultdict(dict)
ddd["x"]["a"] = 1
print ddd
defaultdict(<type 'list'>, {'x': [1]})
defaultdict(<type 'dict'>, {'x': {'a': 1}})
6.2 Counter
counter是一个计数器,帮助我们对某项数据做统计。
from collections import Counter
c = Counter("aaaabbbc")
print c
d = {"a":1,"b":2,"c":3}
c = Counter( k for k,v in d.items())
print c
还可以用counter来统计一个文件
此处没有弄明白,需要后期补上
6.3 deque
deque提供了一个双向队列,可以从头尾两端添加或删除元素,类似于list
from collections import deque
dl = deque(range(5))
print dl
dl.popleft()
print dl
dl.pop()
print dl
dl.extendleft([-10])
print dl
dl.extend([10])
print dl
输出:
deque([0, 1, 2, 3, 4])
deque([1, 2, 3, 4])
deque([1, 2, 3])
deque([-10, 1, 2, 3])
deque([-10, 1, 2, 3, 10])
deque也可以限制列表的大小,先进先出
dl = deque(maxlen=2)
dl.append(1)
dl.append(2)
print dl
dl.append(3)
print dl
输出
deque([1, 2], maxlen=2)
deque([2, 3], maxlen=2)
6.4 namedtuple(命名元组)
正常访问一个元组和访问list一样,都是通过下标来访问,命名元组可以提供类似于字典的访问方式,和tuple一样不可变。
from collections import namedtuple
Animal = namedtuple('Animal','name age type')
perry = Animal(name='perry',age=10,type='cat')
print perry
print perry.name
一个命名元组需要两个参数,他们是元组名称和字段名称。在上面的例子中,我们的元组名称是Animal,字段名称是'name,age,type'。
namedtuple让你的元组变得自文档了。不必使用证书索引来访问一个命名元组,这让代码更易于维护。
而且,namedtuple的每个实例没有对象字典(__dict__),所以它们更轻量,与普通的元组相比,并不需要更多的内存,这使他们比字典更快。
然而,要记住它仍然是一个元组,属性在namedtuple中是不可变的,所以下面的代码不行:
perry.age = 10
命名元组(namedtuple)向后兼容元组,所以用下标访问也是可以的
print perry[0]
命名元组支持多态,可以转换为字典
print (perry._asdict())
7 上下文
上下文管理器允许你在需要的时候,精确的分配和释放资源。
使用上下文管理器最广泛的案例就是with语句。想象一下你有个需要结对执行的操作,然后还要在中间放置一段代码。
上下文管理器就是专门让你做这种事情的,举个例子:
with open('some_file', 'wb') as f:
f.write("fuck u!")
上面这段代码打开了一个文件,往里面写入了一些数据,然后关闭该文件。如果在往文件里写数据的时候发生异常,它也会尝试去关闭文件。上面的代码与下面的是等价的。
file = open('some_file', 'wb')
try:
file.write("funck u !")
finally:
file.close()
当与第一个例子比较的时候,有很多样板代码(boilerplate code)被消掉了。这就是with语句的主要优势,它确保我们的文件会被关闭,而不用关注嵌套代码如何退出。
上下文的又一用例就是资源的加锁与解锁,以及关闭已经打开的文件(就像上面的例子)
下面让我们自己实现一下上下文管理器
一个上下文管理器的类,最起码要定义__enter__,__exit__方法。
class File(object):
def __init__(self,file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, exc_type, exc_val, exc_tb):
self.file_obj.close()
with File('demo.txt', 'wb') as f:
f.write('Hello')
我们的__exit__函数接受三个参数。这些参数对于每个上下文管理器类中的__exit__方法都是必须得,我们来谈谈在底层都发生了什么。
1,with语句先暂存了File类的__exit__方法
2,然后它调用File类的__enter__方法
3,__enter__方法返回打开文件对象
4,打开的文件对象被传递给 f
5,使用write来写文件
6,调用之前暂存的__exit__
7,__exit__关闭了文件
8,处理异常
我们目前还没有谈到__exit__方法的这三个参数,exc_type,exc_val,exc_tb,在with以下部分如果发生异常,python会将异常的type,value和traceback传递给__exit__方法。
它让__exit__方法来决定如何关闭文件以及是否需要其他步骤,如果没有异常这三个参数的值为None
class File(object):
def __init__(self,file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, exc_type, exc_val, exc_tb):
print exc_val
self.file_obj.close()
return True
with File('demo.txt', 'wb') as f:
f.write_function('Hello')
当发生异常的时候with语句会采取如下步骤:
1,它把异常的type,value,traceback传递给__exit__方法
2,它让__exit__方法来处理异常
3,如果__exit__返回的是True,那么这个异常就优雅的处理了
4,如果__exit__返回的是除了True以外的其它值,那么这个异常会被抛出
基于装饰器和生成器来实现上下文管理
python有个contextlib专门用于这个,我们可以使用一个生成器函数来实现一个上下文管理器,而不是使用一个类。
from contextlib import contextmanager
@contextmanager
def open_file(name):
f = open(name,'w')
yield f
f.close()
with open_file('aaa.log') as of:
of.write("fuck u!")
这块我个人用得比较少,因为内部也是通过__enter__和__exit__来实现的。
8 继承、多态、封装
对于很多新入门的朋友来说面向对象的概念只停留在使用上,但深入理解面向对象并能用其特性使代码变得简洁,可复用,可扩展才算是真正的入门。python web开发中函数式编程较多,因为@staticmethod确实看起来很简洁,但是python语言有着和Java一样的特性,大家在使用上不应该只停留在函数式编程,利用面向对象才能让自己的工程更加“完美”。
8.1什么是继承
它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。在python中子类可以继承一个或多个父类(纵向),一个父类也可以被多个子类继承,多对多的关系。
8.2 什么时候使用继承
python中所有的类都使用了继承,比如class Test(object),例如sqlalchemy这种orm框架会用到Base类,新建的ORM类继承该类。很多框架提供的接口类都是需要继承的时候使用,或者当你需要拓展使用其它类时可以用继承。
8.3 为什么使用继承
1、代码的冗余会大大减少,提升代码的复用
2、继承是多态的基础,没有继承就没有多态
3、提高代码的扩展性
8.4 继承有哪些缺点
1、侵入性,只要继承了,父类的所有东西即刻拥有且必须有
2、降低类的灵活性,稍微有点约束,但这个作者在使用中没感觉多大约束
3、增加了耦合性,继承就相当于增加了耦合,但是这是必须要做的,要不就不叫继承了
8.5 什么是多态
什么是多态呢? 网上查的说法是这样,让具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同的功能的函数。如下:
>>> a = "a"
>>> b = 1
>>> l = [1,2,3]
>>> d = {"a":1}
>>> str(a)
'a'
>>> str(b)
'1'
>>> str(l)
'[1, 2, 3]'
>>> str(d)
"{'a': 1}"
其实我们在写代码的时候经常用到多态,只是有时候我们不知道,a,b,l,d这里是不同的数据类型,但为什么我都能用str()来对其转换成字符串格式,这就是多态的用法。
>>> a.__str__()
'a'
>>> b.__str__()
'1'
>>> l.__str__()
'[1, 2, 3]'
>>> d.__str__()
"{'a': 1}"
上面的代码里的数据类型都有__str__()方法,函数的定义是这样的class Test(object):pass,这里的object就是所有类的爸爸,这就是为什么我说python里的类都使用继承,我们看下object类。
object类里有该函数,所以我们只要对类执行str()都可以。这就是一个函数名实现了不同功能。
Tips:这里作者提醒一下大家,像list,dict,tuple这些都是python内置的数据结构,python的灵活性非常高,我们也可以自己定义特定的数据结构,只要复写指定的函数就行。
8.6 为什么使用多态
其实我们平时在开发的时候不会刻意的使用多态,个人认为多态有如下好处:
子类的功能可以被父类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。继承相当于从下面往上用,多态可以从上面往下用。
8.7 封装
类的封装可有数据封装,方法封装,属性封装,封装的目的是为了保护隐私,隔离复杂性,我在python开发过程中很少刻意的去使用封装,个人认为在编写类的时候把变量,
函数做好定义,提升复用性即可。python的私有变量也不完全是私有变量,也可以被引用,封装这块和Java有点不同。
9 生成器,迭代器,可迭代对象
9.1 生成器的定义
在Python中,一边循环一边计算的机制,称为生成器:generator
生成器仅仅保存了一套生成数值的算法,并且没有让这个算法现在就开始执行,而是我什么时候调它,它什么时候开始计算一个新的值,并给你返回。
9.2 为什么使用生成器
列表所有数据都在内存中,如果有海量数据的话将会非常耗内存。
如:仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。
简单一句话:我又想要得到庞大的数据,又想让它占用空间少,那就用生成器!
9.3 怎么创建生成器
第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x7f6804d43c80>
创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator。
方法二, 如果一个函数中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator。调用函数就是创建了一个生成器(generator)对象,如下图:
def test():
temp = 1
while temp < 5:
a = yield temp
temp+=1
t = test()
print(t.__next__())
print(t.__next__())
print(t.__next__())
print(t.__next__())
print(t.__next__())
1
2
3
4
Traceback (most recent call last):
File "D:/pyprojects/DocAnd/test", line 11, in <module>
print(a.__next__())
StopIteration
9.4 生成器的工作原理
(1)生成器(generator)能够迭代的关键是它有一个next()方法,
工作原理就是通过重复调用next()方法,直到捕获一个异常。
(2)带有 yield 的函数不再是一个普通函数,而是一个生成器generator。
可用next()调用生成器对象来取值。next 两种方式 t.__next__() | next(t),可用for 循环获取返回值,每执行一次,
取生成器里面一个值,实际使用中基本上不会用next()
来获取下一个返回值,而是直接使用for
循环来迭代。
(3)yield相当于 return 返回一个值,并且记住这个返回的位置,下次迭代时,代码从yield的下一条语句开始执行。
(4).send() 和next()一样,都能让生成器继续往下走一步(下次遇到yield停),但send()能传一个值,这个值作为yield表达式整体的结果
——换句话说,就是send可以强行修改上一个yield表达式值。比如函数中有一个yield赋值,a = yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用.send(10),那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10
感受下yield返回值的过程(关注点:每次停在哪,下次又开始在哪)及send()传参的通讯过程,
思考None是如何产生的(第一次取值:yield 返回了 i 值 0,停在yield i,temp没赋到值。第二次取值,开始在print,temp没被赋值,故打印None,i加1,继续while判断,yield 返回了 i 值 1,停在yield i):
def test():
temp = 1
while temp < 5:
a = yield temp
print(a)
temp+=1
t = test()
print(t.__next__())
print(t.__next__())
print(t.__next__())
print(t.__next__())
1
None
2
None
3
None
4
再看如下代码,帮助理解代码的执行顺序。理解的关键在于:下次迭代时,代码从yield的下一条语句开始执行。
#encoding:UTF-8
def yield_test(n):
for i in range(n):
yield call(i)
print("i=",i)
print("Done.")
def call(i):
return i*2
for i in yield_test(5):
print(i,",")
0 ,
i= 0
2 ,
i= 1
4 ,
i= 2
6 ,
i= 3
8 ,
i= 4
Done.
10 自省/反射
10.1 什么是自省
检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。
说的更简单直白一点:自省就是面向对象的语言所写的程序在运行时,能够知道对象的类型。简单一句就是,运行时能够获知对象的类型。
例如python, buby, object-C, c++都有自省的能力,这里面的c++的自省的能力最弱,只能够知道是什么类型,而像python可以知道是什么类型,还有什么属性。
最好的理解自省就是通过例子: Type introspection 这里是各种编程语言中自省(introspection)的例子(这个链接里的例子很重要,也许你很难通过叙述理解什么
是introspection,但是通过这些例子,一下子你就可以理解了)
Python中比较常见的自省(introspection)机制(函数用法)有: dir(),type(), hasattr(), setattr(), getattr(), isinstance(),通过这些函数,我们能够在程序运行时得知对象的类型,
判断对象是否存在某个属性,访问对象的属性。
10.2 为什么需要自省机制
代码里的对象引用有时候不是固定的,比如说你想去根据变量或者字符串来引用对象的属性,这时候就需要灵活的自省机制,或者有时候我们需要动态判断变量类型,
也需要自省。下面的代码是一个自动发邮件的程序,用到了自省,大家可以学习(以下代码无法直接执行)。
class SendThread(object):
def __init__(self):
self.db = DbClass(PG_DB_URI)
self.Session = self.db.make_session()
self.pg_conn = self.Session()
def start(self):
while True:
now = arrow.now().timestamp
job_obj_list, job_total = DMLHandle.query_obj(Job, [Job.send_status == 1, Job.start_time < now], context=1)
for job_obj in job_obj_list:
exec(
'self.{} = Process(name="{}"+str(uuid.uuid1()),target=getattr(self, "send_mail"), args=("{}",))'.format(
job_obj.job_name, job_obj.job_name, job_obj.id))
logging.warn("{} Job is starting ...".format(job_obj.job_name))
getattr(self, job_obj.job_name).start()
for job_obj in job_obj_list:
if hasattr(self, job_obj.job_name):
status = getattr(self, job_obj.job_name).is_alive()
logging.warn("Heart Beat: {} [{}]".format(status, job_obj.job_name))
time.sleep(3)
def send_mail(self, job_id):
pattern = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+')
job_obj = DMLHandle.query_obj_scalar(Job, [Job.id == job_id ], context=1)
if not job_obj or job_obj.send_status !=1:
return
update_flag = DMLHandle.update_obj(Job, [Job.id == job_id], {"send_status": 2}, context=1)
if not update_flag:
DMLHandle.update_obj(Job, [Job.id == job_id], {"send_status": 5, "failed_reason": "更新任务状态失败"}, context=1)
return
ms_obj = DMLHandle.query_obj_scalar(MailServer, [MailServer.mail_server == job_obj.mail_server], context=1)
if not ms_obj:
DMLHandle.update_obj(Job, [Job.id == job_id], {"send_status": 5, "failed_reason": "邮件服务器未配置"}, context=1)
return
template_obj = DMLHandle.query_obj_scalar(Template, [ Template.id == job_obj.template_id ],context=1)
if not template_obj:
DMLHandle.update_obj(Job, [Job.id == job_id], {"send_status": 5, "failed_reason": "邮件模板未配置"}, context=1)
return
content = template_obj.content
url_list = re.findall(pattern, content)
try:
app = Flask(__name__)
app.config.update(
MAIL_DEBUG=False,
MAIL_SERVER=ms_obj.server_domain,
MAIL_USERNAME=ms_obj.mail_server,
MAIL_PASSWORD=ms_obj.mail_password,
MAIL_DEFAULT_SENDER=(ms_obj.send_username, ms_obj.mail_server)
)
email = Mail(app)
email.init_app(app)
except Exception as e:
logging.error("执行任务 {} 失败:{}".format(job_obj.job_name, str(e)))
DMLHandle.update_obj(Job, [Job.id == job_id], {"failed_reason": "连接邮件服务器失败,初始化失败"}, context=1)
return
try:
while True:
time.sleep(job_obj.send_speed)
receive_obj_list, receive_total = DMLHandle.query_obj(MailAll, [ MailAll.job_id == job_obj.id, MailAll.send_status == 0 ], limit=5, context=1)
if receive_total == 0:
break
#password = 'SHBLMMYARTDUDDLS' # 邮箱密码:需要使用授权码
job_obj_inside = DMLHandle.query_obj_scalar(Job, [Job.id == job_id], context=1)
if job_obj_inside.send_status == 4:
return
with app.app_context():
for recv_obj in receive_obj_list:
s_code = pc.encrypt(str(recv_obj.id)).decode("utf-8")
send_content = content
now = arrow.now().timestamp
try:
for url in url_list:
send_content = send_content.replace(url, url+'?user={}'.format(s_code))
msg = Message(subject=template_obj.mail_title,
recipients=[recv_obj.mail])
msg.html = send_content
email.send(msg)
except Exception as e:
DMLHandle.update_obj(MailAll, [ MailAll.id == recv_obj.id ], {"send_status": 2, "send_time": now}, context=1)
logging.error(str(e))
continue
try:
DMLHandle.update_obj(MailAll, [ MailAll.id == recv_obj.id ], {"send_status": 1, "send_time": now}, context=1)
except:
logging.error("{} Job running failed".format(job_obj.job_name))
return
except Exception as e:
DMLHandle.update_obj(Job, [Job.id == job_id], {"send_status": 5, "failed_reason": str(e)}, context=1)
logging.error(str(e))
logging.warning("{} Job is complete".format(job_obj.job_name))
DMLHandle.update_obj(Job, [Job.id == job_id], {"send_status": 3}, context=1)
if __name__ == "__main__":
st = SendThread()
logging.warning("Main Process Start")
st.start()
11 闭包
其实我们经常用到闭包,装饰器就是闭包的最好应用。
11.1 什么是闭包
在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
#闭包函数的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer( a ):
b = 10
# inner是内函数
def inner():
#在内函数中 用到了外函数的临时变量
print(a+b)
# 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
# 在这里我们调用外函数传入参数5
#此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo
# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
demo = outer(5)
# 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
# demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
demo() # 15
demo2 = outer(7)
demo2()#17
从上面例子是我写的一个最简单的很典型的闭包。我估计如果是初学的小伙伴,可能很多名词都不明白是什么意思,没关系,我把这些名词按照自己的理解去解释一下~
1 外函数返回了内函数的引用:
引用是什么?在python中一切都是对象,包括整型数据1,函数,其实是对象。
当我们进行a=1的时候,实际上在内存当中有一个地方存了值1,然后用a这个变量名存了1所在内存位置的引用。引用就好像c语言里的指针,大家可以把引用理解成地址。a只不过是一个变量名字,a里面存的是1这个数值所在的地址,就是a里面存了数值1的引用。
相同的道理,当我们在python中定义一个函数def demo(): 的时候,内存当中会开辟一些空间,存下这个函数的代码、内部的局部变量等等。这个demo只不过是一个变量名字,它里面存了这个函数所在位置的引用而已。我们还可以进行x = demo, y = demo, 这样的操作就相当于,把demo里存的东西赋值给x和y,这样x 和y 都指向了demo函数所在的引用,在这之后我们可以用x() 或者 y() 来调用我们自己创建的demo() ,调用的实际上根本就是一个函数,x、y和demo三个变量名存了同一个函数的引用。
不知道大家有没有理解,很晦涩,希望我说明白了我想表达的。
有了上面的解释,我们可以继续说,返回内函数的引用是怎么回事了。对于闭包,在外函数outer中 最后return inner,我们在调用外函数 demo = outer() 的时候,outer返回了inner,inner是一个函数的引用,这个引用被存入了demo中。所以接下来我们再进行demo() 的时候,相当于运行了inner函数。
同时我们发现,一个函数,如果函数名后紧跟一对括号,相当于现在我就要调用这个函数,如果不跟括号,相当于只是一个函数的名字,里面存了函数所在位置的引用。
2 外函数把临时变量绑定给内函数:
按照我们正常的认知,一个函数结束的时候,会把自己的临时变量都释放还给内存,之后变量都不存在了。一般情况下,确实是这样的。但是闭包是一个特别的情况。外部函数发现,自己的临时变量会在将来的内部函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量送给内函数绑定在一起。所以外函数已经结束了,调用内函数的时候仍然能够使用外函数的临时变量。
在我编写的实例中,我两次调用外部函数outer,分别传入的值是5和7。内部函数只定义了一次,我们发现调用的时候,内部函数是能识别外函数的临时变量是不一样的。python中一切都是对象,虽然函数我们只定义了一次,但是外函数在运行的时候,实际上是按照里面代码执行的,外函数里创建了一个函数,我们每次调用外函数,它都创建一个内函数,虽然代码一样,但是却创建了不同的对象,并且把每次传入的临时变量数值绑定给内函数,再把内函数引用返回。虽然内函数代码是一样的,但其实,我们每次调用外函数,都返回不同的实例对象的引用,他们的功能是一样的,但是它们实际上不是同一个函数对象。
闭包中内函数修改外函数局部变量:
在闭包内函数中,我们可以随意使用外函数绑定来的临时变量,但是如果我们想修改外函数临时变量数值的时候发现出问题了!咋回事捏??!!(哇哇大哭)
在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:1 global 声明全局变量 2 全局变量是可变类型数据的时候可以修改
在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:
1 在python3中,可以用nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
2 在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。
上代码!!!
#修改闭包变量的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer( a ):
b = 10 # a和b都是闭包变量
c = [a] #这里对应修改闭包变量的方法2
# inner是内函数
def inner():
#内函数中想修改闭包变量
# 方法1 nonlocal关键字声明
nonlocal b
b+=1
# 方法二,把闭包变量修改成可变数据类型 比如列表
c[0] += 1
print(c[0])
print(b)
# 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
demo = outer(5)
demo() # 6 11
从上面代码中我们能看出来,在内函数中,分别对闭包变量进行了修改,打印出来的结果也确实是修改之后的结果。以上两种方法就是内函数修改闭包变量的方法。
还有一点需要注意:使用闭包的过程中,一旦外函数被调用一次返回了内函数的引用,虽然每次调用内函数,是开启一个函数执行过后消亡,但是闭包变量实际上只有一份,每次开启内函数都在使用同一份闭包变量
上代码!
#coding:utf8
def outer(x):
def inner(y):
nonlocal x
x+=y
return x
return inner
a = outer(10)
print(a(1)) //11
print(a(3)) //14
两次分别打印出11和14,由此可见,每次调用inner的时候,使用的闭包变量x实际上是同一个。
12 C扩展
15 函数缓存
函数缓存允许我们将一个函数对于给定参数的返回值缓存起来。
当一个I/O密集的函数被频繁使用相同的参数调用的时候,函数缓存可以节约时间。
在Python 3.2版本以前我们只有写一个自定义的实现。在Python 3.2以后版本,有个lru_cache
的装饰器,允许我们将一个函数的返回值快速地缓存或取消缓存。
我们来看看,Python 3.2前后的版本分别如何使用它。
我们来实现一个斐波那契计算器,并使用lru_cache
。
from functools import lru_cache
@lru_cache(maxsize=32)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
>>> print([fib(n) for n in range(10)])
# Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
那个maxsize
参数是告诉lru_cache
,最多缓存最近多少个返回值。
我们也可以轻松地对返回值清空缓存,通过这样:
16 Flask上下文
18 多线程,多进程,协程
20 socket和socketserver
21 python加密
整体思路
1、修改python源码opcode为随机值
修改下载后的Python源码包中opcode值(opcode可以理解为python读取代码的坐标,比如一个变量的opcode是5,则cpython读取这个变量时是从第5个字符串开始读的),修改后会导致关键变量的opcode混乱,除了自己的环境外,其他人都执行和解密不了了
2、在修改好opcode的python环境,把所有py文件编译成pyc,然后删除原始py文件
#!/usr/bin/env python2.7
# encoding:utf-8
#FileName scramble-opcodes.py
__author__ = 'mafei'
import argparse
import re
import random
import os
python_version = 'Python-3.5.3'
regex_for_opcode_h = r'^#define\s+(?P<name>[A-Z_]+)\s+(?P<code>\d+)(?P<extra>.*)'
regex_for_opcode_py = r'^(?P<key>[a-z_]+)+\(\'+(?P<name>[A-Z_]+)+\'+\,\s+(?P<code>\d+)(?P<extra>.*)'
try:
from importlib.machinery import SourceFileLoader
except ImportError:
import imp
class replace_opcode(object):
def __init__(self):
self.replace_dict = {}
self.not_have_argument_code_list = []
self.have_argument_code_list = []
def set_list(self, reg, file):
f1 = open(file, 'r+')
infos = f1.readlines()
f1.seek(0, 0)
for line in infos:
rex = re.compile(reg).match(line)
if rex:
if rex.group('name') in ['CALL_FUNCTION', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_EX', 'CALL_FUNCTION_VAR',
'CALL_FUNCTION_VAR_KW']:
continue
elif int(rex.group('code')) < 90:
self.not_have_argument_code_list.append(int(rex.group('code')))
else:
self.have_argument_code_list.append(int(rex.group('code')))
def replace_file(self, reg, file, is_update=False):
if not is_update:
self.set_list(reg, file)
f1 = open(file, 'r+')
infos = f1.readlines()
f1.seek(0, 0)
for line in infos:
rex = re.compile(reg).match(line)
if rex:
n = self.replace_code(rex, is_update)
line = line.replace(rex.group('code'), str(n))
f1.write(line)
f1.close()
def replace_code(self, rex, is_update):
if is_update:
try:
n = self.replace_dict[rex.group('name')]
except:
n = rex.group('code')
return n
if rex.group('name') == "CALL_FUNCTION":
n = int(rex.group('code'))
elif rex.group('name') in ['CALL_FUNCTION_KW', 'CALL_FUNCTION_EX', 'CALL_FUNCTION_VAR',
'CALL_FUNCTION_VAR_KW']:
n = int(rex.group('code'))
else:
if int(rex.group('code')) < 90:
n = random.choice(self.not_have_argument_code_list)
self.not_have_argument_code_list.remove(n)
else:
n = random.choice(self.have_argument_code_list)
self.have_argument_code_list.remove(n)
self.replace_dict[rex.group('name')] = n
return n
def run(self, source_directory):
OPCODE_PY = 'Lib/opcode.py'
OPTARGETS_H = "Include/opcode.h"
print source_directory
print('start run......', os.path.join(source_directory, OPCODE_PY))
self.replace_file(reg=regex_for_opcode_py, file=os.path.join(source_directory, OPCODE_PY))
print('run {} end'.format(os.path.join(source_directory, OPCODE_PY)))
print('start run......', os.path.join(source_directory, OPCODE_PY))
self.replace_file(reg=regex_for_opcode_h, file=os.path.join(source_directory, OPTARGETS_H), is_update=True)
print('run {} end'.format(os.path.join(source_directory, OPTARGETS_H)))
self.write_opcode_targets_contents()
print('run {} end'.format(os.path.join(source_directory, OPTARGETS_H)))
def write_opcode_targets_contents(self, file='Python/opcode_targets.h'):
"""Write C code contents to the target file object.
"""
targets = ['_unknown_opcode'] * 256
for opname, op in sorted(self.replace_dict.items(), key=lambda nc: nc[1]):
targets[op] = "TARGET_%s" % opname
with open(os.path.join(source_directory, file), 'w') as f:
f.write("static void *opcode_targets[256] = {\n")
sep = ',%s' % os.linesep
f.write(sep.join([" &&%s" % s for s in targets]))
f.write("\n};\n")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Scramble python opcodes table')
parser.add_argument('--python-source', dest='src', type=str,
help='Python source code', required=True)
args = parser.parse_args()
source_directory = os.path.abspath(args.src)
# main(source_directory)
replace_opcode_class = replace_opcode()
replace_opcode_class.run(source_directory)
cd /opt/
下载源码包 wget
解压 tar xJf Python-3.5.3.tar.xz
执行修改opcode操作
python scramble-opcodes.py --python-source=/opt/Python-3.5.3
#后面几步就是标准的python安装步骤啦
cd /opt/Python-3.5.3
./configure --prefix=/opt/python-3.5.3 && make && make install
加入系统路径
export PATH=/opt/python-3.5.3/bin/:$PATH
这时候执行python3就可以进入python3.5.3的修改opcode后的环境了
加密Python代码(一定要在修改过opcode的Python环境执行,否则不生效的)
#!/usr/bin/env python2.7
# encoding:utf-8
__author__ = 'mafei'
import os
import subprocess
import argparse
import sys
def compile_py3(source_dir):
g = os.walk(source_dir)
# compileall.compile_dir(source_dir, maxlevels=100)
subprocess.check_output('{} -m compileall -b {}'.format(sys.executable,source_dir),shell=True)
for path,d,filelist in g:
for filename in filelist:
#对所有py文件进行加密并删除原始文件
if os.path.splitext(filename)[-1] =='.py':
os.remove(os.path.join(path, filename))
print('compile {}'.format(os.path.join(path, filename)))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Scramble python opcodes table')
parser.add_argument('--python-source', dest='src', type=str,
help='Python source code', required=True)
args = parser.parse_args()
source_directory = os.path.abspath(args.src)
compile_py3(source_directory)
22 进程间通讯
23 进程内存共享
24 Python排序算法
25 对象
26 IO多路复用
27 动态加载
27.1 Python 模块动态加载技术
C 语言中可以使用 dlopen,dlsym 和 dlclose 让程序在运行过程中按需加载和卸载动态库。Python 也支持这种方式,使用模块动态加载技术,我们可以把程序的配置文件写成可运行的 python 程序,在程序运行过程中可以动态去更新配置。当然也可以将 python 脚本作为业务逻辑加载到正在运行的主程序中,而不用重启服务。
作者在个人项目 pyed 中使用了这种技术,本文对个人研究和使用这种技术的一个总结。如有问题,欢迎大家讨论。
python 提供了 exec 用于在程序中执行一段 python 代码,官方说明:
exec_stmt ::= "exec" or_expr ["in" expression ["," expression]]
该语句可以使用 exec() 函数进行替代。来看一个简单的例子:
>>> exec "print('Hello World')"
Hello World
>>>
这种使用方式,在程序中其实作用不大,我们使用动态加载,一般是希望将一个模块中的某个变量或函数按需引入到正在执行的程序中,而不仅仅是去执行一下,打印一句 “Hello World”,exec 中的 in 解决了这个问题。
in 的作用是将执行代码中的变量,函数或者类放入到一个字典中,这里再来看一个例子:
>>> exec "a=100" in tmp
>>> print tmp
{'__builtins__': ..., 'a': 100}
>>>
上面的语句等效于:
exec("a=100", tmp)
执行结果中,tmp 除了我们给定的一个 a 变量,赋值为 100 外,还有一个 __builtins__ 成员,内容很多,这里使用 … 替代了实际的内容。如果要访问 a 的值,只需要像操作字典一样就行了:
>>> print tmp["a"]
100
>>>
27.2 简单的模块加载
简单模块加载库
按照上面的思路,我们构造了一个模块
import traceback
class loader(object):
def __init__(self):
pass
def load(self, path):
try:
tmp = {}
exec open(path).read() in tmp
return tmp
except:
print("Load module [path %s] error: %s"
% (path, traceback.format_exc()))
return None
27.2.1 加载配置文件
有一个配置文件 test.conf:
$ cat test.conf
addr="127.0.0.1"
port=2539
$
使用以下代码加载它:
load = loader()
m = load.load("test.conf")
addr = m["addr"]
port = m["port"]
print addr + ":" + str(port)
执行结果:
$ python loader.py
127.0.0.1:2539
$
27.2.2 加载和执行函数
如果要执行加载模块(test.py)中的函数:
def greeting(name):
print "Hello", name
使用以下代码加载它:
load = loader()
m = load.load("test.py")
func = m["greeting"]
func("World")
执行结果:
$ python loader.py
Hello World
$
27.2.3 加载和使用模块中的类
按照上面的思路,如果加载的模块是一个类,其实调用方式也是大同小异的。
修改 test.py
class test(object):
def __init__(self):
pass
def greeting(self, name):
print "Hello", name
使用以下代码加载它:
load = loader()
m = load.load("test.py")
c = m["test"]
print c
print dir(c)
t = c()
t.greeting("World")
执行结果:
$ python loader.py
<class 'test'>
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'greeting']
Hello World
$
从上面可以看到 m[“test”] 是一个 class 类型,我们可以使用它创建类的实例,并调用实例方法
27.2.4 加载的模块引入了其它模块
如果在加载的模块中导入了其它模块,调用方法也是不变的。我们引入一个 test1,继承上例中的 test:
from test import test
class test1(test):
def __init__(self):
test.__init__(self)
使用以下代码加载它:
load = loader()
m = load.load("subtest.py")
c = m["test1"]
print c
print dir(c)
t = c()
t.greeting("World")
执行结果:
$ python loader.py
<class 'test1'>
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'greeting']
Hello World
$
27.3 改进模块加载
上一节介绍了使用 exec … in … 的方式动态去加载模块。完成后可以直接使用返回的字典,访问模块中的变量,函数和类。但是从习惯上,我们更习惯使用模块去调用模块中的变量,函数和类,按此思路,我们对前面的模块加载器进行修改。
新的模块加载器
import traceback, types
class loader(object):
def __init__(self):
pass
def load(self, name, path):
try:
m = types.ModuleType(name)
exec open(path).read() in m.__dict__
return m
except:
print("Load module [path %s] error: %s"
% (path, traceback.format_exc()))
return None
这里使用 types.ModuleType 来构造一个模块 m,将 exec 生成的字典放入到 m.__dict__。这样就生成了一个简单的模块
使用新的模块加载器
待加载的模块:
def test():
s = 0
for i in range(1000000):
s += i
print s
执行逻辑:
load = loader()
m = load.load("test", "test.py")
print m
print m.__dict__
m.test()
执行结果:
$ python loader.py
<module 'test' (built-in)>
{'__builtins__': ..., '__name__': 'test', 'test': <function test at 0x1007f7398>, '__doc__': None}
499999500000
$
从执行结果,我们可以看到使用新的模块加载器,我们得到的是一个 module 类型的实例,其 __dict__ 中包含了 test 函数,我们可以直接使用 m.test() 调用该函数