2010-08-01 241 views
4

我是一名探索外部世界的PHP开发人员。我决定开始学习Python。学习Python;我怎样才能使这个更Pythonic?

以下脚本是我第一次尝试将PHP脚本移植到Python。它的工作是从Redis商店获取推文。推文来自Twitter的Streaming API并以JSON对象的形式存储。然后提取所需信息并将其转储到CSV文件中,然后使用托管在不同服务器上的LOAD DATA LOCAL INFILE导入到MySQL中。

所以,问题是:现在我有我的第一个Python脚本运行,我怎么能使它更Pythonic?有什么建议,你们有吗?让它变得更好?我应该知道的技巧?建设性的批评?

更新:已经采取了大家的建议迄今,这里是更新版本:
UPDATE2:通过pylint的冉代码。现在得分9.89/10。还有其他建议吗?

# -*- coding: utf-8 -*- 
"""Redis IO Loop for Tweelay Bot""" 
from __future__ import with_statement 

import simplejson 
import re 
import datetime 
import time 
import csv 
import hashlib 

# Bot Modules 
import tweelay.red as red 
import tweelay.upload as upload 
import tweelay.openanything as openanything 

__version__ = "4" 

def process_tweets(): 
    """Processes 0-20 tweets from Redis store""" 
    data = [] 
    last_id = 0 
    for i in range(20): 
    last = red.pop_tweet() 
    if not last: 
     break 

    t = TweetHandler(last) 
    t.cleanup() 
    t.extract() 

    if t.get_tweet_id() == last_id: 
     break 

    tweet = t.proc() 
    if tweet: 
     data = data + [tweet] 
     last_id = t.get_tweet_id() 

    time.sleep(0.01) 

    if not data: 
    return False 

    ch = CSVHandler(data) 
    ch.pack_csv() 
    ch.uploadr() 

    source = "http://bot.tweelay.net/tweets.php" 
    openanything.openAnything(
    source, 
    etag=None, 
    lastmodified=None, 
    agent="Tweelay/%s (Redis)" % __version__ 
    ) 

class TweetHandler: 
    """Cleans, Builds and returns needed data from Tweet""" 
    def __init__(self, json): 
    self.json = json 
    self.tweet = None 
    self.tweet_id = 0 
    self.j = None 

    def cleanup(self): 
    """Takes JSON encoded tweet and cleans it up for processing""" 
    self.tweet = unicode(self.json, "utf-8") 
    self.tweet = re.sub('^s:[0-9]+:["]+', '', self.tweet) 
    self.tweet = re.sub('\n["]+;$', '', self.tweet) 

    def extract(self): 
    """Takes cleaned up JSON encoded tweet and extracts the datas we need""" 
    self.j = simplejson.loads(self.tweet) 

    def proc(self): 
    """Builds the datas from the JSON object""" 
    try: 
     return self.build() 
    except KeyError: 
     if 'delete' in self.j: 
     return None 
     else: 
     print ";".join(["%s=%s" % (k, v) for k, v in self.j.items()]) 
     return None 

    def build(self): 
    """Builds tuple from JSON tweet""" 
    return (
    self.j['user']['id'], 
    self.j['user']['screen_name'].encode('utf-8'), 
    self.j['text'].encode('utf-8'), 
    self.j['id'], 
    self.j['in_reply_to_status_id'], 
    self.j['in_reply_to_user_id'], 
    self.j['created_at'], 
    __version__) 

    def get_tweet_id(self): 
    """Return Tweet ID""" 
    if 'id' in self.j: 
     return self.j['id'] 

    if 'delete' in self.j: 
     return self.j['delete']['status']['id'] 


class CSVHandler: 
    """Takes list of tweets and saves them to a CSV 
    file to be inserted into MySQL data store""" 
    def __init__(self, data): 
    self.data = data 
    self.file_name = self.gen_file_name() 

    def gen_file_name(self): 
    """Generate unique file name""" 
    now = datetime.datetime.now() 

    hashr = hashlib.sha1() 
    hashr.update(str(now)) 
    hashr.update(str(len(self.data))) 

    hash_str = hashr.hexdigest() 
    return hash_str+'.csv' 

    def pack_csv(self): 
    """Save tweet data to CSV file""" 
    with open('tmp/'+self.file_name, mode='ab') as ofile: 
     writer = csv.writer(
     ofile, delimiter=',', 
     quotechar='"', 
     quoting=csv.QUOTE_MINIMAL) 
     writer.writerows(self.data) 

    def uploadr(self): 
    """Upload file to remote host""" 
    url = "http://example.com/up.php?filename="+self.file_name 
    uploadr = upload.upload_file(url, 'tmp/'+self.file_name) 
    if uploadr[0] == 200: 
     print "Upload: 200 - ("+str(len(self.data))+")", self.file_name 
     print "-------" 
     #os.remove('tmp/'+self.file_name) 
    else: 
     print "Upload Error:", uploadr[0] 

if __name__ == "__main__": 
    while True: 
    process_tweets() 
    time.sleep(1) 
+3

请参阅http://www.python.org/dev/peps/pep-0008/ – 2010-08-01 22:50:07

+1

函数和文档字符串是你的朋友。据我所知,这个脚本得到一个推文,不做任何事,然后等一会儿,冲洗,重复。 – msw 2010-08-02 00:07:29

+1

+1“我决定开始学习Python。” – Seth 2010-08-02 00:09:33

回答

19

相反的:

i=0 
    end=20 
    last_id=0 
    data=[] 
    while(i<=end): 
    i = i + 1 
    ... 

代码:

last_id=0 
    data=[] 
    for i in xrange(1, 22): 
    ... 

相同的语义,更紧凑,更符合Python。

而不是

if not last or last == None: 

做到这

if not last: 

因为None是假十岁上下无论如何(所以not lastTruelastNone). In general, when you want to check if something is, code是无, not == None`。

if(j['id'] <> last_id): 

失去了多余的括号和过时的<>运营商和代码,而不是

if j['id'] != last_id: 

,并删除其他if声明冗余括号。

相反的:

if len(data) == 0: 

代码:

if not data: 

因为任何空容器是假十岁上下。

hash_str = str(hash.hexdigest()) 

代码代替

hash_str = hash.hexdigest() 

由于该方法已经返回一个字符串,使得str呼叫冗余。

相反的:

for item in data: 
    writer.writerow(item) 

使用

writer.writerows(data) 

这确实代表你的循环。

而不是

ofile = open('tmp/'+file_name, mode='ab') 
    ... 
    ofile.close()  

使用(在Python 2.6或更高,或2.5与

from __future__ import with_statement 

启动模块到with声明功能 “从今后进口”):

with open('tmp/'+file_name, mode='ab') as ofile: 
    ... 

它保证为您做出关闭(包括在发生异常可能会被提出)。

而不是

print "Upload Error: "+uploadr[0] 

使用

print "Upload Error:", uploadr[0] 

,类似的还有其他print语句 - 逗号插入一个空间给你。

我确定还有更多这样的小东西,但是这些只是一些“跳到眼睛”,因为我正在扫描您的代码。

+0

真棒,谢谢:) – Jayrox 2010-08-02 03:14:42

1
  1. 我见过的Python每个方法变量名是在没有下划线小写。 (我不认为这是一个要求,可能不是标准做法。)
  2. 你应该把逻辑分解成多个单一用途的方法。
  3. 再向前2步,创建一些类将相关方法封装在一起。
+10

什么?下划线一直在Python中使用。 PEP 8甚至明确地说“函数名称应该是小写的,为了提高可读性,必须使用由下划线分隔的单词。”哎呀,就在我头顶,因为昨晚我使用它看看线程模块:http://docs.python.org/library/threading.html – 2010-08-01 22:54:44

+0

我已经更新了代码,以包括您关于打破将代码放入方法和类中。因为我发现它们更易于阅读,所以我留下了下划线。 – Jayrox 2010-08-02 15:37:04

+0

@Nicholas Doh!你是对的。我在想变量名称。我自己还在学习Python。对不起。 – 2010-08-02 15:51:08

6

pythonic python不使用整数流量控制非常多。这个成语几乎总是for item in container:。另外,我会使用一个类来保存一个'用户对象'。它比简单的容器类型更容易使用,比如列表和字典(并且将你的代码编排成更加面向对象的风格)。你可以手动编译reg-exes以获得更多的性能。

class MyTweet(object): 
    def __init__(self, data): 
    # ...process json here 
    # ... 
    self.user = user 

for data in getTweets(): 
    tweet = MyTweet(data) 
+0

就整数流控制达成一致,即使您认为需要整数控制(将索引编入两个列表),最好将它们映射到一起或将它们压缩在一起,然后迭代。这会在某处产生一个更有意义的错误,并使您的代码更具可读性。 – marr75 2010-08-01 23:35:25

2
# Bot Modules 
import red #Simple Redis API functions 
import upload #pycurl script to upload to remote server 

如果您的应用程序将被使用和维护,它能够更好地收拾包中的所有这些模块。

+0

你是什么意思?你的意思是把他们的功能放在同一个文件中?作为代码的其余部分? – Jayrox 2010-08-02 03:20:34

+0

不,我的意思是把它们放在自己的命名空间中,比如'myapp.red','myapp.upload'等。 基本上你只需要创建目录'myapp',把'__init __。py'放在那里(它可以是空),并将文件移动到那里。然后,可以将其作为'import myapp.red'导入,或者以更常见的形式从'myapp.red import Class1,Class2,Class3,func1'中导入。而且,你也可以在以后添加setup.py,并安装包。请参阅http://docs.python.org/distutils/examples.html#pure-python-distribution-by-package – 2010-08-02 05:06:52

+0

有趣的是,如果我目前没有计划分发代码,该怎么办?我还应该遵循这种模式吗? – Jayrox 2010-08-02 05:52:12

2

代替....

i=0 
    end=20 
    last_id=0 
    data=[] 
    while(i<=end): 
    i = i + 1 

可以使用...

for i in range(20): 

但总的来说,这不是很清楚这20来自哪里?魔法 #?

+0

20只是我为控制限制器选择的一个数字。真的不多。 – Jayrox 2010-08-02 03:19:20

2

如果你有一个方法不适合在视图窗格中,你真的想缩短它。说15行左右。我看到至少有三种方法:print_tweet,save_csv和upload_data。要确切地说明他们应该命名的内容有点困难,但似乎有三个不同的代码段,您应该尝试突破。

+0

是的,我正在阅读PEP8中的内容,但我不确定要如何打破这些内容。 – Jayrox 2010-08-02 03:22:21

+0

@jayrox:然后再读一遍paulrubel写的,他在这里给了你功能分解。 – msw 2010-08-02 06:35:06

+0

@mswn,抱歉不清楚。我在谈论每行80个字符的分解部分。 – Jayrox 2010-08-02 11:52:30

2

通过pylint运行您的代码。

+0

太棒了!谢谢,我没有意识到pylint :) – Jayrox 2010-08-03 01:26:10