9.19 在定义的时候初始化类的成员
- 问题:想要在类定义的时候初始化一部分类的成员,而不是等到实例被创建的时候
- 方案:本质上,一个元类会在定义时被触发,这时候可以执行一些额外的操作
import operator
class StructTupleMeta(type):
def __init__(cls,*args, **kwargs):
super().__init__(*args, **kwargs)
for n, name in enumerate(cls._fields):
setattr(cls, name, property(operator.itemgetter(n)))
class StructTuple(tuple,metaclass=StructTupleMeta):
_fields = []
def __new__(cls,*args):
if len(args) != len(cls._fields):
raise TypeError("{} arguments required".format(len(cls._fields)))
return super().__new__(cls, args)
# 上述代码可以用来定义简单的基于元组的数据结构
class Stock(StructTuple):
_fields = ['name','shares','price']
class Point(StructTuple):
_fields = ['x','y']
s = Stock("ACME", 50, 91.3)
s
('ACME', 50, 91.3)
s[0]
'ACME'
s.name
'ACME'
s.shares
50
s.shares * s.price
4565.0
s.shares = 30
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-b50f5c8ac48e> in <module>()
----> 1 s.shares = 30
AttributeError: can't set attribute
9.20 利用函数注解来实现方法的重载
- 问题:已经学会了使用函数参数注解,想使用它实现基于类型的方法重载
- 方案:如下
class Spam:
def bar(self, x:int, y:int):
print('Bar 1:', x,y)
def bar(self,s:str, n:int = 0):
print('Bar 2:', s, n)
s = Spam()
s.bar(1,2)
Bar 2: 1 2
s.bar('hello',100)
Bar 2: hello 100
s.bar('hello')
Bar 2: hello 0
- 下面开始第一步,使用到了一个元类和描述器
import inspect
import types
class MultiMethod:
def __init__(self, name):
self._methods = {}
self.__name__ = name
def register(self,meth):
sig = inspect.signature(meth)
types = []
for name, parm in sig.parameters.items():
if name == 'self':
continue
if parm.annotation is inspect.Parameter.empty:
raise TypeError("Argument {} must be annotation with a type".format(name))
if not isinstance(parm.annotation,type):
raise TypeError("Argument {} must be annotation with a type".format(name))
if parm.default is not inspect.Parameter.empty:
self._methods[tuple(types)] = meth
types.append(parm.annotation)
self._methods[tuple(types)] = meth
def __call__(self,*args):
types = tuple(type(arg) for arg in args[1:])
meth = self._methods.get(types,None)
if meth:
return meth(*args)
else:
raise TypeError(" No matching method for types {}".format(types))
def __get__(self,instance,cls):
if instance is not None:
return types.MethodType(self, instance)
else:
return self
class MultiDict(dict):
def __setitem__(self,key,value):
if key in self:
current_value = self[key]
if isinstance(current_value,MultiMethod):
current_value.register(value)
else:
mvalue = MultiMethod(key)
mvalue.register(current_value)
mvalue.register(value)
super().__setitem__(key, mvalue)
else:
super().__setitem__(key, value)
class MultipleMeta(type):
def __new__(cls, clsname, bases, clsdict):
return type.__new__(cls,clsname,bases, dict(clsdict))
@classmethod
def __prepare__(cls, clsnae, bases):
return MultiDict()
# 使用
class Spam(metaclass=MultipleMeta):
def bar(self, x:int, y:int):
print("Bar1: ", x, y)
def bar(self, s:str, n:int = 0):
print("Bar2: ",s, n)
import time
class Date(metaclass=MultipleMeta):
def __init__(self,year:int, month:int, day:int):
self.year = year
self.month = month
self.day = day
def __init__(self):
t = time.localtime()
self.__init__(t.tm_year,t.tm_mon,t.tm_mday)
s = Spam()
s.bar(2,3)
Bar1: 2 3
s.bar('hello')
Bar2: hello 0
s.bar('hello',5)
Bar2: hello 5
s.bar(3,'hello')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-21-f306f96769e1> in <module>()
----> 1 s.bar(3,'hello')
<ipython-input-16-ef226f50c070> in __call__(self, *args)
25 return meth(*args)
26 else:
---> 27 raise TypeError(" No matching method for types {}".format(types))
28 def __get__(self,instance,cls):
29 if instance is not None:
TypeError: No matching method for types (<class 'int'>, <class 'str'>)
d = Date(2012,11,15)
d.year
2012
e = Date()
e.year
2018
9.21 避免重复的属性方法
- 问题:在类中定义了一些逻辑相同的重复代码,比如类型检查,如何简化
- 方案:如下
class Person:
def __init__(self, name , age):
self.name = name
self.age = age
@property
def name(self):
return self._name
@name.setter
def name(self,value):
if not isinstance(value, str):
raise TypeError('name must be a string')
self._name = value
@property
def age(slef):
return self._age
@age.setter
def age(self,value):
if not isinstance(value, int):
raise TypeError("age must be an int")
self._age = value
- 上面的代码有很多重复的代码,可以使用一个函数来定义属性并返回它
def typed_property(name, expexted_type):
storage_name = "_" + name
@property
def prop(self):
return getattr(self, storage_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expexted_type):
raise TypeError('{} must be {}'.format(name, expexted_type))
setattr(self,storage_name, value)
return prop
class Person:
name = typed_property('name',str)
age = typed_property('age', int)
def __init__(self,name, age):
self.age = age
self.name = name
p = Person('Lily',20)
p.age
20
p.name
'Lily'
9.22 定义上下文管理器的简单方法
- 问题:需要实现一个新的上下文管理器,以便使用with语句
- 方案:使用contextlib模块中的@contextmanager装饰器
import time
from contextlib import contextmanager
@contextmanager
def timethis(label):
start = time.time()
try:
yield
finally:
end = time.time()
print("{}:{}".format(label, end-start))
with timethis('counting'):
n = 100000
while n>0:
n -= 1
counting:0.011967897415161133
-
在函数 timethis() 中, yield 之前的代码会在上下文管理器中作为 enter()
方法执行,所有在 yield 之后的代码会作为 exit() 方法执行。如果出现了异常,
异常会在 yield 语句那里抛出。 -
下面是一个更加高级的上下文管理器,实现了列表对象上的某种事务
@contextmanager
def list_transaction(orig_list):
working = list(orig_list)
yield working
orig_list[:] = working
- 这段代码的作用是任何对列表的修改只有当所有代码运行完成并且不出现异常的
情况下才会生效
items = [1,2,3]
with list_transaction(items) as working:
working.append(4)
working.append(5)
items
[1, 2, 3, 4, 5]
with list_transaction(items) as working:
working.append(6)
working.append(7)
raise RuntimeError("oops")
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-49-4230d9b92354> in <module>()
2 working.append(6)
3 working.append(7)
----> 4 raise RuntimeError("oops")
RuntimeError: oops
items
[1, 2, 3, 4, 5]
9.23 在局部变量域中执行代码
- 问题:想要在使用范围内执行某片代码,并且希望在执行后所有的结果都不可见
- 方案:如下
- 首先,在全局命名空间内执行一个代码段:
a = 14
exec('b = a + 1')
print(b)
15
- 然后在一个函数中执行同样的代码
def test():
a = 14
exec('b = a + 1')
print(b)
test()
15
- 上面的代码在书中会报NameError的错误
- 默认
情况下, exec() 会在调用者局部和全局范围内执行代码。然而,在函数里面,传递给
exec() 的局部范围是拷贝实际局部变量组成的一个字典。因此,如果 exec() 如果执
行了修改操作,这种修改后的结果对实际局部变量值是没有影响的。
def test1():
x = 0
exec('x += 1')
print(x)
test1()
0
- 当你调用 locals() 获取局部变量时,你获得的是传递给 exec() 的
局部变量的一个拷贝。通过在代码执行后审查这个字典的值,那就能获取修改后的值
了.
def test2():
x = 0
loc = locals()
print("before: ",loc)
exec('x += 1')
print("after: ",loc)
print("x = ",x)
test2()
before: {'x': 0}
after: {'x': 1, 'loc': {...}}
x = 0
- 在使用 locals() 的时候,要注意操作顺序。每次它被调用的时候, locals()
会获取局部变量值中的值并覆盖字典中相应的变量。
def test3():
x = 0
loc = locals()
print(loc)
exec('x += 1')
print(loc)
locals()
print(loc)
test3()
{'x': 0}
{'x': 1, 'loc': {...}}
{'x': 0, 'loc': {...}}
- 作为locals的一个替代方案,可以使用自己的字典,并将它传递给exec()
def test4():
a = 14
loc = {'a':a}
glb = {}
exec('b = a + 1',glb, loc)
b = loc['b']
print(b)
test4()
15
9.24 解析并分析python的源代码
- 问题:如题
- 方案:如下
# 我们知道,python可以执行字符串形式的代码:
x = 32
eval('2 + 3 * 4 + x')
46
exec('for i in range(5): print(i)')
0
1
2
3
4
- ast 模块可以将python的源代码编译成可以被分析的抽象语法树(AST)
import ast
ex = ast.parse('2 + 3*4 + x', mode='eval')
ex
<_ast.Expression at 0x158ea10>
ast.dump(ex)
"Expression(body=BinOp(left=BinOp(left=Num(n=2), op=Add(), right=BinOp(left=Num(n=3), op=Mult(), right=Num(n=4))), op=Add(), right=Name(id='x', ctx=Load())))"
top = ast.parse('for i in range(5): pritn(i)')
top
<_ast.Module at 0x1a0d130>
ast.dump(top)
"Module(body=[For(target=Name(id='i', ctx=Store()), iter=Call(func=Name(id='range', ctx=Load()), args=[Num(n=5)], keywords=[]), body=[Expr(value=Call(func=Name(id='pritn', ctx=Load()), args=[Name(id='i', ctx=Load())], keywords=[]))], orelse=[])])"
9.25 拆解python字节码
- 问题:想要通过你的代码反编译成低级的字节码来查看它的底层工作机制
- 方案:使用dis模块
def countdown(n):
while n > 0:
print('T-minus',n)
n -= 1
print('Blastoff!')
import dis
dis.dis(countdown)
2 0 SETUP_LOOP 30 (to 32)
>> 2 LOAD_FAST 0 (n)
4 LOAD_CONST 1 (0)
6 COMPARE_OP 4 (>)
8 POP_JUMP_IF_FALSE 30
3 10 LOAD_GLOBAL 0 (print)
12 LOAD_CONST 2 ('T-minus')
14 LOAD_FAST 0 (n)
16 CALL_FUNCTION 2
18 POP_TOP
4 20 LOAD_FAST 0 (n)
22 LOAD_CONST 3 (1)
24 INPLACE_SUBTRACT
26 STORE_FAST 0 (n)
28 JUMP_ABSOLUTE 2
>> 30 POP_BLOCK
5 >> 32 LOAD_GLOBAL 0 (print)
34 LOAD_CONST 4 ('Blastoff!')
36 CALL_FUNCTION 1
38 POP_TOP
40 LOAD_CONST 0 (None)
42 RETURN_VALUE
countdown.__code__.co_code
b'x\x1e|\x00d\x01k\x04r\x1et\x00d\x02|\x00\x83\x02\x01\x00|\x00d\x038\x00}\x00q\x02W\x00t\x00d\x04\x83\x01\x01\x00d\x00S\x00'