Python语言特性的梳理

Posted by onceme on Thursday, March 7, 2019

TOC

对python的语言特性,多线程机制,以及性能局限的梳理

运行环境

由于Python不同版本,尤其是Python2与Pyhton3之间差异明显,所以运行不同项目时往往需要不同版本的运行环境,这种情况下,就需要能快速切换版本的运行环境 现在存在着virtualenv pyenv conda anaconda等虚拟环境 * Anaconda 一个科学计算环境,Python的发行版本 :包括了Conda * Conda –包和虚拟环境管理工具 * virtualenv 轻量级第三方虚拟环境管理工具,没有Anaconda好用 * pyenv   python版本管理工具

Pythonic

Pythonic就是以Python的方式写出简洁优美的代码

上下文管理器

上下文管理器就算在想要执行的目标代码前做一些预处理工作,然后再目标代码执行后,做一些后续扫尾工作 在上下文管理协议中,有两个方法enterexit,分别实现上述两个功能。 使用with语句,以及一个支持上下文协议的对象,就可以使用上下文管理器 >装饰器contextmanager 将一个函数中yield语句之前的代码当做enter方法执行,yield语句之后的代码当做exit方法执行。同时yield返回值赋值给as后的变量。

@contextlib.contextmanager
def open_func(file_name):
    # __enter__方法
    print('open file:', file_name, 'in __enter__')
    file_handler = open(file_name, 'r')

    yield file_handler

    # __exit__方法
    print('close file:', file_name, 'in __exit__')
    file_handler.close()
    return

with open_func('python_base.py') as file_in:
    for line in file_in:
        print(line)
装饰器

装饰器实际上就是给其他函数和类附加额外功能 值得注意的是使用装饰器后代码的执行顺序 >装饰器的逻辑是,将被装饰的函数传入装饰器,返回一个装饰器的函数对象。在这个对象里面猜实际执行被装饰的方法

下面的例子,把foo函数传入的装饰器函数中去,装饰器函数返回一个可执行的wrapper函数,wrapper函数里面再执行foo函数

import logging

def use_logging(func):

    def wrapper(*args, **kwargs):
        logging.warn("%s is running" % func.__name__)
        return func(*args, **kwargs)   # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
    return wrapper

@use_logging    # 装饰器的逻辑是,把foo函数传入的装饰器函数中去,装饰器函数返回一个可执行的wrapper函数,wrapper函数里面再执行foo函数
def foo():
    print("i am foo")


foo()

property装饰器把函数调用伪装成对属性的访问

class Topic(object):
    def __init__(self, View):
        self._View = None
        self.View = View
 
    @property
    def budget(self):
        return self._budget
 
    @budget.setter
    def budget(self, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self._budget = value

t = Topic(1)
print(t.budget) # 实际访问的是
生成器

生成器与列表推导有点类似,区别在于,列表推导一开始就执行完计算,返回的就是一个列表,而生成器返回的是一个生成器对象,里面保存的是生产数据的算法,可以步进的执行里面的逻辑

>>> 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 0x7fbea411e9a8>

可以用next(g)来获得生成器的下一次结果,但一般使用迭代,generator也是可迭代对象

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
... 
0
1
4
...

可以使用yield将函数变成生成器

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
>>> f = fib(6)
>>> f
<generator object fib at 0x7fbea411ea20>
描述符

使用描述符的类中,多次实例化描述,不同变量保存的描述符是独立的,但是类被多次实例化的话,不同实例的同名描述符变量之间是共享实例的 下面的例子说明,对描述符变量的读写都会被转接到对象内部的getset方法

from weakref import WeakKeyDictionary

class NonNegative(object):
    def __init__(self, default):
        self.default = default
        self.data = WeakKeyDictionary()
        self.value = default

    def __get__(self, instance, owner):
        return self.data.get(instance, self.default)

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError("Negative value not allowed: %s" % value)
        self.data[instance] = value


class Movie(object):
    View = NonNegative(0)

    def __init__(self, View):
        self.View = View

    def profit(self):
        return self.View +1

m = Movie(9)

# 
print(m.View)  # calls Movie.View.__get__(m, Movie)
m.View = 100  # calls Movie.View.__set__(m, 100)
元类

python中类不但可以创建对象,而且本身就是一个对象 (python这种类的实现方式,感觉有点想javascirpt)

  • 可以像操作普通对象一样操作类对象

    >>> class ObjectCreator(object):
    ...     pass
    ...
    >>> ObjectCreator
    <class '__main__.ObjectCreator'>
    >>> ObjectCreator.new_attribute = 'foo'  # 添加属性
    >>> print(ObjectCreator.new_attribute)
    foo
    >>> ObjectCreatorMirror = ObjectCreator  # 赋值给其他变量
    >>> print(ObjectCreatorMirror)
    <class '__main__.ObjectCreator'> 
    
  • 动态地创建类 >type有一种完全不同的能力,它也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类

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

    例如这样的类定义

    >>> class MyShinyClass(object):
    …       bar = True
    

    可以用type来实现,type 接受一个字典来为类定义属性

    >>> MyShinyClass = type('MyShinyClass', (), {'bar':True})  # 返回一个类对象
    >>> print MyShinyClass
    <class '__main__.MyShinyClass'>
    >>> print MyShinyClass()  #  创建一个该类的实例
    <__main__.MyShinyClass object at 0x8997cec>
    

元类可以理解为创建类的类,主要目的是为了当创建类时能够自动地改变类。

Python中所有的东西都是对象。包括整数、字符串、函数以及类。而且它们都是从元类type衍生而来

>>> language = 'Python'
>>> language.__class__
<class 'str'>
>>> language.__class__.__class__
<class 'type'>
  • 元类的实际应用 metaclass属性 metaclass属性 可以指定使用什么元类来创建当前类对象,它可以设置在类中,也可以在设置在模块这一层级上

    
    class Foo(object):
    __metaclass__ = something…
    
    
  • 自定义元类

    # 元类会自动将你通常传给‘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'
    
其他
  • python的特殊语法:变量交换

    a, b = b, a   # 快速交换变量值
    
  • 列表推导 这个其实类似于生成器了,只是会直接进行计算,返回计算结果

    [ i*i for i in range(30, 41) if i% 2 == 0 ]
    
    

代码组织与包管理机制

Python工程目录结构 > https://zhuanlan.zhihu.com/p/36221226 模块与包的引入与管理

  • python 引入模块要注意避免循环引用
  • 包 :只要一个文件夹下面有个 init.py 文件,那么这个文件夹就可以看做是一个包
  • python使用pip进行包管理,包下载后,当前版本的执行环境下都可以进行引用,这区别与php comporsor 将包引入项目内部

TODO 与go,php composor的包管理机制的对比

数据结构

python中的元组 tuple,可以认为是一种特殊的列表,只是一经创建,内容不可修改

tup1 = ('physics', 'chemistry', 1997, 2000)

不过对于tuple中的引用类型数据(借用静态语言的概念),在不修改引用本身的情况下,是可以对这个引用内部的数据进行修改的 tuple中相当于保存的是指针,无需改变指针,就可以对指向的数据进行修改

>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

多线程编程

python的多线程编程会用到一些内置特性来进行线程间通信,与并发安全控制(虽然默认的CPython不会真正的并行,但是在多个线程间切换执行,仍绕会面临并发安全问题) * 线程间通信 event与queue,锁 * 线程安全 -锁机制 * 子线程启动后, * 有join,主线程阻塞在join的位置 * 没有join,主线程与子线程并发执行,主线程执行完所有逻辑后,子线程仍然能继续执行(主线程执行完退出) 下面的例子,在多个线程操作同一变量时使用锁来保证线程安全

import time, threading
from threading import Thread

num = 0
lock = threading.Lock() #使用锁来协调线程的并发执行

class tWork(Thread):
    def __init__(self,step=0):
        Thread.__init__(self)
        self.step = step

    def run(self):
        global num
        for i in range(10):
            lock.acquire() # 先要获取锁:

            print(str(num) + '+'+str(self.step))
            num = num+self.step
            print(str(num) + '-' + str(self.step))
            num = num-self.step

            lock.release()

threadSer = tWork(5)
threadSer2 = tWork(8)

threadSer.start()
threadSer2.start()

解释器,多线程GIL 与性能优化

python的性能一直挺受诟病,一个是默认的cpyton解释器的执行效率确实不高,另外一个就是鼎鼎大名的GIL全局锁导致python的多线程直接是互斥执行的,同一时间只会有一个线程在执行

  • 解释器慢的问题,有 Pypy, Jython等第三方解释器 其中pypy的性能优化做的挺不错,使用了JIT,我的实测对比,pypy的速度大致是cpython的5倍,另外由于使用了JIT,对一个变量不停的追加内容,会导致明显的性能下降 Jython则是在jvm中执行python,看到一些测评,性能并不如意
  • CPython 使用带condition的互斥锁来实现GIL,并且在线程执行碰到阻塞时,会释放锁,交给其他线程继续支持
  • 针对CPython解释器GIL的问题 Jython与IronPython中实现了真正的多线程并发,PyPy也有独立的分支版本PyPy-stm来支持多线程 > 从单核性能来看,首选pypy来执行,考虑多线程的时候,可以使用PyPy-stm

更多实例代码可以我的github项目 https://github.com/wosiwo/pythonic

参考

https://www.zhihu.com/question/21408921 https://zhuanlan.zhihu.com/p/24709718 http://python.jobbole.com/81899/ http://blog.jobbole.com/21351/ http://www.newsmth.net/nForum/#!article/Programming/118874 https://cyrusin.github.io/2016/04/27/python-gil-implementaion/


comments powered by Disqus
Ï