首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Python中的Strawpoll

Python中的Strawpoll
EN

Code Review用户
提问于 2015-09-24 11:13:35
回答 1查看 620关注 0票数 5

作为一个在构建软件方面有一些新手经验的Python初学者,我决定为Strawpoll编写一个API包装器将是学习语言和设计模式的一个很好的练习。

我的目的是完成整个包装器的编写,但我觉得有些地方不对劲,但我无法确定为什么我编写的代码没有我所见过的其他Python库那么有条理。

我认为我已经做了一个好的工作,分离和组织模块,但希望有一些评论,如何可以做得更好/以一种更仿生的方式。

如果你对我的决策/思考过程有任何疑问,请告诉我,我会尽力回答这些问题的。

下面是我绝对希望得到一些帮助的模块:

基类:

strawpoll/base/strawpoll_base.py

代码语言:javascript
运行
复制
"""
A python module to provide the base class for Strawpoll's JSON API:
https://strawpoll.me/api/v2/polls

The StrawpollAPIWriter class provides methods for:
* Calculations on fetched poll data
* Fetching poll data is left to the reader class to define
"""
import re


class StrawpollAPIBase(object):
    """
    TODO: Document this class
    """
    API_KEYWORDS = frozenset(
        [u'title', u'options', u'votes',
         u'multi', u'permissive', u'id', u'captcha'])

    API_ENDPOINT = 'https://strawpoll.me/api/v2/polls/'

    URL_PATTERN = re.compile('^https?://strawpoll.me/(?P<id>[1-9][0-9]*)/?r?')

    USER_AGENT = 'Strawpoll API Reader'

    API_POST_HEADERS = {
        'Content-Type': 'application/json',
        'X-Requested-With': 'StrawpollAPIWriter, github=http://git.io/vsV1E'
    }

    def __init__(self, data={}):
        self.id = None
        self.title = None
        self.options = None
        self.votes = None
        self.multi = False
        self.permissive = False
        self.captcha = False

        for key in data.keys():
            try:
                setattr(self, key, data[key])
            except AttributeError:
                continue

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    # Begin instance methods
    def total_votes(self):
        """ Returns the sum of votes cast for all option in a strawpoll """
        return sum(self.votes)

    def normalize(self):
        """ Returns Normalized votes on a 0.0 - 1.0 scale """
        total = self.total_votes()
        return [vote / total for vote in self.votes]

    def votes_for(self, option):
        """
        Returns the number of votes an option recieved
        Return None if no such option exists
        """
        try:
            return self.votes[self.options.index(option)]
        except ValueError:
            return None

    def normalized_votes_for(self, option):
        """
        Returns the fraction of votes an option recieved
        Return None if no such option exists
        """
        return self.normalize()[self.options.index(option)]

    def winner(self):
        """ Returns the option that got the most votes """
        most_popular_index = self.votes.index(max(self.votes))
        return self.options[most_popular_index]

    def loser(self):
        """ Returns the option that got the least votes """
        least_popular_index = self.votes.index(min(self.votes))
        return self.options[least_popular_index]

    def to_clean_dict(self):
        """
        Cleans up self.__dict__ so that it is accepted as json by strawpoll API
        """
        cdict = self.__dict__
        for key in cdict.keys():
            if cdict[key] == None:
                del cdict[key]
        return cdict

这是从strawpoll.base派生的API读取器类:

strawpoll/strawpoll_api_reader.py

代码语言:javascript
运行
复制
"""
A python module to provide methods to read data from existing polls using
Strawpoll's JSON API (https://strawpoll.me/api/v2/polls).
The StrawpollAPIReader class provides methods for:
* Capturing all poll data in an instance
* Performing basic options such as normalizing votes for each option
* Finding the winner / loser
"""

from __future__ import division
from base.strawpoll_base import StrawpollAPIBase

import requests
import json


class StrawpollAPIReader(StrawpollAPIBase):

    def __init__(self, data={}):
        """ Construct self using a dictionary of data """
        super(StrawpollAPIReader, self).__init__()
        for key in data.keys():
            # This actually worked.
            # hasattr -> setattr died with AttributeErrors
            try:
                setattr(self, key, data[key])
            except AttributeError:
                # Log this?
                continue

    @classmethod
    def from_json(cls, json_string):
        """
        Constructs a poll instance from a JSON string
        returned by strawpoll.me API
        """
        api_response = json.loads(json_string)
        response_keys = set(api_response.keys())
        if response_keys.issubset(cls.API_KEYWORDS):
            return cls(data=api_response)

    @classmethod
    def from_apiv2(cls, id):
        """ Constructs a poll instance using a strawpoll id """
        response = requests.get(cls.API_ENDPOINT + str(id))
        return cls.from_json(response.text)

    @classmethod
    def from_url(cls, url):
        """
        Constructs a poll instance using a strawpoll url, that matches:
        ^https?://strawpoll.me/[1-9][0-9]*/?r?
        Issues: Still matches 'http://strawpoll.me/1r', but ignores the r at
        the very end
        """
        matches = cls.URL_PATTERN.match(url)
        if matches is not None:
            # Note: we are actually passing a str and not an int
            return cls.from_apiv2(matches.group('id'))

如果我所做的不够好的话,请告诉我你认为组织这两者的更好的方法。

EN

回答 1

Code Review用户

回答已采纳

发布于 2015-09-26 00:27:51

文档

我想我的第一个问题是:

代码语言:javascript
运行
复制
""
TODO: Document this class
"""

天使会在你写代码的时候哭泣,而不是先记录下来。一般来说,在写之前,你应该知道它应该做什么。实现细节是不相关的,无论如何也不应该放在文档中(它们属于注释)。

ABC

撇开这一点不说,我想我看不出基类的意义。除非您希望实现其他StrawpollAPI,否则应该将其子类化。

__init__

另外,我不喜欢你写__init__的方式。你为什么要这么做?

代码语言:javascript
运行
复制
for key in data.keys():
    try:
        setattr(self, key, data[key])
    except AttributeError:
        continue

您不应该忽略这样的错误--至少记录错误(就像您在子类中评论的那样)。除此之外,在我看来,API中应该有有限数量的已知键--在构造函数中接受它们作为带有合理默认值的关键字参数。毕竟,引用Python的禅宗,

外显好于内隐。

我想说的是

代码语言:javascript
运行
复制
def __init__(self, id_=None, title=None, options=None ....):
    self.id = id_
    ...

它要干净得多,并且不需要在如何称呼它上有很大的不同:

代码语言:javascript
运行
复制
cls(**api_response)

而不是

代码语言:javascript
运行
复制
cls(data=api_response)

如果它有奇怪的、无效的数据,那么你会大声地得到一个错误。如果您仍然想要处理它,那么请使用

代码语言:javascript
运行
复制
def __init__(self, id_=None, ..., **kwargs):

然后适当地处理额外的关键字参数。

一个可能的问题是名称id_ --不幸的是,id是Python中的一个内置函数,使用它作为参数将在本地覆盖它。同样不幸的是,API很可能不会有id_的名称。您可以始终从id模块访问__builtins__,或者设置一个类成员,如

代码语言:javascript
运行
复制
class StrawpollAPIBase(object):

    id_function = id

    def __init__(self, id=None, ...):

然后,如果需要在id_function内部使用id,只需使用__init__

性质

我想说的是,像normalize这样的东西可能是属性。

代码语言:javascript
运行
复制
@property
def normalize(self):
    return [vote / self.total_votes() for vote in self.votes]

因此,您可以以instance.normalize的形式访问它,但这是一个次要的细节。

魔术方法

如果您实现了__eq__,请确保您也实现了__ne__。很简单

代码语言:javascript
运行
复制
def __ne__(self, other):
    return not self == other

白色空间

在文档字符串之后添加一个空行。只是个风格上的问题,而且很小。

命名

我不喜欢from_apiv2这个名字。对我来说不是很好的描述。相反,也许类似于from_strawpoll_id或者只是from_id

此外,这也是个人偏好,我喜欢使用表单Api而不是API,同时在名称中使用缩略词,特别是如果它们后面跟着另一个CamelCased单词。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/105602

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档