Python进阶教程(二)

时间:2022-05-07
本文章向大家介绍Python进阶教程(二),主要内容包括概述、Intermediate Python 中译(二)、global and return、__slots__、collections、comprehension、try/else、总结、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

概述

在上一篇博客中,我们介绍了Python进阶教程(一),还有一些新的技巧没有翻译完,我们下面来继续我们的翻译。

Intermediate Python 中译(二)

Decorators

Decorators装饰器是Pytho非常重要的特性,相当于Java注解功能。装饰器是修饰函数的一种语法特性,可以使其功能发生一些改变。在Python函数也是第一等公民,可以直接赋值对象和使用。

def hi(name="brian"):
    return "hi,"+name
print(hi())
#输出
hi,brian
greet = hi
print(greet())
#输出
hi,brian
#此时已经删除hi对象,无法运行hi()方法,但是可以通过greet()方法运行
del hi
print(greet())
#输出
hi,brian

我们也可以在函数内定义函数,比如:

def hi(name="brian"):
    print("now you are inside in the hi() method")
    def greet():
        return "now you are in inside the greet() method"
    def welcome():
        return "now you are in inside the welcome() method"
    print(greet())
    print(welcome())
    print("now you are back in the hi() method")
hi()
#输出为
now you are inside in the hi() method
now you are in inside the greet() method
now you are in inside the welcome() method
now you are back in the hi() method

我们可以看到在函数(主函数)内定义函数(子函数),只要是不调用子函数,子函数只是声明并不执行,当执行完子函数时又返回了主函数。由于Python只是函数第一等公民的功能特性,那么在Python代码中是可以将Python的函数可以作为变量使用并将其作为返回值亦可。从主函数中返回子函数(即从函数中返回函数)

def hi(name="brian"):
    def greet():
        return "now you are in inside the greet() method"
    def welcome():
        return "now you are in inside the welcome() method"
    if name=="brian":
        return greet
    else:
        return welcome
a=hi()
print(a)
#输出
<function hi.<locals>.greet at 0x10f792a60>
a()
#输出
'now you are in inside the greet() method'

既然函数可以作为变量使用既可以赋值又可以作为函数返回值,那么自然而然肯定还可以作为参数使用。 函数作为参数

def hi():
    return "hi,brian"
def doSomethingBeforeHi(func):
    print("I'm doing some boring work before executing "+func.__name__+" function")
    print(func())
doSomethingBeforeHi(hi)
#输出
I'm doing some boring work before executing hi function
hi,brian

我们来总结一下Python将函数作为第一等公民具备以下三个特性:

  • 函数可以作为变量使用和赋值
  • 在函数内可以定义函数
  • 从函数可以返回函数
  • 函数可以作为函数参数

下面我们来写一个我们自己原生的一个装饰器,

#定义了一个装饰器函数和一个参数,该参数是接收函数的参数。
def new_decorator(a_func):
    #在函数内定义要执行的函数
    def wrap_function():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrap_function

#定义一个要装饰的函数
def func_requiring_decorator():
    print("I am the function which needs some decorators")
#将装饰了的函数功能重新赋值给原始对象进行更新
func_requiring_decorator = new_decorator(func_requiring_decorator)
#调用函数
func_requiring_decorator()
#输出
I am doing some boring work before executing a_func()
I am the function which needs some decorators
I am doing some boring work after executing a_func()

我们上述做的事情就是一个简单的装饰器的工作,它的运行原理是一样的。Python为了使其简短和语义方便将@object作为装饰器。我们来修改一下内容:

def new_decorator(a_func):
    
    def wrap_function():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrap_function
@new_decorator
def func_requiring_decorator():
    print("I am the function which needs some decorators")
func_requiring_decorator()
#输出:
I am doing some boring work before executing a_func()
I am the function which needs some decorators
I am doing some boring work after executing a_func()

但是我们的函数命名和docstring并没改变,python通过functools的wraps可以解决这个问题。

def new_decorator(a_func):
    
    def wrap_function():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrap_function
@new_decorator
def func_requiring_decorator():
    print("I am the function which needs some decorators")
print(func_requiring_decorator.__name__)
#输出
wrap_function

我们需要导入functools包的wraps来导入。

def new_decorator(a_func):
    @wraps(a_func)
    def wrap_function():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrap_function

@new_decorator
def func_requiring_decorator():
    print("I am the function which needs some decorators")
print(func_requiring_decorator.__name__)
#输出:
func_requiring_decorator

下面我们来看一下一个标准的装饰器范本

def decorator_name(f):
    @wraps(f)
    def decorated(*args,**kwargv):
        if not can_run:
            return "Function will not run"
        return f(*args,**kwargv)
    return decorated
@decorator_name
def func():
    return("Function is running")
can_run=True
func()
#输出
'Function is running'
can_run=False
func()
#输出
Function will not run

@wraps接收一个函数进行装饰,并复制了函数名称、注释文档、参数列表等功能。装饰器在Python中经常使用的,我们看一下在权限验证、日志方面的应用。

def requires_auth(f):
    @wraps(f)
    def decorated(*args,**kwargs):
        #获得授权的用户信息
        auth=request.authorizztion
        #检查auth是否正确,即验证授权信息
        if not auth or not check_auth(auth.username,auth.userpassword):
            #执行你相关的业务授权信息
            authenticate()
        return f(*args,**kwargs)
    return decorated

日志应用的模块中也有大量应用装饰器,我们下面来看一下装饰器的例子:

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

print(addition_func(2))
#输出
addition_func was called
4

我们看一下如何在函数中嵌套装饰器,这个应用也非常的广泛。我们来看一下:

from functools import wraps


def logit(logfile="out.log"):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_func(*args, **kwargv):
            log_string = func.__name__+" was called"
            print(log_string)
            with open(logfile, 'a') as f:
                f.write(log_string+"n")
            return func(*args, **kwargv)
        return wrapped_func
    return logging_decorator


@logit()
def func1():
    pass


@logit(logfile="func2.log")
def func2():
    pass

虽然上面的函数嵌套装饰器可以满足参数定制化的需求,但是在一些复杂业务还是无法满足需求的。这个时候我们需要采用面向对象的思维模式对装饰器进行抽象和编程。下面我们来看一下这个例子:

from functools import wraps

class logit(object):
    def __init__(self, logfile="out.log"):
        self.logfile = logfile

    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargv):
            log_string = func.__name__ + " was called"
            print(log_string)
            with open(self.logfile, 'a') as f:
                f.write(log_string + "n")
            self.notify()
            return func(*args, **kwargv)
        return wrapped_function

    def notify(self):
        pass


@logit()
def func1():
    pass

func1()
#输出
func1 was called

当我们的业务需要具体实现notify的子类时,我们可以这样去做:

class email_logit(logit):
    def __init__(self,email="admin@project.com",*argc,**kwagrv):
        self.email = email
        super(email_logit,self).__init__(*argc,**kwagrv)

    def notify(self):
        pass

global and return

如何在讲解global和return时不了解python的变量作用域是不合理的。我们来看一下Python的变量作用域范围LEGB

  • L(local)局部作用域 局部变量:包含在def关键字定义的语句块中,即在函数中定义的变量。每当函数被调用时都会创建一个新的局部作用域。Python中也有递归,即自己调用自己,每次调用都会创建一个新的局部命名空间。在函数内部的变量声明,除非特别的声明为全局变量,否则均默认为局部变量。有些情况需要在函数内部定义全局变量,这时可以使用global关键字来声明变量的作用域为全局。局部变量域就像一个 栈,仅仅是暂时的存在,依赖创建该局部作用域的函数是否处于活动的状态。所以,一般建议尽量少定义全局变量,因为全局变量在模块文件运行的过程中会一直存在,占用内存空间。 注意:如果需要在函数内部对全局变量赋值,需要在函数内部通过global语句声明该变量为全局变量。
  • E(enclosing)嵌套作用域 E也包含在def关键字中,E和L是相对的,E相对于更上层的函数而言也是L。与L的区别在于,对一个函数而言,L是定义在此函数内部的局部作用域,而E是定义在此函数的上一层父级函数的局部作用域。主要是为了实现Python的闭包,而增加的实现。
  • G(global)全局作用域 即在模块层次中定义的变量,每一个模块都是一个全局作用域。也就是说,在模块文件顶层声明的变量具有全局作用域,从外部开来,模块的全局变量就是一个模块对象的属性。 注意:全局作用域的作用范围仅限于单个模块文件内
  • B(built-in)内置作用域 系统内固定模块里定义的变量,如预定义在builtin 模块内的变量。

变量名解析LEGB法则

搜索变量名的优先级:局部作用域 > 嵌套作用域 > 全局作用域 > 内置作用域 LEGB法则: 当在函数中使用未确定的变量名时,Python会按照优先级依次搜索4个作用域,以此来确定该变量名的意义。首先搜索局部作用域(L),之后是上一层嵌套结构中def或lambda函数的嵌套作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止。如果没有找到,则会出发NameError错误。 另外值得注意的是Python中的模块代码在执行之前,并不会经过预编译,但是模块内的函数体代码在运行前会经过预编译,因此不管变量名的绑定发生在作用域的那个位置,都能被编译器知道。Python虽然是一个静态作用域语言,但变量名查找是动态发生的,直到在程序运行时,才会发现作用域方面的问题。 当我们在局部作用域时想使用全局作用域中的变量时,需要通过使用global来覆盖使用。比如:

result=12
def add(x,y):
  result=x+y
  return result

temp=add(2,3)
print("global result is :",result)
print("add result is :",temp)
#输出为
global result is : 12
add result is : 5
#如果我们想通过全局变量的result,则需要通过global
def add(x,y):
  global result
  result = x+y
  return result

print("global result is :",result)
print("add result is :",temp)
#输出为
global result is : 5
add result is : 5

如果想return多个值,可以通过tuple,list或者dict等数据结构返回。

__slots__

在Python中每个类都有实例,每个实例都有一些属性。默认情况下,Python采用字典的方式来保存对象实例属性,这就允许我们在runtime时可以动态设置一些属性。然而这会造成一些资源的浪费,比如随着类实例的增加内存随之增加,这样会消耗很多内存。我们可以用__slots__来进优化这部分内存空间,它告诉Python解释器在构造类的实例时不要使用字典的方式并且只给一个固定集合的属性分配空间。

class Email(object):
    __slots__=['emailNo','emailAddress']
    def __init__(self,emailNo, emailAddress):
        self.emailNo = emailNo
        self.emailAddress = emailAddress

这样定义的类,在类进行实例化后会优化内存占用,具体占用多少这个根据你自己写的代码、业务逻辑有关。但是有一点可以肯定,__slots__要比不加该特性的使用的。

collections

collections主要是基于该模块下面的容器类型的数据结构,我们来看一下:

  • defaultdict
  • counter
  • dequeue
  • namedtuple
  • OrderedDict
  • enum.Enum(Python 3.4及以上)

defaultdict

如果在k,v这种数据结构的场景,建议使用defaultdict作为你的数据结构,在Python官方文档里面将collections下的数据结构称为高性能容器数据结构。我们来看一下两个实用的例子。

from collections import defaultdict
colours=(('brian','red'),('eric','yellow'),('brian','yellow'),('doc','blue'),('brian','blue'))
fav_colors = defaultdict(list)
for name,color in colours:
    fav_colors[name].append(color)
print(fav_colors)
#输出
defaultdict(<class 'list'>, {'brian': ['red', 'yellow', 'blue'], 'eric': ['yellow'], 'doc': ['blue']})

这个是常用的业务处理模型,下面我来看看非常经典的另外一个实例。比如:

#不要被这一行代码给迷惑了,它只是将函数参数传递给defaultdict,它和我下面注释的代码逻辑功能是一致的。
tree=lambda:defaultdict(tree)
#def tree():
#    return defaultdict(tree)
some_dict = tree()
#我们能看到作为dict来取数据的时候,并没有造成KeyError错误。
some_dict['colors']['fav']='yellow'

由于defaultdict是相当于层级的dict高性能容器数据结构,我们通过json来解构和分析。下面我们来看一下:

import json
print(json.dumps(some_dict))
#输出
'{"colors": {"fav": "yellow"}}'

counter

counter是个计数器,它也属于Python高性能容器数据结构。我们来看一下使用方式:

from collections import Counter

colours = (('brian', 'red'), ('eric', 'yellow'),
           ('brian', 'yellow'), ('docx', 'blue'))

fav_colours = Counter(name for name, color in colours)
print(fav_colours)
#输出
Counter({'brian': 2, 'docx': 1, 'eric': 1})
#下面是我通过defaultdict来实现
from collections import defaultdict
fav_colours_dict = defaultdict(int)
for name, color in colours:
    fav_colours_dict[name] += 1
print(fav_colours_dict)
#输出
defaultdict(<type 'int'>, {'brian': 2, 'docx': 1, 'eric': 1})

dequeue

dequeue是双端队列,在队列的头部和尾部都可以操作。一些常用的API可以查看Python帮助文档,这里就列出了。队列是一种先进先出(First-In-First-Out,FIFO)的数据结构。队列被用在很多地方,比如提交操作系统执行的一系列进程、打印任务池等,一些仿真系统用队列来模拟银行或杂货店里排队的顾客。队列的两种主要操作是:向队列中插入新元素和删除队列中的元素。插入操作也叫做入队,删除操作也叫做出队。入队操作在队尾插入新元素,出队操作删除队头的元素。队列的另外一项重要操作是读取队头的元素。这个操作叫做peek()。该操作返回队头元素,但不把它从队列中删除。除了读取队头元素,我们还想知道队列中存储了多少元素,可以使用size()满足该需求。

namedtuple

命名元组是一个给元组命名的一种元组,这么说有点抽象。我们来看一下使用:

from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perrty = Animal(name="brian", age=31, type="cat")
# 还可以通过这样构造
eric = Animal(**{"name":"brian", "age":31, "type":"cat"})
print(perrty, "the name is "+perrty.name)
#输出
(Animal(name='brian', age=31, type='cat'), 'the name is brian')
print(perrty._asdict())
#输出
OrderedDict([('name', 'brian'), ('age', 31), ('type', 'cat')])
#_make是namedtuple的
print(Animal._make(("brian", 31, "dog")))
#输出
Animal(name='brian', age=31, type='dog')

namedtuple相对比传统的tuple,可以说是轻量级的tuple。特别是在ORM框架中经常使用,来保持数据一致性并节省一定的内存空间。_make是namedtuple的类方法是来生成对象的实例,接收的是有序参数的容器(list或者tuple)。这种方式和数据库序列化时非常常见。

EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')

import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
    print(emp.name, emp.title)

import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
#这边将对象的属性批量构造实例
for emp in map(EmployeeRecord._make, cursor.fetchall()):
    print(emp.name, emp.title)

还有一个方法是_replace,是用来修改对象,它不是在原有基础上进行修改,而是直接创建一个新的对象。

from collections import namedtuple

Account = namedtuple('Account', 'owner balance transaction_count')
default_account = Account('<owner name>', 0.0, 0)
johns_account = default_account._replace(owner='John')
janes_account = default_account._replace(owner='Jane')
print(johns_account)
print(janes_account)
#输出
Account(owner='John', balance=0.0, transaction_count=0)
Account(owner='Jane', balance=0.0, transaction_count=0)

enum.Enum

在Python中枚举是没有直接作为Python的条件选择的,而是自己扩展。在Python(3.4)以后才加入的enum模块。

from collections import namedtuple
from enum import Enum


class Species(Enum):
    cat = 1
    dog = 2
    horse = 3
    owl = 4


Animal = namedtuple('Animal', 'name,age,type')
perry = Animal(name="Perry", age=12, type=Species.cat)
eric = Animal(name="Eric", age=21, type=Species.cat)
charlie = Animal(name="Charlie", age=2, type=Species.dog)
print(charlie.type == perry.type, perry.type == eric.type)
#输出
(false,true)

enumrable

enumrable可枚举的数据类型,它带有自动计数(即索引)功能。

temp = ["brian", "eric", "perry"]
for k, v in enumerate(temp, 1):
    print("key==", k, ";v==", v)
print(list(enumerate(temp)))
#输出
('key==', 1, ';v==', 'brian')
('key==', 2, ';v==', 'eric')
('key==', 3, ';v==', 'perry')
[(0, 'brian'), (1, 'eric'), (2, 'perry')]

comprehension

comprehension推导式这个语法是Python的一种独有特性,一种数据容器用来构造另一种数据容器。常用数据容器支持推导式的有以下三个数据结构:

  • List Comprehension
  • Dict Comprehension
  • Set Comprehension

list comprehension

list comprehension又叫列表推导式(列表解析式),通过一种简洁的方式来构造列表,它相当于在for里面写表达式。我们来看以下规范:

variable = [out_exp for out_exp in result_list if out_exp==2]

形如上面的格式我们统称为列表推导式,我们来看一个例子:

result = [i**2 for i in range(20) if i%2==0]

dict comprehension

dict comprehension字典推导式,我们来看一个例子:

temp = {"name": "brian", "age": 16, "class": "cs"}
result = {v: k for k, v in temp.iteritems()}
print(result)

set comprehension

set comprehension集合推导式。

a=[1,2,3,4,5,6]
result = {x**2 for i in a}

try/else

我们学过其它编程语言的话,大家应该都知道try except finally的异常处理机制,那么python下的try/else是怎么回事?我们来看一下,

try:
    print("no excetion")
    raise Exception("raise exception")
except Exception as e:
        print("raise excetion")
else:
    print("else no excetion")
finally:
    print("finally")
#输出
no excetion
raise excetion
finally

try和except代码块内出现异常时,else代码逻辑是不执行的,当try内无异常时则执行else代码逻辑。finally无论是否有异常则都会执行。else不仅在try异常里面,也会和for配合使用。

for i in range(10):
    if i == 50:
        break
else:
   #for什么都没执行或者没跳出for时,下面逻辑会执行
    print("else code")
#输出
else code
#但是如果运行下面语句,由于break作用会跳出当前上下文不会执行else。
for i in range(10):
    if i == 5:
        break
else:
   #for什么都没执行或者没跳出for时,下面逻辑会执行
    print("else code")

总结

这篇博客总结较多,希望能帮助大家。如果对你有用,可以在捐赠博客。