【问题标题】:(Mis)understanding Generators(错误)理解生成器
【发布时间】:2017-09-28 11:18:00
【问题描述】:

我想从一个每隔一段时间刷新一次的列表中一次提取一个代理,我对此没有任何问题。

有些代理不好,我希望使用列表中的下一个。这就是我的生成器的用武之地,然而,虽然我可以通过第一次调用 .next() 来让生成器滚动,但第二次调用它时,我会得到相同的值!

显然,在理解生成器的工作原理方面,我一定遗漏了一个关键部分。

我的生成器代码在 ProxyHandler 类中:

class ProxyHandler:

    def __init__(self):
        self.proxies = list()
        self.current = dict()

    def get_proxies(self):
        """ Retrieves proxies """

    def __len__(self):
        return len(self.proxies)

    def yield_proxy(self):
        if not self.proxies:
            print 'Created new proxy list'
            self.get_proxies()  # This populates self.proxies which is a list of tuples where the 0th element is the host and the 1st element is the port
        for p in self.proxies:
            try:
                proxy = {'http': 'http://%s:%s' % (p[0], p[1])}  # Formatted to python's request lib proxy format
                self.current = proxy
                yield proxy
            except StopIteration:
                print 'Reached end of proxy list'
                self.current = {}
                self.get_proxies()
                yield self.yield_proxy()

及用法:

def get_response(self, url):
    proxy = self.proxy_handler.current
    if proxy == {}:
        proxy = self.proxy_handler.yield_proxy().next()
    print 'Current proxy -', proxy
    response = url_request(url, proxy=proxy)  # url_request() is basically a modified version of python's requests
    print response
    if response: # url_request() returns true if status code == 200
        return response, proxy
    gen = self.proxy_handler.yield_proxy()
    gen.next()
    return self.get_ebay_response(url)

【问题讨论】:

    标签: python python-2.7 recursion python-requests generator


    【解决方案1】:

    您每次都在重新创建生成器:

    gen = self.proxy_handler.yield_proxy()
    gen.next()
    

    一个新的生成器从头开始;单独的生成器不共享状态。将生成器存储在某处,然后重用该对象以获取新值。

    您或许可以将该生成器对象作为属性存储在self

    proxy_generator = None
    
    def get_response(self, url):
        if not self.proxy:
            if self.proxy_generator is None
                self.proxy_generator = self.proxy_handler.yield_proxy()
            self.proxy = next(self.proxy_generator)
        proxy = self.proxy
    

    我使用next() function 使您的代码与 Python 3 前向兼容(您迟早要切换到 Python 2,Python 2 现在是一种旧语言)。

    接下来,您的生成器会尝试捕获永远不会抛出的异常:

    for p in self.proxies:
        try:
            proxy = {'http': 'http://%s:%s' % (p[0], p[1])}  # Formatted to python's request lib proxy format
            self.current = proxy
            yield proxy
        except StopIteration:
            print 'Reached end of proxy list'
            self.current = {}
            self.get_proxies()
            yield self.yield_proxy()
    

    在您的try 中没有生成器被访问;您将这项工作交给了self.proxies 上的for 循环,而for 已经 知道如何处理迭代器(它将捕获StopIterator 以结束循环)。而self.proxies 只是一个列表。

    如果您想在您的代理上进行循环循环,请在无休止的 while True 循环中执行此操作:

    while True:
        for p in self.proxies:
            proxy = {'http': 'http://%s:%s' % (p[0], p[1])}  # Formatted to python's request lib proxy format
            self.current = proxy
            yield proxy
    
        print 'Reached end of proxy list'
        self.current = {}
        self.get_proxies()
    

    我不确定你为什么认为你需要在那里清除 self.current 并重新获取代理。生成器上的元组从未更改过,为什么要重新获取?即使您确实从顶部再次启动循环,您当前的代理仍然有效。我会删除最后三行。

    您可以进一步简化代码。生成器没有长度,因此不需要__len__ 方法。该方法充其量会产生错误信息;在您开始迭代之前,您的 self.proxies 属性为空,因此您的对象的长度为 0。完全放弃该方法。

    接下来,您可以为您的对象提供一个__iter__ 方法来生成生成器:

    class ProxyHandler:
        def __init__(self):
            self.proxies = []
            self.current = {}
    
        def get_proxies(self):
            """ Retrieves proxies """
    
        def __iter__(self):
            if not self.proxies:
                print 'Created new proxy list'
                self.get_proxies()
            while True:
                for p in self.proxies:
                    proxy = {'http': 'http://%s:%s' % (p[0], p[1])} 
                    self.current = proxy
                    yield proxy
    

    这使得整个 ProxyHandler 实例成为可迭代的,只需使用 iter(self.proxy_handler) 而不是 self.proxy_handler.yield_proxy() 即可让生成器生成所有这些值。

    最后但并非最不重要的一点是,您可以对整个事物使用生成器表达式,并与 itertools.cycle() 一起使迭代器无穷无尽。但是,您必须删除 current 属性,但这并不重要,因为当您的生成器刚刚生成当前对象时,您实际上并不需要该属性无论如何

    from itertools import cycle
    
    class ProxyHandler:
        def __init__(self):
            self.proxies = []
    
        def get_proxies(self):
            """ Retrieves proxies """
    
        def __iter__(self):
            if not self.proxies:
                print 'Created new proxy list'
                self.get_proxies()
            return cycle({'http': 'http://%s:%s' % (p[0], p[1])} for p in self.proxies)
    

    生成器表达式产生相同类型的对象。

    这一切仍然需要iter(self.proxy_generator);您可以通过让__iter__ 返回self 并添加next() 方法来创建实例iterator(而不是可迭代);在第一次调用时将上面的生成器表达式移动到一个属性,然后将 next() 调用传递给该属性以生成值:

    class ProxyHandler:
        def __init__(self):
            self.proxies = []
            self._gen = None
    
        def get_proxies(self):
            """ Retrieves proxies """
    
        def __iter__(self):
            return self
    
        def next(self):
            if not self._gen:
                self.get_proxies()
                self._gen = cycle({'http': 'http://%s:%s' % (p[0], p[1])} for p in self.proxies)
            return next(self._gen)
    
        __next__ = next  # Python 3 compatibility
    

    现在你可以每次都使用`next(self.proxy_handler)了:

    def get_response(self, url):
        if not self.proxy:
            self.proxy = next(self.proxy_handler)
        proxy = self.proxy
    

    【讨论】:

    • 那么,为了解决这个问题,我需要在我的方法之外创建一个生成器并在需要时调用它?
    • @galalmighty:完全正确。生成器是保持状态的单个对象;不要在每次需要值时都替换它,而是保留它。
    • 有见地、详细且非常清晰。谢谢!在大约 10 分钟内学到了很多东西。
    • @galalmighty Step 调试一个产生多个值的简单函数,你会看到。
    • @galalmighty 在实践中,生成器通常像其他可迭代对象一样使用:gen = some_generator(); for x in gen: do_something(x)。这就是为什么您希望创建一次生成器然后多次访问它的原因。在您的情况下,您可以将self.proxy_gen = self.yield_proxy() 添加到您的__init__。然后您可以将self.proxy_handler.yield_proxy().next() 替换为self.proxy_handler.proxy_gen.next(),这将是一个无限生成器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    • 2016-01-21
    • 2012-01-15
    • 1970-01-01
    • 2020-10-10
    相关资源
    最近更新 更多