写给Python用户的Julia编程指南[updating]

本文的目的是,为拥有Python编程经验的用户提供一些指引,方便其快速上手Julia。

准备

下载和安装

官网提供了各个系统下的安装包,不过我建议新手先安装Julia PRO,这个有点类似Python下的Anaconda,目前版本是0.6.2,安装Julia的同时还会安装一些Julia下常见的包(省去了许多包管理麻烦,当然,也会带来一些新的小问题,这个碰到的时候再细说)。

由于我日常开发在Windows下,所以以下内容中操作系统相关的部分都以Windows为主(Linux和Mac下要容易很多,应该问题不大)。

安装后直接双击桌面的JuliaPro - Command Prompt 0.6.2.1快捷键即可打开Julia的REPL界面,就跟IPython有点像(后面会详细介绍其与IPython的联系和区别),建议将JuliaPRO安装路径下Julia-0.6.2中的bin目录添加到环境变量Path中,这样可以直接在git-bash或者cmd中直接运行julia(如下图)。

Julia_REPL

Julia_REPL

Windows下开发学习的一点个人建议

我个人对Windows的态度是不吹不黑,但老实说,Windows下开发的体验很差。强烈建议安装个Debian/Ubuntu的WSL,除了不能直接跟底层GPU打交道外,我目前的开发中没遇到什么大问题,日常一般是开了两个terminal,一个显示REPL,另一个在Debian中切到对应的目录下做文件读取和服务管理。

编辑器

一般Python的开发会选择PyCharm之类的IDE,不过Julia下还没有与PyCharm对应的IDE。安装JuliaPRO之后,桌面会有一个Juno for JuliaPro 0.6.2.1,其实就是一个Atom上套了个插件,如果你原来就用Atom的话,也许你会喜欢这个编辑环境。我个人在Windows下倾向使用VSCode+Julia插件,不过社区里也有人用Vim和Emacs,选择一个适合你的就好。

使用VSCode的Julia插件的时候,需要配置(File -> Preferences -> Settings)julia的路径,如"julia.executablePath": "D:\workspace\software\JuliaPro-0.6.2.1\Julia-0.6.2\bin\julia.exe"。

当然,你一定注意到了桌面还有一个Jupyter 0.6.2.1,其实就是Jupyter+IJulia的内核,如果你习惯Jupyter的话几乎可以做到无缝迁移。

REPL

在IPython中,最常用的一个功能是查看函数的帮助文档,通过在函数/方法名后面加一个?来实现,不过在Julia的REPL中,是先输入?再输入函数名:

使用?的例子

使用?的例子

另外,在IPython中,经常会执行一些类似ls,pwd等等系统命令,这类命令在Julia中是通过函数来实现的(如readdir(), pwd())。此外,原来%%的魔法函数也有对应的函数实现。不过如果是在REPL中,可以直接输入;进入shell模式,执行各种命令(自行对比iPython中加了!执行命令)。

包管理

新版的Pkg管理模块比以前好用了很多,功能上有点像内置了一个pipenv。打开Julia的REPL后,按]进入Pkg管理模块,通过generate Foo即可新建一个Project,然后add Bar可以添加依赖,更多操作可以查看Pkg的Doc。

语法细节

下面从一些日常的操作来介绍如何从Python迁移到Julia。

代码结构

在Python中,代码是以目录结构组织起来的,然后用__init__.py来区分包和普通的目录,Julia的代码以模块(Modules)组织起来的,一个典型的Julia文件有如下结构:

module MyModule
using Lib

using BigLib: thing1, thing2

import Base.show

importall OtherLib

export MyType, foo

struct MyType
    x
end

bar(x) = 2x
foo(a::MyType) = bar(a.x) + 1

show(io::IO, a::MyType) = print(io, "MyType $(a.x)")
end

首先,module定义了整个模块,其作用范围一直到最后一行end,module名称一般采用驼峰式,在module中也可以定义一个__init__函数,其功能有点类似Python下的__init__.py文件(不完全对应,__init__.py某种程度上提供了下面export的功能)。

using Lib有点类似Python中的from xxx import *,此刻你脑海中想到的第一个问题应该是命名冲突!对,我一开始也这么想的,后来习惯了发现,好像问题不大,因为Julia是带类型的,一定程度上通过重载缓解了这个问题。

using BigLib: thing1, thing2就是显式地导入,类似from xxx import foo, bar

个人感觉,Julia中import主要用于扩展某个函数的时候使用,这一点using做不到(using只用于提供查找变量的搜索空间)。importall就是import的扩展版新版本中importall已废弃。

export则是,将该module内部的某些变量暴露出去。在Python中可能是通过__foo等来实现的。

函数部分的区别和联系后面再详述。

有时候,我们想直接从某个文件中导入,需要用include("foo.jl")来实现。

有一个函数whos()可以用来查看当前的变量信息,有点像Python中的globals()

数据结构

index

在Julia中,按index访问访问元素的时候,是从1而不是0开始的。这大概是我自己从Python迁移到Julia最不习惯的一方面(一不小心就出错了)。访问最后一个元素要使用end关键字,有点像Python中的-1。另外,Julia中索引的时候,是左闭右闭[]的,不是Python中的左闭右开[)

string

  • 在Julia中,Char和String分别是用单引号'和双引号"初始化的,不能像Python中一样混用,三个双引号的用法是一致的。
  • 字符串的连接采用*符号,而不是+
  • 字符串中支持变量替换,这个在Python3.6中有支持。二者只是表示上有所差别,Julia中是"foo is ${1 + 1}",Python3.6中是f"foo is {1 + 1}"
  • 前面你可能注意到了,Python中可以在string前增加f""r""等实现某种特殊功能,在Julia中,可以自定义许多非标准的字符串解释器,这个很强大,谁用谁知道。
  • 读文件有一点稍稍不同,需要调用eachline函数,此外,Python中常常会用到的strip,Julia中对应的是chomp

list

Python中最常用的就是list了,Julia中并没有严格与之对应的数据结构,不过,就一般使用而言,可以把一维的Array{Any,1}拿来用。

  • push!对应appendappend!对应extend方法,这里提一下,Julia还是函数式的风格,而Python中平常使用还是面向对象的风格为主。此外还有prepend!等等对用的函数。
  • Python中的list comprehension 一样可以使用,[x * 2 for x in range(1,10) if x > 3]。我个人为了保持代码风格的一致性,习惯用mapfilter处理。
  • Python中list可以通过*来扩展,Julia中的Array并不能这样做(用repeat函数实现)。

Tuple

和Python中基本一致,未来也会目前支持类似Python3中的NamedTuple。

numpy

这里单独说明下,Julia可以看作是自带了numpy的Python,Numpy的许多操作都能在标准库中Array部分找到。

记录几个需要注意的点:

  • maxmaximum的区别
  • 初始化矩阵的时候,julia中用;来区分行
  • numpy中的数据类型一般通过dtype参数来指定(通过.astype做类型转换),而Julia中,则是采用带参数类型函数来初始化(如Array{T}(m,n)
  • Julia中,array的存储顺序是按照列存储的,因此对矩阵赋值的时候需要注意区别。A[[1,2], [2,3]] = [2, 3,4 ,5][[1,2], [2,3]] = [2 3;4 5]的结果是有区别的。

iterator

  • 在python中经常会用到yield来生成迭代器,Julia中似乎没有找到对应的,我看到过一个相关的讨论,一定程度上可以用Channel来实现。但仍然有些繁琐,希望以后能支持。
  • Python中有个itertools也经常用,这个在Julia中都是通过macro实现的,我个人感觉Lazy.jl基本够用,有些个性化需求的话,自己写一个也很方便。

struct

对比Python中的class

macro

这个先放这里,在Python中很少会接触,前期迁移到Julia中的时候,只需要掌握一些常用的宏就可以了。

Grammar

Julia中有些Python中没有的语法糖,个人感觉有些还算有意思,有些就比较累赘了。

  • do,这个没啥意思,就是把lambda搬了个位置
  • control flow上,因为没有了Python中的intent,需要用end来控制,虽然麻烦了点,但也可以理解。另外没有pass关键字。
  • let用于局部的变量重绑定,特定时候可以解决命名冲突问题,其实有很多方式绕开。有点怀念Clojure中的let

function

如果不考虑类型的话,Julia下的function和Python相比,主要的差别是:

  • end作为结尾,默认返回最后一个表达式,基本不需要return语句
  • positional,optional参数写法保持一致,keyword参数的写法用;做了分隔,不过一般都这么写: julia function f(x; y=0, kwargs...) ### end
  • 对于one-line definition,一般用f(x) = x的形式,如果一个表达式不够用,可以这样f(x) = (step1;step2;step3)
  • lambda函数的写法看起来更简洁点,x -> x^x

operator

  • .,向量化操作,起初觉得没啥意思,记得Clojure有个macro实现类似的语义,后来发现,这个点真心简洁
  • +=,注意Julia中默认是immutable的,所以类似A += 3的操作其实是做了个rebinding
  • ...,Python中会用***来对函数的参数解绑,在Julia是...
  • 注意区分=====,类比Python中的==is

type

这个应该是从Python转Julia最大的区别,没有捷径,好好阅读文档。这里我记下点自己觉得比较常用的几个知识点:

  • typemaxtypemin可以提供某些类型的取值范围(Int64,Float32等)

接下来?