先说两句废话
作为一个算法工程师,一般都要同时兼顾着模型预研和工程部署实现的工作。
如果说现在模型预研用得最多的是python(简单,易上手,除了慢和写得好需要极强的内容外,几乎没有别的缺点)这个目前应该是没有争议的,但是在工程部署上就不大统一了,有的公司用java,有的用c++,还有的用JavaScript(没想到吧,毕竟js是可以贯通前端+后端的神器,还可以直接用TensorFlow),当然,他们几个之间的好处坏处我这里也懒得或者说不想叙述了,只能说,本渣更注重后台实现,所以工程上几乎都是java。
重点是,如果真要做一个工程师,日常应该是至少要熟练运用2门程序语言以上的(本渣就是2门)。但是混着用两门语言,而且几乎使用频率一样有一个非常尴尬的特点——非常容易混淆,例如我写着java可能会突然来了一句for i in range(n_len),写着python可能突然来一句if(true){};
当然,这点其实这是一点小插曲,毕竟idea或者pycharm一报错,瞬间可以反应过来。
重点是,不同语言之间的一些重点核心的概念,也容易混,例如迭代、例如正则、例如多线程。所以久着久着都得无聊翻书看看,后面翻书多了,一发狠,还是将比较重要的那几个的概念在java和python区分开,毕竟点击自己的blog比翻书还是轻松多了。
这次先讲python和java的迭代概念上的区别和联系。
什么是迭代?
⽤简单的话讲,它就是从某个地⽅(⽐如⼀个列表)取出⼀个元素的过程。当我们使⽤⼀个循环来遍历某个东西时,这就叫⼀个迭代[1]。
更简单的话,只用能用for语句(java和python都有)进行循环的,就是迭代。
这就是java和python关于迭代的所有共同之处
除此之外,java和python关于迭代的概念还是相差有点大的,需要重点掌握下面几个关键词:
java:Iterable,Iterator
python:Iterable, Iterator, Generator
先谈在迭代上更容易实现的java
Java的迭代
java的迭代有且只跟两个接口有关,Iterable,Iterator。而且这两个接口是强关联在一起,缺一不可。
Iterable: 作用是选择Iterator,可用if-else条件判断的方法选择不同的iterator
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
}
Iterator:具体的迭代循环方式
public interface Iterator<E> {
/**
* Returns {@code true} if the iteration has more elements.
* (In other words, returns {@code true} if {@link #next} would
* return an element rather than throwing an exception.)
*
* @return {@code true} if the iteration has more elements
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws NoSuchElementException if the iteration has no more elements
*/
E next();
/**
* Removes from the underlying collection the last element returned
* by this iterator (optional operation). This method can be called
* only once per call to {@link #next}. The behavior of an iterator
* is unspecified if the underlying collection is modified while the
* iteration is in progress in any way other than by calling this
* method.
*
* @implSpec
* The default implementation throws an instance of
* {@link UnsupportedOperationException} and performs no other action.
*
* @throws UnsupportedOperationException if the {@code remove}
* operation is not supported by this iterator
*
* @throws IllegalStateException if the {@code next} method has not
* yet been called, or the {@code remove} method has already
* been called after the last call to the {@code next}
* method
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
在java中,要实现迭代,必须实现Iterable接口——该接口必须实现的iterator方法就是要返回一个满足Iterator接口的对象!
而Iterator接口也必须依附在Iterable上才能正常的工作,不然它就是个普通的接口。
以算法第四版关于背包的实现为例:
public class Bag<Item> implements Iterable<Item>{
private Node first;//栈顶
private int N;
private class Node{
Item item;
Node next;
}
public int size(){
return N;
}
public boolean isEmpty(){
return first==null;
}
public void add(Item item){
Node oldFirst = first;
first = new Node();
first.item= item;
first.next = oldFirst;
N++;
}
public Item pop(){
N--;
Item item = first.item;
first=first.next;
return item;
}
public Iterator<Item> iterator(){
return new ListIterator();
}
private class ListIterator implements Iterator<Item>{
private Node current = first;
@Override
public boolean hasNext() {
return current==null;
}
public Item next(){
Item item = current.item;
current = current.next;
return item;
}
public void remove(){}
}
}
当然,为什么要把Iterable和Iterator进行分开呢,因为在例子中iterator方法其实是可以根据其他的判断条件进行不同迭代器的选择,根据条件的不同,我可以像背包那样无序输出,也可以根据栈那样进行先进后出,或者队列那样先进先出。
Python的迭代
python的迭代其实比java的复杂一丢丢。
虽然python中也有Iterable和Iterator的关联,但是!它们只是有可能关联,不是java的那种强关联。如果说python中迭代概念存在强关联的,那是Generator一定是一个Iterator!
先说各自的概念:
可迭代对象(Iterable):⼀个可迭代对象是Python中任意的对象,只要它定义了可以返回⼀个迭代器的__iter__ ⽅法,或者定义了可以⽀持下标索引的__getitem__⽅法,可以用for进行遍历。
迭代器(Iterator):⼀个迭代器是任意⼀个对象,只要它定义了⼀个__next__⽅法。迭代器本身表示一个惰性计算的序列:数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据。
生成器(Genertor):自定义最常用的迭代器。可以用更加优雅的方式实现迭代器的效果——不用专门写__next__方法:1. 使用yield关键字。2.要把一个列表生成式的[]改成()
为什么说Iterable和Iterator不是强关联?——因为我们可以用__getitem__方法进行遍历,最常见的在pytorch的自定义dataset的时候就是这样。
用一个更简单的例子来验证:
class Library(object):
def __init__(self):
self.books = [1, 2, 3]
self.index = -1
def __getitem__(self, i):
return str(self.books[i])+"test"
l = Library()
print(l[1])
for i in l:
print(i)
而实际在进行模型训练时候,生成器是非常常用的!(准确说yield关键字)。因为生成器是迭代器的一种,不需要我们把数据加入到内存之中,例如gensim训练word2vec的数据集的过程中:
class MySentences(object):
def __init__(self, dirname):
self.dirname = dirname
def __iter__(self):
for fname in os.listdir(self.dirname):
for line in open(os.path.join(self.dirname, fname)):
yield line.split()
sentences = MySentences('/some/directory') # a memory-friendly iterator
model = gensim.models.Word2Vec(sentences)
这里用__iter__方法实现了一个生成器(迭代器),以实现了整个读取数据的循环——用yield方法避免了像java那样要使用另一个类,并且实现__next__方法,其实到目前为止,本渣几乎都是用生成器来替代迭代器(其实主要是我写得代码还不够多)。
参考链接:
[1] https://www.liaoxuefeng.com/wiki/1016959663602400/1017316949097888