# coding: utf-8
from __future__ import unicode_literals
import logging
from collections import Counter
from wxpy.compatible import *
from wxpy.utils import match_attributes, match_name, ensure_one
logger = logging.getLogger(__name__)
[文档]class Chats(list):
"""
多个聊天对象的列表,可用于搜索、更新、统计等功能
"""
def __init__(self, chat_list=None, source=None):
if chat_list:
super(Chats, self).__init__(chat_list)
self.source = source
def __add__(self, other):
return Chats(
super(Chats, self).__add__(other or list()),
self.source if self.source == getattr(other, 'source', None) else None
)
def _iter_match(self, keywords=None, **attributes):
def match(_chat):
if not match_name(_chat, keywords):
return
if not match_attributes(_chat, **attributes):
return
return True
for chat in self:
if match(chat):
yield chat
[文档] def get(self, keywords=None, **attributes):
"""
| 找到匹配所设条件的唯一聊天对象
| 若结果不唯一,或没有找到,会抛出 `ValueError` 异常
:param keywords:
| 聊天对象的名称关键词 (空格分隔, 不区分大小写)
| 用于在 remark_name, display_name, nickname, wxid 中进行搜索
:param attributes:
属性键值对,键可以是 sex(性别), province(省份), city(城市) 等,例如::
bot.chats.get(nickname='游否', city='深圳', sex=MALE)
:rtype: :class:`Chat`
"""
found = list(self._iter_match(keywords, **attributes))
return ensure_one(found)
[文档] def find(self, keywords=None, **attributes):
"""
| 类似于 any:`Chats.get`,不同之处在于仅返回 **首个匹配的** 聊天对象
| 也就是说: 即便列表内有多个匹配,也不会抛出异常; 若没有匹配的,则返回 None
:rtype: :class:`Chat`
"""
for chat in self._iter_match(keywords, **attributes):
return chat
[文档] def search(self, keywords=None, **attributes):
"""
类似于 any:`Chats.get`,不同之处在于会返回 **所有匹配的** 聊天对象
:rtype: :class:`Chats`
"""
return Chats(self._iter_match(keywords, **attributes), self.source)
[文档] def update(self):
"""
更新列表中的所有聊天对象
"""
cores = dict()
for chat in self:
cores.setdefault(chat.core, list()).append(chat)
for core in cores:
core.batch_get_contact(cores[core])
[文档] def stats(self, attribs=('sex', 'province', 'city')):
"""
统计各属性的分布情况
:param attribs: 需统计的属性列表或元组
:return: 统计结果
"""
def attr_stat(objects, attr_name):
return Counter(list(map(lambda x: getattr(x, attr_name), objects)))
from wxpy.utils import ensure_list
attribs = ensure_list(attribs)
ret = dict()
for attr in attribs:
ret[attr] = attr_stat(self, attr)
return ret
[文档] def stats_text(self, total=True, sex=True, top_provinces=10, top_cities=10):
"""
简单的统计结果的文本
:param total: 总体数量
:param sex: 性别分布
:param top_provinces: 省份分布
:param top_cities: 城市分布
:return: 统计结果文本
"""
from .group import Group
from wxpy.api.consts import FEMALE
from wxpy.api.consts import MALE
from wxpy.api.core import Core
def top_n_text(attr, n):
top_n = list(filter(lambda x: x[0], stats[attr].most_common()))[:n]
top_n = ['{}: {} ({:.2%})'.format(k, v, v / len(self)) for k, v in top_n]
return '\n'.join(top_n)
stats = self.stats()
text = str()
if total:
if self.source:
if isinstance(self.source, Core):
user_title = '微信好友'
nickname = self.source.name
elif isinstance(self.source, Group):
user_title = '群成员'
nickname = self.source.nickname
else:
raise TypeError('source should be Bot or Group')
text += '{nickname} 共有 {total} 位{user_title}\n\n'.format(
nickname=nickname,
total=len(self),
user_title=user_title
)
else:
text += '共有 {} 位用户\n\n'.format(len(self))
if self:
if sex:
males = stats['sex'].get(MALE, 0)
females = stats['sex'].get(FEMALE, 0)
text += '男性: {males} ({male_rate:.1%})\n女性: {females} ({female_rate:.1%})\n\n'.format(
males=males,
male_rate=males / len(self),
females=females,
female_rate=females / len(self),
)
if top_provinces:
text += 'TOP {} 省份\n{}\n\n'.format(
top_provinces,
top_n_text('province', top_provinces)
)
if top_cities:
text += 'TOP {} 城市\n{}\n\n'.format(
top_cities,
top_n_text('city', top_cities)
)
return text