Python元编程

Posted HT . WANG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python元编程相关的知识,希望对你有一定的参考价值。

1.在函数上添加包装器

想使用额外的代码包装一个函数,可以定义一个装饰器函数

import time
from functools import wraps

def timethis(func): #定时器装饰函数
    '''
    Decorator that reports the execution time.
    '''
    @wraps(func) # @wraps 装饰器来注解底层包装函数,确保被包装后的函数保留它的元信息
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs) #调用了原始函数func并将其结果返回
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper #新的函数包装器被作为结果返回来代替原始函数


>>> @timethis
... def countdown(n):
...     '''
...     Counts down
...     '''
...     while n > 0:
...         n -= 1
...
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown(10000000)
countdown 0.87188299392912
>>>

一个装饰器就是一个函数,它接受一个函数作为参数并返回一个新的函数

调用方式(1):

@timethis
def countdown(n):
    pass

调用方式(2):

def countdown(n):
    pass
countdown = timethis(countdown)

2.解除一个装饰器

一个装饰器已经作用在一个函数上,想撤销它,直接访问原始的未包装的那个函数

假设装饰器是通过 @wraps 实现的,那么可以通过访问 __wrapped__ 属性来访问原始函数

from functools import wraps

def decorator1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Decorator 1')
        return func(*args, **kwargs)
    return wrapper

def decorator2(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Decorator 2')
        return func(*args, **kwargs)
    return wrapper

@decorator1
@decorator2
def add(x, y):
    return x + y

>>> add(2, 3) #调用装饰器
Decorator 1
Decorator 2
5
>>> add.__wrapped__(2, 3) #去除装饰器
5
>>>

3.定义一个装饰器

(1)带参数的装饰器

from functools import wraps
import logging

def logged(level, name=None, message=None):最外层的函数 logged() 接受参数并将它们作用在内部的装饰器函数decorate @wraps上面
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs): #可任意接收最外层传入的参数
            log.log(level, logmsg)
            return func(*args, **kwargs)
        return wrapper
    return decorate #返回对应的日志message

# Example use
@logged(logging.DEBUG) #传入参数为日志level
def add(x, y):
    return x + y

@logged(logging.CRITICAL, 'example') #传入参数为日志level和name
def spam():
    print('Spam!')

(2)自定义属性的装饰器

from functools import wraps, partial
import logging
# Utility decorator to attach a function as an attribute of obj
def attach_wrapper(obj, func=None): #访问函数接收对应的装饰函数
    if func is None:
        return partial(attach_wrapper, obj)
    setattr(obj, func.__name__, func) #访问函数设置对应装饰函数的属性值
    return func

def logged(level, name=None, message=None):
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)

        # Attach setter functions
        @attach_wrapper(wrapper) #访问函数被作为一个属性赋值给装饰函数
        def set_level(newlevel):
            nonlocal level #类似于全局变量 定义可以访问外部函数的变量
            level = newlevel #设置新值的同时也会改变外部函数的值

        @attach_wrapper(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg = newmsg

        return wrapper

    return decorate

# Example use
@logged(logging.DEBUG)
def add(x, y):
    return x + y

@logged(logging.CRITICAL, 'example')
def spam():
    print('Spam!')


>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> add(2, 3)
DEBUG:__main__:add
5
>>> # Change the log message
>>> add.set_message('Add called')
>>> add(2, 3)
DEBUG:__main__:Add called
5
>>> # Change the log level
>>> add.set_level(logging.WARNING)
>>> add(2, 3)
WARNING:__main__:Add called
5
>>>

(3)带可选参数的装饰器

from functools import wraps, partial
import logging

def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
    if func is None: #如果装饰函数传入参数为空
        return partial(logged, level=level, name=name, message=message) #利用 functools.partial方法,它会返回一个未完全初始化的自身,除了被装饰函数外其他参数都已经确定下来了

    logname = name if name else func.__module__
    log = logging.getLogger(logname)
    logmsg = message if message else func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        log.log(level, logmsg)
        return func(*args, **kwargs)

    return wrapper

# Example use
@logged
def add(x, y):
    return x + y

@logged(level=logging.CRITICAL, name='example')
def spam():
    print('Spam!')

4.类装饰器

(1)将装饰器定义为类的一部分

在类中定义装饰器,并将其作用在其他函数或方法上

from functools import wraps

class A:
    # Decorator as an instance method
    def decorator1(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 1')
            return func(*args, **kwargs)
        return wrapper

    # Decorator as a class method
    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 2')
            return func(*args, **kwargs)
        return wrapper

# 实例调用
a = A()
@a.decorator1
def spam():
    pass
# 类调用
@A.decorator2
def grok():
    pass

(2)将装饰器定义为类

为了将装饰器定义成一个实例,你需要确保它实现了 __call__() 和 __get__() 方法

import types
from functools import wraps

class Profiled: #可以将它当做一个普通的装饰器来使用
    def __init__(self, func):
        wraps(func)(self)
        self.ncalls = 0

    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

@Profiled #类外调用
def add(x, y):
    return x + y

class Spam:
    @Profiled #类内使用
    def bar(self, x):
        print(self, x)


>>> add(2, 3)
5
>>> add(4, 5)
9
>>> add.ncalls #调用次数
2
>>> s = Spam() #类实例化 调用 类内使用
>>> s.bar(1)
<__main__.Spam object at 0x10069e9d0> 1
>>> s.bar(2)
<__main__.Spam object at 0x10069e9d0> 2
>>> s.bar(3)
<__main__.Spam object at 0x10069e9d0> 3
>>> Spam.bar.ncalls
3

5.为类和静态方法提供装饰器

类或静态方法提供装饰器是很简单的,不过要确保装饰器在 @classmethod 或 @staticmethod 之前

import time
from functools import wraps

# A simple decorator
def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        print(end-start)
        return r
    return wrapper

# Class illustrating application of the decorator to different kinds of methods
class Spam:
    @timethis
    def instance_method(self, n):
        print(self, n)
        while n > 0:
            n -= 1

    @classmethod
    @timethis
    def class_method(cls, n):
        print(cls, n)
        while n > 0:
            n -= 1
 
    @staticmethod #注意 保证两个装饰器的先后顺序 如果顺序颠倒则报错 因为@classmethod 和 @staticmethod 实际上并不会创建可直接调用的对象, 而是创建特殊的描述器对象。因此当你试着在其他装饰器中将它们当做函数来使用时就会出错
    @timethis
    def static_method(n):
        print(n)
        while n > 0:
            n -= 1

>>> s = Spam()
>>> s.instance_method(1000000)
<__main__.Spam object at 0x1006a6050> 1000000
0.11817407608032227
>>> Spam.class_method(1000000)
<class '__main__.Spam'> 1000000
0.11334395408630371
>>> Spam.static_method(1000000)
1000000
0.11740279197692871
>>>

6.装饰器的功能

(1)装饰器为被包装函数增加参数

可以使用关键字参数来给被包装函数增加额外参数

from functools import wraps

def optional_debug(func):
    @wraps(func)
    def wrapper(*args, debug=False, **kwargs):
        if debug:
            print('Calling', func.__name__)
        return func(*args, **kwargs)

    return wrapper

>>> @optional_debug
... def spam(a,b,c):
...     print(a,b,c)
...
>>> spam(1,2,3)
1 2 3
>>> spam(1,2,3, debug=True)
Calling spam
1 2 3
>>>

(2)使用装饰器扩充类的功能

在不希望使用继承或重写的方式来修改它的方法,使用装饰器为最佳方案

def log_getattribute(cls):
    # Get the original implementation
    orig_getattribute = cls.__getattribute__

    # Make a new definition
    def new_getattribute(self, name): #新方法 重写内置__getattribute__
        print('getting:', name)
        return orig_getattribute(self, name)

    # Attach to the class and return
    cls.__getattribute__ = new_getattribute
    return cls

# Example use
@log_getattribute #调用实例 装饰器
class A:
    def __init__(self,x):
        self.x = x
    def spam(self):
        pass


>>> a = A(42)
>>> a.x
getting: x
42
>>> a.spam()
getting: spam
>>>

7.使用元类控制实例的创建

(1)用户只能调用这个类的静态方法,而不能使用通常的方法来创建它的实例

class NoInstances(type):
    def __call__(self, *args, **kwargs):
        raise TypeError("Can't instantiate directly")

class Spam(metaclass=NoInstances):
    @staticmethod
    def grok(x):
        print('Spam.grok')


>>> Spam.grok(42)
Spam.grok
>>> s = Spam()
>>> Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "example1.py", line 7, in __call__
        raise TypeError("Can't instantiate directly")
TypeError: Can't instantiate directly

(2)单例模式(一个类只能有一个实例化对象)

class Singleton(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            return self.__instance

# Example
class Spam(metaclass=Singleton): #指明类型
    def __init__(self):
        print('Creating Spam')


>>> a = Spam()
Creating Spam
>>> b = Spam()
>>> a is b
True
>>> c = Spam()
>>> a is c
True
>>>

8.定义有可选参数的元类

class MyMeta(type):
    # Optional
    @classmethod
    def __prepare__(cls, name, bases, *, debug=False, synchronize=False):
        # __prepare__() 方法在所有类定义开始执行前首先被调用,用来创建类命名空间。 通常来讲,这个方法只是简单的返回一个字典或其他映射对象
        pass
        return super().__prepare__(name, bases)

    # Required
    def __new__(cls, name, bases, ns, *, debug=False, synchronize=False):
        # __new__() 方法被用来实例化最终的类对象。它在类的主体被执行完后开始执行
        pass
        return super().__new__(cls, name, bases, ns)

    # Required
    def __init__(self, name, bases, ns, *, debug=False, synchronize=False):
        # __init__() 方法最后被调用,用来执行其他的一些初始化工作
        pass
        super().__init__(name, bases, ns)

class Spam(metaclass=MyMeta, debug=True, synchronize=True): #使用 ``metaclass``关键字参数来指定特定的元类
#为了使元类支持这些关键字参数,必须确保在 __prepare__() , __new__() 和 __init__() 方法中 都使用强制关键字参数
    pass

9.在类上强制使用编程规约

如果你想监控类的定义,通常可以通过定义一个元类。一个基本元类通常是继承自 type 并重定义它的 __new__() 方法 或者是 __init__() 方法。

class NoMixedCaseMeta(type):#该元类拒绝任何有混合大小写名字作为方法的类定义
    def __new__(cls, clsname, bases, clsdict):
        for name in clsdict:
            if name.lower() != name:
                raise TypeError('Bad attribute name: ' + name)
        return super().__new__(cls, clsname, bases, clsdict)

class Root(metaclass=NoMixedCaseMeta):#为了使用这个元类,通常要将它放到到一个顶级父类定义中,然后其他的类继承这个顶级父类
    pass

class A(Root):
    def foo_bar(self): # Ok
        pass

class B(Root):
    def fooBar(self): # TypeError
        pass

注意:
元类的一个关键特点是它允许你在定义的时候检查类的内容。在重新定义 __init__() 方法中, 你可以很轻松的检查类字典、父类等等。并且,一旦某个元类被指定给了某个类,那么就会被继承到所有子类中去。 因此,一个框架的构建者就能在大型的继承体系中通过给一个顶级父类指定一个元类去捕获所有下面子类的定义。

10. 在定义的时候初始化类的成员

在类定义时就执行初始化或设置操作是元类的一个典型应用场景。本质上讲,一个元类会在定义时被触发, 这时候你可以执行一些额外的操作。

import operator

class StructTupleMeta(type): #类 StructTupleMeta 获取到类属性 _fields 中的属性名字列表, 然后将它们转换成相应的可访问特定元组槽的方法
    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for n, name in enumerate(cls._fields):
            setattr(cls, name, property(operator.itemgetter(n))) #函数 operator.itemgetter() 创建一个访问器函数, 然后 property() 函数将其转换成一个属性

class StructTuple(tuple, metaclass=StructTupleMeta):
    _fields = []
    def __new__(cls, *args):
        if len(args) != len(cls._fields):
            raise ValueError(' arguments required'.format(len(cls._fields)))
        return super().__new__(cls,args)

11.避免重复的属性方法

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(self):
        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, expected_type): #生成属性并返回这个属性对象 效果跟将它里面的代码放到类定义中去是一样的
    storage_name = '_' + name

    @property
    def prop(self):
        return getattr(self, storage_name)

    @prop.setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError(' must be a '.format(name, expected_type))
        setattr(self, storage_name, value)

    return prop

# Example use
class Person:
    name = typed_property('name', str)
    age = typed_property('age', int)

    def __init__(self, name, age):
        self.name = name
        self.age = age

12.定义上下文管理器的简单方法

实现一个新的上下文管理器的最简单的方法就是使用 contexlib 模块中的 @contextmanager 装饰器。

import time
from contextlib import contextmanager

@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]

@contextmanager 应该仅仅用来写自包含的上下文管理函数。 如果你有一些对象(比如一个文件、网络连接或锁),需要支持 with 语句,你需要定义一个类里面包含一个 __enter__() 和一个 __exit__() 方法,那么你就需要单独实现 __enter__() 方法和 __exit__() 方法

13.在局部变量域中执行代码 

想在使用范围内执行某个代码片段,并且希望在执行后所有的结果都不可见。

>>> a = 13
>>> exec('b = a + 1')
>>> print(b)
14
>>>

上述代码中'b'为局部变量 无法在其他域内使用

>>> def test():
...     a = 13
...     exec('b = a + 1')
...     print(b)
...
>>> test()
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 4, in test
NameError: global name 'b' is not defined
>>>

需要在调用 exec() 之前使用 locals() 函数来得到一个局部变量字典。 之后你就能从局部字典中获取修改过后的变量值了

>>> def test():
...     a = 13
...     loc = locals() #当你调用 locals() 获取局部变量时,你获得的是传递给 exec() 的局部变量的一个拷贝
...     exec('b = a + 1')
...     b = loc['b'] #每次它被调用的时候, locals() 会获取局部变量值中的值并覆盖字典中相应的变量,进行值更新 即会更新'b'的值
...     print(b)
...
>>> test()
14
>>>

14.解析与分析Python源码

Python能够计算或执行字符串形式的源代码

>>> x = 42
>>> eval('2 + 3*4 + x')
56
>>> exec('for i in range(10): print(i)')
0
1
2
3
4
5
6
7
8
9
>>>

ast 模块能被用来将Python源码编译成一个可被分析的抽象语法树(AST)

dis 模块可以被用来输出任何Python函数的反编译结果

以上是关于Python元编程的主要内容,如果未能解决你的问题,请参考以下文章

在第一个元素在特定范围内的元组中查找元组列表中的最小值

Java元编程及其应用

Python基础四--random,so ,sys模块

Python-元编程

命名元组内命名元组的 Python 语法

T-SQL 查询和编程基础