Comparing with Python


#1

Not trying to spin up any controversy… but do we have a list about things that are better in Julia than Python and vice versa as of today? To have a productive discussion, I would think such list should contain items that are fairly clear (eg speed) rather than those that are highly subjective (eg multiple dispatch vs object oriented).


#2

If you are limitting it to things that are as objective as speed,
and less objective then multiple dispatch vs OO.
Then I think you might have hit the empty set of things to talk about.

Since Multiple Dispatch vs OO is more objective than speed.
People constantly argue about benchmarks.
Is it fair to use @inbounds, is it fair to use multi-threading?
Should tail-recursion be not used.
(Code size benchmarks also have similar problems)

Where as Multiple Dispatch vs OO is pretty clear.
Multiple Dispatch is outright more powerful than single dispatch (which is common in OO).
Though the associated polymorphism related discussions (inheritance, composition, traits, etc) much more debatable than that.

When it comes to language features it is hard find any that are more objective,
than Julia has multiple dispatch, python has up-to single dispatch.

Julia has macros, python does not have macros, but are macros even good – debatable.

Like if you are just looking for benchmarks go find benchmarks?


#3

Don’t forget this handy section of the manual!

https://docs.julialang.org/en/stable/manual/noteworthy-differences/#Noteworthy-differences-from-Python-1


#4

What usually happens in this context is that people who care enough invest effort into making things fast, making these comparisons reflect the skill, dedication, and interest of the members of the respective communities, in addition to general the “speed” of the language (which is also not clearly defined).

In Julia is that this process is accessible to users of the language: after some initial investment, it is relatively easy to discover and fix speed issues. But this is a “soft” feature, harder to quantify.

Generally, I would stay away from “Julia vs X” comparisons, and just emphasize what Julia has to offer. Eg the advantages of parametric multimethods will be relatively obscure for those who have not run into the problems that they fix, but will be immediately apparent to those who have.


#5

In Python if you have an object x, dir(x) will return a list of all methods that apply to x. I’ve found no similar ability in Julia with generic functions.

-Tom


#6

Because in Julia it’s not a small or finite set.


#7

Also there’s methodswith:

julia> methodswith(Dict)
29-element Array{Method,1}:
 convert(::Type{Dict{K,V}}, d::Dict{K,V}) where {K, V} in Base at dict.jl:208                                           
 copy(d::Dict) in Base at dict.jl:134                                                                                   
 delete!(h::Dict, key) in Base at dict.jl:558                                                                           
 done(t::Dict, i) in Base at dict.jl:578                                                                                
 empty!(h::Dict{K,V}) where {K, V} in Base at dict.jl:304                                                               
 get(h::Dict{K,V}, key, default) where {K, V} in Base at dict.jl:478                                                    
 get(default::Union{Function, Type}, h::Dict{K,V}, key) where {K, V} in Base at dict.jl:483                             
 get!(h::Dict{K,V}, key0, default) where {K, V} in Base at dict.jl:434                                                  
 get!(default::Union{Function, Type}, h::Dict{K,V}, key::K) where {K, V} in Base at dict.jl:444                         
 get!(default::Union{Function, Type}, h::Dict{K,V}, key0) where {K, V} in Base at dict.jl:436                           
 getindex(h::Dict{K,V}, key) where {K, V} in Base at dict.jl:473                                                        
 getkey(h::Dict{K,V}, key, default) where {K, V} in Base at dict.jl:527                                                 
 haskey(h::Dict, key) in Base at dict.jl:505                                                                            
 isempty(t::Dict) in Base at dict.jl:581                                                                                
 launch(manager::Base.Distributed.SSHManager, params::Dict, launched::Array, launch_ntfy::Condition) in Base.Distributed at distributed\managers.jl:121
 launch(manager::Base.Distributed.LocalManager, params::Dict, launched::Array, c::Condition) in Base.Distributed at distributed\managers.jl:317
 length(t::Dict) in Base at dict.jl:582                                                                                 
 next(t::Dict{K,V}, i) where {K, V} in Base at dict.jl:579                                                              
 pop!(h::Dict, key) in Base at dict.jl:538                                                                              
 pop!(h::Dict, key, default) in Base at dict.jl:543                                                                     
 rand(r::AbstractRNG, t::Dict) in Base.Random at random.jl:374                                                          
 rand(t::Dict) in Base.Random at random.jl:381                                                                          
 serialize(s::AbstractSerializer, d::Dict) in Base.Serializer at serialize.jl:337                                       
 setindex!(h::Dict{K,V}, v0, key::K) where {K, V} in Base at dict.jl:420                                                
 setindex!(h::Dict{K,V}, v0, key0) where {K, V} in Base at dict.jl:412                                                  
 similar(d::Dict{K,V}) where {K, V} in Base at dict.jl:192                                                              
 similar(d::Dict, ::Type{Pair{K,V}}) where {K, V} in Base at dict.jl:193                                                
 sizehint!(d::Dict, newsz) in Base at dict.jl:274                                                                       
 start(t::Dict) in Base at dict.jl:574    

#8

I figured someone would point that out. But even just listing methods whose first argument is typeof(x) would be useful.


#9

Yeah, but to get the true answer you’d have to merge that with every method for abstract supertypes. Not hard, but the list will be huge and uninformative to say the least.


#10

Isn’t that just what methodswith(T, true) does? From the ?methodswith doc:

If optional showparents is true, also return arguments with a parent type of typ, excluding type Any.

Example:

julia> methodswith(Dict, true)
62-element Array{Method,1}:
 ==(l::Associative, r::Associative) in Base at associative.jl:338                                                                                      
 convert(::Type{Dict{K,V}}, d::Dict{K,V}) where {K, V} in Base at dict.jl:208                                                                          
 convert(::Type{Dict{K,V}}, d::Associative) where {K, V} in Base at dict.jl:197

...

I do agree that it can be very useful when exploring a new library in Python to create and object and then do obj.<tab> to get a sense of what I can do with that object. Of course, even in Python that list is still just a subset of what you can actually do, as it doesn’t include useful things like:

  • build-in functions like enumerate()
  • operators you might apply to an object
  • any generic functions that could work with that object
  • protocols like iteration, indexing, etc.

#11

Woah.


#12

Right??!?


#13

Fair enough. From talking to a number of people who want to migrate from a different language e.g. Matlab or SAS, the most common question is why one chooses Julia over Python given the vast Python libraries available. Then, the common response is that Julia solves the two language problem as it is fast. Then, the response is that does speed even matter for the use case. So the conversation pretty much circle arounds speed. Language features don’t play well in such discussions either as they’re so debatable as you mentioned above.

How about having a list that goes like this. Feel free to add or yay/nay anything below:

Python has advantages in these areas

  1. Better development tools e.g. debugger
  2. Large talent pool
  3. Natively supported by cloud vendors e.g. AWS Lambda
  4. Natively supported by apache spark

Julia has advantages in these areas

  1. Easy to make things fast without much effort
  2. Reduced operating cost due to more efficient use of hardware (related to #1)
  3. Ability to build domain specific languages using macros

#14

Ok I feel these things are still debatable but W/E

Julia advantages:

Nicer syntrax for matrix operations.

  • A*B vs A.dot(B)
  • A' vs A.T
  • A[:, end÷2] vs A[:, len(A)//2]
  • [A; B] vs np.stack([A, B], 2)
  • [A B] vs np.stack([A, B], 1)
  • [A B; C] vs np.block([A,B], [C])
  • foo.(A) vs np.vectorize(foo)(A)

Also the ability to not always broadcast every operation, so that one gets errors when one screws up.


#15

You can use them from Julia using PyCall :wink:


#16

While python has a no doubt hundreds of times as many academic users as Julia (maybe even thousands).
The academic users are like <0.0001% of all python users.
Where as they are >90% of all Julia users.

Which means the julia community (and ecosystem) is a really vibrant academic place,


#17

You can find a lot of discussion that is relevant in this thread: “Julia motivation: why weren’t Numpy, Scipy, Numba, good enough?”

Many points are brought up. For me, the focus on first class numerical programming support is among the most important. Numpy’s syntax is pretty awkward compared to Julia.

I have really come to love multiple dispatch, which relieves me of almost all the tedious input parsing that you encounter in Python (and even much more so in Matlab!) I would estimate that about 25% of all coding time I’ve spent in Matlab was on input parsing, or figuring out how to efficiently branch my code based on the type of a variable.

Lightweight or zero-cost abstractions are great! You can choose an appropriate level of abstraction without sacrificing speed, and get very elegant and readable code.

EDIT: Oh, and of the remaining 75% of programming time, I’d say most of it was spent figuring out insane code vectorization tricks, to help performance. This is pretty much the same in Numpy.


#18

I don’t have the numbers, but my understanding is that while many people use Python, Julia has a comparatively (in a relative sense) large number of active contributors to the language. To contribute to Julia, you just need to know Julia.


#19

IMO this is an extremely important point, both for the ecosystem and the management of expectations. Julia attracts people who need to implement something and want to do it in a generic and efficient manner. Those who are using another language just for an existing toolkit or library, mostly writing “scripts”, may benefit little from Julia at this point, especially if their key library is missing or WIP.


#20

Depends. If you want to contribute to e.g. code optimization some C, LLVM IR and internals and some background on femto-lisp would be needed.