首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Python 高级特性之二

Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

1)Monkey patching

2) Class decorators

当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类

2 私有变量(__xx)

python类里的私有变量就是前面加两个下划线这样用,但是这只是在使用上的私有变量,不像Java那种只能通过内部函数修改,python的私有变量可以通过 对象._类名__参数来从外部引用。

3 type

4 推导式

推导式又称解析式,有三种

1,列表推导式

multiples = [ i for i in range(30) if i % 3 is 0 ]

2,字典推导式

mcase = {"a":10,"b":2,"c":3}

3,集合推导式

其实大括号里扩着的就是集合(set),例:

{"a","b",1}

squared =

5 装饰器(@decorate)

装饰器是python特色代表之一,非常好用,先介绍一下如何用装饰器。

函数是可以返回函数的

def hi(name="yasoob"):

def greet():

return "in greet() function"

def welcome():

return "in welcome() function"

if name == "yasoob":

return greet

else:

return welcome

a = hi()

print a

在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接受一个函数来进行装饰,并加入了复制函数名称,注释文档,参数列表等等的功能。这可以让我们在装饰器里面访问在装饰器之前的函数的属性。

装饰器的使用场景:

授权(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

日志(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)

在函数中嵌入装饰器

我们回到日志的例子,并创建一个包裹函数,能让我们指定一个用于输出的日志文件。

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 + '')

return func(*args, **kwargs)

return wrapped_function

return logging_decorator

@logit

def myfunc():

pass

装饰器类

现在我们有了能用于正式环境的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 + '')

self.notify()

return func(*args,**kwargs)

return wrapped_function

def notify(self):

# 可以做一些其它行为

pass

@logit()

def my_func():

pass

现在我们给logit创建子类,来添加email等功能

从现在起,@email_logit会在logit基础上多发送一封邮件。

注意:从以上方法中我们就可以发现__call__这种用法的好处,它在装饰器类和新写元类的时候起到了很大作用。

6 容器

python附带一个模块,它包含许多容器数据类型,名字叫做collections。我们将讨论它的作用和用法。

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(, {'x': [1]})

defaultdict(, {'x': {'a': 1}})

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来统计一个文件

此处没有弄明白,需要后期补上

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)

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__关闭了文件

处理异常

我们目前还没有谈到__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__来实现的。

干货分享

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181009B20QH000?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券