文章目录

  • 概述
  • 将运算符映射到函数
  • 1基本方法 介绍
  • 2 三个类介绍
  • 2-1 attrgetter 介绍
  • 2-2 itemgetter 使用介绍
  • 1 用来排序
  • 2 通过 获取多个值
  • 2-3 methodcaller 介绍
  • 3 性能测试
  • 4 总结
  • 5 参考文档



官方 operator 模块

operator 模块提供了一套与Python的内置运算符对应的高效率函数 . 许多函数名与特殊方法名相同,只是没有双下划线。为了向后兼容性,也保留了许多包含双下划线的函数。函数包含的种类有:对象的比较运算、逻辑运算、数学运算以及序列运算。

这个模块提供了很多和Python 一样的操作符, 这里 只是封装一个函数 进行调用

举个例子

import operator
 
 b = operator.add(3,5)
 print(b)  # 8

概述

operator 模块 封装了很多操作相关的函数, 比如 加,减 乘除 ,比较运算 ,逻辑运算 ,矩阵相乘. 还有一些封装好的类, 用起来 效率 比较高效. 本文 根据个人的使用经验 简单介绍一下, 这些类的常用用法.

将运算符映射到函数


1基本方法 介绍

DESCRIPTION
    This module exports a set of functions implemented in C corresponding
    to the intrinsic operators of Python.  For example, operator.add(x, y)
    is equivalent to the expression x+y.  The function names are those
    used for special methods; variants without leading and trailing
    '__' are also provided for convenience.



1 比较运算符

>  >=   <   <=  !=   
对应函数的名称
gt  ge  lt   le  ne 



2 逻辑运算符

与(&&) ,或(|) , 非(!), 异或 (^)  , is  , is not ,truth 


对应函数的名称 
and_  , or_ ,  not_  ,  xor



3 一些数学运算符 和一些  按位 运算符  bitwise Operations

+ - * /  //(整除)   %(取余) mod ,   


matmul (矩阵相乘),   mul 



floordiv  
index 

mod


取绝对值 abs   ,  pos   , neg
 
inv   ~ 取反 
and_  按位与





4 一些操作 

contact     contains 

def concat(a, b):
    "Same as a + b, for a and b sequences."
    if not hasattr(a, '__getitem__'):
        msg = "'%s' object can't be concatenated" % type(a).__name__
        raise TypeError(msg)
    return a + b

def contains(a, b):
    "Same as b in a (note reversed operands)."
    return b in a

def countOf(a, b):
    "Return the number of times b occurs in a."
    count = 0
    for i in a:
        if i == b:
            count += 1
    return count

def delitem(a, b):
    "Same as del a[b]."
    del a[b]

def getitem(a, b):
    "Same as a[b]."
    return a[b]

def indexOf(a, b):
    "Return the first index of b in a."
    for i, j in enumerate(a):
        if j == b:
            return i
    else:
        raise ValueError('sequence.index(x): x not in sequence')

def setitem(a, b, c):
    "Same as a[b] = c."
    a[b] = c

def length_hint(obj, default=0):
    """
    Return an estimate of the number of items in obj.
    This is useful for presizing containers when building from an iterable.

    If the object supports len(), the result will be exact. Otherwise, it may
    over- or under-estimate by an arbitrary amount. The result will be an
    integer >= 0.
    """
    if not isinstance(default, int):
        msg = ("'%s' object cannot be interpreted as an integer" %
               type(default).__name__)
        raise TypeError(msg)

    try:
        return len(obj)
    except TypeError:
        pass

    try:
        hint = type(obj).__length_hint__
    except AttributeError:
        return default

    try:
        val = hint(obj)
    except TypeError:
        return default
    if val is NotImplemented:
        return default
    if not isinstance(val, int):
        msg = ('__length_hint__ must be integer, not %s' %
               type(val).__name__)
        raise TypeError(msg)
    if val < 0:
        msg = '__length_hint__() should return >= 0'
        raise ValueError(msg)
    return val



# In-place Operations *********************************************************
# 注意这些 并不能原地修改 值.,...  主要是Python ,函数 参数的传递方式, 
# 我感觉是值 copy 一份到 , 函数里面. 所以 值并没有变. 可以看看例子.


def iadd(a, b):
    "Same as a += b."
    a += b
    return a

def iand(a, b):
    "Same as a &= b."
    a &= b
    return a

def iconcat(a, b):
    "Same as a += b, for a and b sequences."
    if not hasattr(a, '__getitem__'):
        msg = "'%s' object can't be concatenated" % type(a).__name__
        raise TypeError(msg)
    a += b
    return a

def ifloordiv(a, b):
    "Same as a //= b."
    a //= b
    return a

def ilshift(a, b):
    "Same as a <<= b."
    a <<= b
    return a

def imod(a, b):
    "Same as a %= b."
    a %= b
    return a

def imul(a, b):
    "Same as a *= b."
    a *= b
    return a

def imatmul(a, b):
    "Same as a @= b."
    a @= b
    return a

def ior(a, b):
    "Same as a |= b."
    a |= b
    return a

def ipow(a, b):
    "Same as a **= b."
    a **=b
    return a

def irshift(a, b):
    "Same as a >>= b."
    a >>= b
    return a

def isub(a, b):
    "Same as a -= b."
    a -= b
    return a

def itruediv(a, b):
    "Same as a /= b."
    a /= b
    return a

def ixor(a, b):
    "Same as a ^= b."
    a ^= b
    return a

2 三个类介绍

operator 中 有三个类, attrgetter , itemgetter ,methocaller 这三个类.

2-1 attrgetter 介绍

通过 attrgetter 可以 获取 对象的属性, 然后进行 排序 操作 .

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@User    : frank 
@Time    : 2019/7/29 22:53
@File    : custom_sort.py
@Email   : frank.chang@xinyongfei.cn

operator  中 可以使用  attrgetter 获取 对象的 属性

attrgetter('xxx')  来获取对象的属性 .

"""
from operator import attrgetter


class Student:
    pass

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

    def __str__(self):
        return '%s(name=%r,age=%r,score=%r)' % (self.__class__.__name__, self.name, self.age, self.score)

    __repr__ = __str__


if __name__ == '__main__':
    std1 = Student("A", 11, 23)
    std2 = Student("B", 13, 10)
    std3 = Student("C", 16, 15)
    std4 = Student("D", 34, 4)

    students = [std1, std2, std3, std4]

    # 按照分数 排序 , 升序
    print("按照分数 排序,升序:")
    students.sort(key=lambda student: student.score, reverse=False)
    print(students)

    # 按照 年龄排序 降序
    print("按照 年龄排序 降序:")
    print(sorted(students, key=attrgetter('age'), reverse=True))

    # 按照 分数 排序
    print("按照 分数 排序: ")
    print(sorted(students, key=attrgetter('score'), reverse=False))

结果如下:

按照分数 排序,升序
[Student(name='D',age=34,score=4), Student(name='B',age=13,score=10), Student(name='C',age=16,score=15), Student(name='A',age=11,score=23)]
按照 年龄排序 降序:
[Student(name='D',age=34,score=4), Student(name='C',age=16,score=15), Student(name='B',age=13,score=10), Student(name='A',age=11,score=23)]
按照 分数 排序: 
[Student(name='D',age=34,score=4), Student(name='B',age=13,score=10), Student(name='C',age=16,score=15), Student(name='A',age=11,score=23)]
from operator import attrgetter

class Point:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return 'Point({0},{1})'.format(self.x, self.y)


points = [Point(x, y) for x, y in zip([3, 2, 1, 0, 0.2], [0.1, -1, 5, 3, 1, 8])]

# 通过获取 points中 x 的值 进行排序. 
points = sorted(points, key=attrgetter('x'))

for p in points:
    print(p)

2-2 itemgetter 使用介绍

官方解释 :

Return a callable object that fetches item from its operand using the operand's __getitem__() method.

If multiple items are specified, returns a tuple of lookup values. For example:

After f = itemgetter(2), the call f(r) returns r[2].

After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]).

这个 类会返回 一个 可调用对象 ,它会从 它的操作数里面取值,会调用 操作数的__getitem__() 方法 获取值.

如果 传入了多个 item , 那么 结果 就会返回一个元祖 .

返回使用操作数的__getitem__()方法从其操作数获取的可调用对象。如果指定了多个项,则返回查找值的元组。例如:

1 用来排序
from operator import itemgetter


def one_example():
  
    data = [
       # (name , age)
        ("frank", 10),
        ("frank1", 15),
        ("frank2", 19),
        ("frank3", 12),
        ("frank4", 17),
        ("frank5", 11),
        ("frank6", 18),

    ]

    data.sort(key=itemgetter(1), reverse=True)
    print(data)
[('frank2', 19), ('frank6', 18), ('frank4', 17), ('frank1', 15), ('frank3', 12), ('frank5', 11), ('frank', 10)]
2 通过 获取多个值

传入多个参数 可以获取多个值. 看下面的例子

itemgetter 里面 甚至 可以传入一个 slice 对象

>>> from operator import itemgetter
>>> itemgetter(1)('ABCDEFG')
'B'
>>> itemgetter(1, 3, 5)('ABCDEFG')
('B', 'D', 'F')
>>> itemgetter(slice(2, None))('ABCDEFG')
'CDEFG'

>>> inventory = [('apple', 3), ('banana', 2), ('pear', 5), ('orange', 1)]
>>> getcount = itemgetter(1)
>>> list(map(getcount, inventory))
[3, 2, 5, 1]

>>> getname = itemgetter(0)
>>> list(map(getname,inventory))
['apple', 'banana', 'pear', 'orange']

>>> sorted(inventory, key=getcount)
[('orange', 1), ('banana', 2), ('apple', 3), ('pear', 5)]

2-3 methodcaller 介绍

class methodcaller:
    """
    Return a callable object that calls the given method on its operand.
    After f = methodcaller('name'), the call f(r) returns r.name().
    After g = methodcaller('name', 'date', foo=1), the call g(r) returns
    r.name('date', foo=1).
    """
    __slots__ = ('_name', '_args', '_kwargs')

    def __init__(*args, **kwargs):
        if len(args) < 2:
            msg = "methodcaller needs at least one argument, the method name"
            raise TypeError(msg)
        self = args[0]
        self._name = args[1]
        if not isinstance(self._name, str):
            raise TypeError('method name must be a string')
        self._args = args[2:]
        self._kwargs = kwargs

    def __call__(self, obj):
        return getattr(obj, self._name)(*self._args, **self._kwargs)

    def __repr__(self):
        args = [repr(self._name)]
        args.extend(map(repr, self._args))
        args.extend('%s=%r' % (k, v) for k, v in self._kwargs.items())
        return '%s.%s(%s)' % (self.__class__.__module__,
                              self.__class__.__name__,
                              ', '.join(args))

    def __reduce__(self):
        if not self._kwargs:
            return self.__class__, (self._name,) + self._args
        else:
            from functools import partial
            return partial(self.__class__, self._name, **self._kwargs), self._args

举个例子

operator.methodcaller(name[, args…])

有以下场景 :

你有一个字符串形式的方法名称,想通过它调用某个对象的对应方法。

import math
from operator import methodcaller


class Point:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return 'Point({0},{1})'.format(self.x, self.y)

    def distance(self, x, y):
        return math.hypot(self.x - x, self.y - y)


points = [Point(x, y) for x, y in zip([3, 2, 1, 0, 0.2], [0.1, -1, 5, 3, 1, 8])]


# 按离原点的距离排序 
points.sort(key=methodcaller('distance', 0, 0))

for p in points:
    print(p)
# operator.methodcaller() 创建一个可调用对象,并同时提供所有必要参数, 然后调用的时候只需要将实例对象传递给它即可,比如:  

import math
from operator import methodcaller

p = Point(3, 4)

methodcaller('distance', 0, 0)
operator.methodcaller('distance', 0, 0)
d= methodcaller('distance', 0, 0)
d
operator.methodcaller('distance', 0, 0)
d(p)
5.0

3 性能测试

测试 itemgetter , 和自己写 一个匿名函数之间排序 差异

时间的一个装饰器函数

from functools import wraps
import time 

now = time.time

def fn_timer(fn=None, *, prefix=""):
    """
    计算 fn 的运算时间

    :param fn: 函数
    :param prefix:
    :return:
    """
    if fn is None:
        return partial(fn_timer, prefix=prefix)

    @wraps(fn)
    def function_timer(*args, **kwargs):
        start = now()
        result = fn(*args, **kwargs)
        t = now() - start
        if t > 0.002:
            logger.info(f'{prefix}{fn.__name__} total running time {now() - start} seconds')
        return result

    return function_timer
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@User    : frank 
@Time    : 2019/7/30 11:16
@File    : compare.py
@Email  : frank.chang@xinyongfei.cn
"""
import random
import operator

from collections import defaultdict
from util.tools import fn_timer
from config.APP import LOG_PATH
from util.log import configure_logging
configure_logging(LOG_PATH)


def create_mobile():
    prelist = ["130", "131", "132", "133", "134", "135", "136", "137", "138", "139",
               "147", "150", "151", "152", "153", "155", "156", "157", "158", "159",
               "186", "187", "188", "189"]

    mobile = random.choice(prelist) + "".join(random.choice("0123456789") for _ in range(8))
    return mobile


item = {
    "peer_number": "13121111111",
    "duration": 15,
    "details_id": "xxxxxxxxxxxxx",
    "dial_type": "DIALED",
    "fee": 0,
    "location": "上海",
    "time": "2011-11-14 12:27:07",
    "location_type": "国内被叫"
}

calls = []

for i in range(10_0000):
    copy = item.copy()

    copy['duration'] += random.randint(10, 60)
    copy['mobile'] = create_mobile()
    calls.append(copy)


# 记录手机号对应 通话时长
durations = defaultdict(int)


for call in calls:
    mobile = call.get('mobile', "")
    durations[mobile] = call.get('duration')

print(len(durations))


@fn_timer()
def sort_call(calls):
    # 按照duration 排序
    sorted_by_duration = sorted(calls, key=lambda e: e[1], reverse=True)
    ret = sorted_by_duration[0:20]
    return ret


@fn_timer()
def sort_call2(calls):
    duration = operator.itemgetter(1)
    # 按照duration 排序
    sorted_by_duration = sorted(calls, key=duration, reverse=True)
    ret = sorted_by_duration[0:20]
    return ret


if __name__ == '__main__':
    sort_call2(calls=durations)
    sort_call(calls= durations)
    pass

测试 排序花费的时间 .

数据量

itemgetter 单位(s)

匿名函数 单位(s)

100000

0.01939702033996582

0.023350000381469727

500000

0.10256004333496094

0.12110304832458496

100_0000

0.232496976852417

0.25639915466308594

从测试的性能可以看出 , itemgetter 还是要比匿名函数的效率要高一些. 对于一百万的数据的排序, 两百多毫秒 也就完成了 . 所以还是 库函数 的效率比自己写的效率要 高一些 呀.

4 总结

本文总结 , 简单介绍了 operator 模块 的一些 函数的用法 ,以及类的用法. attrgetter , itemgetter , methodcaller 三个类 的基本使用 .