2010-08-01 2 views
4

Я разработчик PHP, изучающий внешний мир. Я решил начать изучать Python.Обучение Python; Как я могу сделать это более Pythonic?

Этот сценарий является первой попыткой переноса PHP-скрипта на Python. Его задача - взять твиты из магазина Redis. Твиты поступают из Twitter Streaming API и хранятся как объекты JSON. Затем необходимая информация извлекается и сбрасывается в CSV-файл, который должен быть импортирован в MySQL, используя LOAD DATA LOCAL INFILE, размещенный на другом сервере.

Итак, вопрос в том, что теперь у меня есть мой первый скрипт на 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/ –

+1

Строки и документация - ваши друзья. Насколько я могу судить, этот скрипт получает чириканье, ничего не делает с ним, а потом немного ждет, промойте, повторите. – msw

+1

+1 для «Я решил начать изучать Python». – Seth

ответ

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): 
    ... 

же семантику, более компактный и Pythonic.

Вместо

if not last or last == None: 

сделать только

if not last: 

так None ложно-иш в любом случае (так not last является True когда last не None). In general, when you want to check if something is Ни , code не None , 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

awesome, thanks :) – Jayrox

1
  1. Каждый метод имя переменной я когда-либо видел в Python был в нижнем регистре, без подчеркивания. (Я не думаю, что это требование и не может быть стандартной практикой.)
  2. Вы должны действительно разбить логику на несколько одноцелевых методов.
  3. Сделайте еще 2 шаг и создайте несколько классов для инкапсуляции связанных методов вместе.
+10

Что? Подчеркивания используются все время в Python. PEP 8 даже явно говорит: «Названия функций должны быть строчными, со словами, выделенными символами подчеркивания, для повышения удобочитаемости». Черт возьми, совсем рядом с моей головой, так как я использовал его вчера вечером, смотрю на модуль потоковой передачи: http://docs.python.org/library/threading.html –

+0

Я обновил код, чтобы включить ваши предложения о разрыве вниз код в методы и классы. Я оставил там подчеркивания, поскольку мне легче их читать. – Jayrox

+0

@ Николас Дох! Ты прав. Я думал о переменных именах. Я все еще изучаю Python. Извините за goof. –

6

Python python не использует управление целым потоком. Идиома почти всегда for item in container:. Кроме того, я бы использовал класс для хранения «Пользовательского объекта». Это будет намного проще в использовании, чем простые типы контейнеров, которые нравятся спискам и словарям (и упорядочивайте свой код в более стиле OO.) Вы можете скомпилировать рег-exes перед рукой для получения большей производительности.

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

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

Согласовано на управление целым потоком, и даже если вы считаете, что вам нужен целочисленный контроль (индексирование на два списка), лучше сопоставить или закрепить их вместе перед повторением , Это будет приводить к ошибкам где-то, что имеет больше смысла и делает ваш код более удобочитаемым. – marr75

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

Если ваше приложение будет использоваться и поддерживаться, лучше, чтобы упаковать все эти модули в пакете.

+0

Что значит? вы имеете в виду, что их функции включены в один файл? как остальная часть кода? – Jayrox

+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 –

+0

Интересно, что, если я в настоящее время не планирую распространять код? Должен ли я продолжать следовать этой схеме? – Jayrox

2

Вместо ....

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

вы можете использовать ...

for i in range(20): 

но в целом, это не очень ясно, где это 20 происходит? магия #?

+0

20 всего лишь номер, который я выбрал для ограничителя. не намного больше. – Jayrox

2

Если у вас есть метод, который не подходит для панели просмотра, вы действительно хотите его укоротить. Скажите 15 строк или около того. Я вижу, что выглядит как минимум 3 метода: print_tweet, save_csv и upload_data. Трудно точно сказать, на что они должны быть названы, но, похоже, есть три отдельных раздела кода, которые вы должны попытаться вырваться.

+0

Да, я читал это в PEP8, но я не уверен, как еще их сломать. – Jayrox

+0

@jayrox: затем прочитайте снова, что написал paulrubel, он дал вам функциональный пробой прямо здесь. – msw

+0

@ mswn, извините, не было ясно. я говорил о 80 символах в строке, чтобы сломать его. – Jayrox

2

Запустите свой код через pylint.

+0

Удивительный! Спасибо, я не знал о pylint :) – Jayrox

Смежные вопросы