作为一个在构建软件方面有一些新手经验的Python初学者,我决定为Strawpoll编写一个API包装器将是学习语言和设计模式的一个很好的练习。
我的目的是完成整个包装器的编写,但我觉得有些地方不对劲,但我无法确定为什么我编写的代码没有我所见过的其他Python库那么有条理。
我认为我已经做了一个好的工作,分离和组织模块,但希望有一些评论,如何可以做得更好/以一种更仿生的方式。
如果你对我的决策/思考过程有任何疑问,请告诉我,我会尽力回答这些问题的。
下面是我绝对希望得到一些帮助的模块:
基类:
"""
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读取器类:
"""
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'))
如果我所做的不够好的话,请告诉我你认为组织这两者的更好的方法。
发布于 2015-09-26 00:27:51
我想我的第一个问题是:
""
TODO: Document this class
"""
天使会在你写代码的时候哭泣,而不是先记录下来。一般来说,在写之前,你应该知道它应该做什么。实现细节是不相关的,无论如何也不应该放在文档中(它们属于注释)。
撇开这一点不说,我想我看不出基类的意义。除非您希望实现其他StrawpollAPI,否则应该将其子类化。
__init__
另外,我不喜欢你写__init__
的方式。你为什么要这么做?
for key in data.keys():
try:
setattr(self, key, data[key])
except AttributeError:
continue
您不应该忽略这样的错误--至少记录错误(就像您在子类中评论的那样)。除此之外,在我看来,API中应该有有限数量的已知键--在构造函数中接受它们作为带有合理默认值的关键字参数。毕竟,引用Python的禅宗,
外显好于内隐。
我想说的是
def __init__(self, id_=None, title=None, options=None ....):
self.id = id_
...
它要干净得多,并且不需要在如何称呼它上有很大的不同:
cls(**api_response)
而不是
cls(data=api_response)
如果它有奇怪的、无效的数据,那么你会大声地得到一个错误。如果您仍然想要处理它,那么请使用
def __init__(self, id_=None, ..., **kwargs):
然后适当地处理额外的关键字参数。
一个可能的问题是名称id_
--不幸的是,id
是Python中的一个内置函数,使用它作为参数将在本地覆盖它。同样不幸的是,API很可能不会有id_
的名称。您可以始终从id
模块访问__builtins__
,或者设置一个类成员,如
class StrawpollAPIBase(object):
id_function = id
def __init__(self, id=None, ...):
然后,如果需要在id_function
内部使用id
,只需使用__init__
。
我想说的是,像normalize
这样的东西可能是属性。
@property
def normalize(self):
return [vote / self.total_votes() for vote in self.votes]
因此,您可以以instance.normalize
的形式访问它,但这是一个次要的细节。
如果您实现了__eq__
,请确保您也实现了__ne__
。很简单
def __ne__(self, other):
return not self == other
在文档字符串之后添加一个空行。只是个风格上的问题,而且很小。
我不喜欢from_apiv2
这个名字。对我来说不是很好的描述。相反,也许类似于from_strawpoll_id
或者只是from_id
。
此外,这也是个人偏好,我喜欢使用表单Api
而不是API
,同时在名称中使用缩略词,特别是如果它们后面跟着另一个CamelCased单词。
https://codereview.stackexchange.com/questions/105602
复制相似问题