首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >简单,pythonic IRC接口

简单,pythonic IRC接口
EN

Code Review用户
提问于 2014-11-14 15:33:57
回答 1查看 277关注 0票数 5

我看到的所有其他Python包对于简单的任务来说都过于复杂,而且它们没有抽象出协议的复杂性和异步性。我已经尝试了一个更好的接口,但我对IRC协议并不熟悉。它运行在Python2和3下,看上去怎么样?

代码语言:javascript
复制
"""
simple_irc: a simple, Pythonic IRC interface.

This module contains 2 classes: an IRC adapter, and a message wrapper.

Basic usage:
  >>> irc = simple_irc.IRC('mynick', '#python', 'irc.myserver.net')
  >>> for msg in irc: print(msg.sender + ': ' + msg)
  somebody: Hello, simple_irc!
  somebody_else: Oh hai there!
  >>> irc.write('Hi guys!')
"""

import socket, threading, time, sys
if sys.version_info.major < 3:
  import Queue
else: import queue as Queue

_wait = .01

class IRC(object):
  '''A simple IRC interface that can handle simultaneous reading and writing.
  Its interface is similar to those in the io module.'''

  def __init__(self, nick, channel, network, port=6667, future=False,
               mode=2, realname='Python simpleirc bot'):
    '''Initialize an IRC connection. By default, does not return until
    the connection is established.

      nick: the nickname to connect with
      channel: the name of the channel to connect to (e.g. "#python")
      network: the network to connect to (e.g. "irc.freenode.net")
      port: the port to connect on
      future: if True, will not connect until open() is called
      mode: bitmask of initial user mode. Only 'w' (2) and 'i' (4) are available.
      realname: real name field. Can be any string.
    '''

    self.nick = nick
    self.channel = channel
    self.network = network
    self.port = port
    self.mode = mode
    self.realname = realname

    self._readqueue = Queue.Queue()
    self._writequeue = Queue.Queue()
    self._closed = True

    if not future:
      self.open()


  def open(self):
    '''Open the connection. Does not return until the connection has been established.'''
    if not self._closed:
      raise IOError(self + " is already open")

    self._soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self._soc.connect((self.network, self.port))
    self._soc.send('NICK {}\r\n'.format(self.nick).encode('UTF-8'))
    self._soc.send('USER {} {} * :{}\r\n'.format(self.nick, self.mode, self.realname).encode('UTF-8'))
    self._soc.send('JOIN {}\r\n'.format(self.channel).encode('UTF-8'))

    while True:
      #Read through all of the opening garbage
      data = self._soc.recv(4096).decode('UTF-8')

      #Error messages don't follow the same format as others, 
      #so we parse the code out early
      code = data.split()[1]
      if code.isdigit() and 400<=int(code)<=499:  
        raise ValueError(data.split(':')[2])
      elif "End of /NAMES list." in data:   #This is the last message before it's all good for some reason
        break

    self._soc.settimeout(_wait)   #the threads use this for nonblocking io
    self._closed = False

    tread = threading.Thread(target=self._reader)
    tread.daemon = True
    tread.start()

    twrite = threading.Thread(target=self._writer)
    twrite.daemon = True
    twrite.start()


  def _reader(self):
    '''Message reader thread'''
    while not self._closed:
      try: data = self._soc.recv(4096).decode('UTF-8')
      except socket.timeout: continue

      if 'PING' in data:  #so we don't get booted
        self._soc.send ('PONG ' + data.split()[1] + '\r\n')
      else:
        self._readqueue.put(message(data))

  def _writer(self):
    '''Message writer thread'''
    while not self._closed:
      time.sleep(_wait)
      if not self._writequeue.empty():
        msg = self._writequeue.get()
        self._writequeue.task_done()
        self._soc.send("PRIVMSG {} :{}\r\n".format(self.channel, msg).encode('UTF-8'))


  def __next__(self):
    msg = self.read()
    if msg is None:
      raise StopIteration
    else:
      return msg

  #Python2 compatability
  next = __next__

  def __iter__(self):
    return iter(self.read, None)

  def read(self):
    '''Returns the oldest unread message, or None if there are no unread messages.'''
    if self._readqueue.empty():
      return None
    else:
      msg = self._readqueue.get()
      self._readqueue.task_done()
      return msg

  def readall(self, limit=None):
    '''Returns a list of at most <limit> unread messages.
    If limit is unspecified, returns all unread messages.'''
    i = 0
    l = []
    for m in self:  #self is an iterable!
      l.append(m)
      i += 1
      if limit is not None and i>=limit:
        break

    return l

  def write(self, msg):
    '''Write a single message to the connected channel'''
    self._writequeue.put(msg)

  def writeall(self, msgs):
    '''Write all messages in an iterable to the connected channel'''
    for m in msgs:
      self.write(m)


  def close(self):
    '''Close the IRC connection'''
    self._closed = True
    time.sleep(2*_wait)   #wait for threads to terminate
    self._soc.send("QUIT\r\n".encode('UTF-8'))
    self._soc.close()
    del self._writequeue, self._readqueue

  @property
  def closed(self):
    '''True if the IRC connection is closed'''
    return self._closed


  def __enter__(self):
    return self

  def __exit__(self, exception_type, exception_value, traceback):
    self.close()
    return True



class message(str):
  '''An IRC message.

  Properties:
  - sender: the sender
  - hostname: the hostname of the sender
  '''
  def __new__(cls, raw):
    self = str.__new__(cls, raw.split(':')[2].rstrip())
    raw = raw.split(':')
    self.sender = raw[1].split('!')[0]
    self.hostname = raw[1].split()[0].split('@')[1]
    return self
EN

回答 1

Code Review用户

发布于 2014-11-14 20:34:24

很难理解这段代码,因为它不遵循PEP8。最令人印象深刻的违法行为:

  • 缩进应为4个空格
  • 类名应该跟随CamelCase (message违反了这一点)
  • :做断线,例如在else: import queue as Queue
  • 在运算符周围放置空格,例如400 <= int(code) <= 499而不是400<=int(code)<=499
  • 在类的方法声明前面放置一个空行。
  • 在注释中在#comment之后添加一个空格

在同名的包中有一个pep8命令行实用程序。我建议安装它,并运行您的脚本,并纠正所有报告的违规行为。

其他一些小窍门:

  • 你经常做self._soc.send("........".encode('UTF-8'))。最好添加一个助手方法,这样您就可以执行self.send("........")了。
    • 我建议self._soc.recv调用也是如此,尽管目前您只做了两次。

  • 对于机器人来说,IRC不是一个很好的名字。它没有像类应该有的那样有一个明确的目的。它看起来将成为一个神的阶级,做任何事情。为什么它应该是可迭代的?
  • 避免在l方法中使用单字母变量名(如readall )。字母l可能是最糟糕的单字母变量之一,因为很容易将它误认为数字1,或大写字母I。在本例中,我建议使用items
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

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

复制
相关文章

相似问题

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