やるきなし

2021/12/04 12:53 / Ruby and Python

Assuming python3.

Multi-line comments

Ruby

=begin
Comment.
Comment.
Comment.
=end

Python

'''
Comment.
Comment.
Comment.
'''

If statements with regular expression

Ruby

i = 'hoge'
if i =~ /^h/
    print "#{i}\n"
end

Python

import re
i = 'hoge'
if re.search(r'^h', i):
    print(i)

Python (search() vs. match())

>>> import re
>>> i = 'hoge'
>>> re.match(r'h', i)
<_sre.SRE_Match object; span=(0, 1), match='h'>
>>> re.search(r'h', i)
<_sre.SRE_Match object; span=(0, 1), match='h'>
>>> re.match(r'g', i)
>>> re.search(r'g', i)
<_sre.SRE_Match object; span=(2, 3), match='g'>

If statements with regular expression 2

Ruby

i = 'hoge 100'
if i =~ /\s+(\d+)/
  print "#{$1}\n"
end
i = 'hoge 100'
if i =~ /\s+(\d+)/
  print "#{Regexp.last_match[1]}\n"
end
i = 'hoge 100'
print "#{Regexp.last_match[1]}\n" if i =~ /\s+(\d+)/

Python

import re
i = 'hoge 100'
r = re.search(r'\s+(\d+)', i)
if r:
    print(r.groups()[0])

Note that python’s assignment is not expression but statement. So the following causes SyntaxError.

if r = re.search(r'\s+(\d+)', i):
    print(r.groups()[0])

From Python 3.8, we can use Assignment Expression.

if r := re.search(r'\s+(\d+)', i):
    print(r.groups()[0])

In Python, there are two types of assignment as follows.

Matched object and string-to-integer conversion

Ruby

i = '0,11,22'
i.scan(/\d+/).map { |j| j.to_i }

i = '0,11,22'
i.scan(/\d+/).map(&:to_i)

Python

import re
i = '0,11,22'
list(map(int,re.findall(r'\d+', i)))

import re
i = '0,11,22'
[int(j) for j in re.findall(r'\d+', i)]

Split by regular expressions

Ruby

i = '0-11,22'
i.split(/[-,]/)

Python

import re
i = '0-11,22'
re.split(r'[-,]', i)

And more

Ruby

irb(main):001:0> '0-11,22'.split(/[-,]/).map { |j| j.to_i }
=> [0, 11, 22]
irb(main):002:0> '0-11,22'.split(/[-,]/).map(&:to_i)
=> [0, 11, 22]
irb(main):003:0> '0-11,22'.split(/[-,]/).map(&:to_i).inject(0) { |sum,j| sum + j }
=> 33
irb(main):004:0> '0-11,22'.split(/[-,]/).map(&:to_i).inject{ |sum,j| sum + j }
=> 33
irb(main):005:0> '0-11,22'.split(/[-,]/).map(&:to_i).inject(0, :+)
=> 33
irb(main):006:0> '0-11,22'.split(/[-,]/).map(&:to_i).inject(:+)
=> 33
irb(main):007:0> '0-11,22'.split(/[-,]/).map(&:to_i).reduce(:+)
=> 33

Note that reduce is an alias of inject. sum method for Array is available in Ruby 2.4.

Python3

>>> import re
>>> map(int, re.split(r'[-,]', '0-11,22'))
<map object at 0x7fc0dfe171d0>
>>> list(map(int, re.split(r'[-,]', '0-11,22')))
[0, 11, 22]
>>> [int(x) for x in re.split(r'[-,]', '0-11,22')]
[0, 11, 22]
>>> int
<class 'int'>
>>> sum(map(int, re.split(r'[-,]', '0-11,22')))
33
>>> sum(list(map(int, re.split(r'[-,]', '0-11,22'))))
33

Python2

>>> import re
>>> map(int, re.split(r'[-,]', '0-11,22'))
[0, 11, 22]
>>> list(map(int, re.split(r'[-,]', '0-11,22')))
[0, 11, 22]
>>> [int(x) for x in re.split(r'[-,]', '0-11,22')]
[0, 11, 22]
>>> int
<type 'int'>
>>> sum(map(int, re.split(r'[-,]', '0-11,22')))
33
>>> sum(list(map(int, re.split(r'[-,]', '0-11,22'))))
33

split strings including newlines

Ruby

irb(main):001:0> "a\nb\n".split
=> ["a", "b"]
irb(main):002:0> "a\nb\n".split("\n")
=> ["a", "b"]
irb(main):003:0> "a\nb\n".split("\n", -1)
=> ["a", "b", ""]
irb(main):004:0> "a\nb\n".lines
=> ["a\n", "b\n"]
irb(main):005:0> "a\nb\n".lines(chomp: true)
=> ["a", "b"]
irb(main):006:0> "a\nb\n".each_line.to_a
=> ["a\n", "b\n"]

Python

>>> "a\nb\n".split()
['a', 'b']
>>> "a\nb\n".split("\n")
['a', 'b', '']
>>> "a\nb\n".splitlines()
['a', 'b']
>>> "a\nb\n".splitlines(True)
['a\n', 'b\n']

YAML

Ruby

irb(main):001:0> i = [['a', 'b'], ['c', 'd']]
=> [["a", "b"], ["c", "d"]]
irb(main):002:0> require "yaml"
=> true
irb(main):003:0> print YAML.dump(i)
---
- - a
  - b
- - c
  - d
=> nil
irb(main):004:0> open('test.yaml', 'w').write(YAML.dump(i))
=> 28
irb(main):005:0> YAML.load(open('test.yaml'))
=> [["a", "b"], ["c", "d"]]

Python 3.7.3

>>> i=[['a', 'b'],['c', 'd']]
>>> import yaml
>>> print(yaml.dump(i), end="")
- [a, b]
- [c, d]
>>> print(yaml.dump(i, default_flow_style = False), end="")
- - a
  - b
- - c
  - d
>>> open('test.yaml', 'w').write(yaml.dump(i, default_flow_style = False))
24
>>> yaml.load(open('test.yaml'))
[['a', 'b'], ['c', 'd']]

Python 3.8.6

>>> i = [['a', 'b'], ['c', 'd']]
>>> import yaml
>>> print(yaml.dump(i), end="")
- - a
  - b
- - c
  - d
>>> print(yaml.dump(i, default_flow_style = False), end="")
- - a
  - b
- - c
  - d
>>> print(yaml.dump(i, default_flow_style = True), end="")
[[a, b], [c, d]]
>>> open("test.yaml", "w").write(yaml.dump(i))
24
>>> yaml.load(open("test.yaml"))
<stdin>:1: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details.
[['a', 'b'], ['c', 'd']]
>>> yaml.load(open("test.yaml"), Loader=yaml.FullLoader)
[['a', 'b'], ['c', 'd']]
>>> yaml.full_load(open("test.yaml"))
[['a', 'b'], ['c', 'd']]
>>> yaml.warnings({'YAMLLoadWarning': False})
>>> yaml.load(open("test.yaml"))
[['a', 'b'], ['c', 'd']]

In the case of UTF-8, allow_unicode = True is required for dump() in both python 3.7 and 3.8.

To suppress the warning in python 3.8, the following might be useful considering python 3.7 environment. See https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation.

if hasattr(yaml, 'FullLoader'):
    data = yaml.load(open(file), Loader=yaml.FullLoader)
else:
    data = yaml.load(open(file))
if hasattr(yaml, 'warnings'):
    yaml.warnings({'YAMLLoadWarning': False})
data = yaml.load(open(file))

Character translation

Ruby

irb(main):001:0> i = 'hoge'
=> "hoge"
irb(main):002:0> i.tr('he', 'HE')
=> "HogE"

Python

>>> i = 'hoge'
>>> i.translate(str.maketrans("he", "HE"))
'HogE'

Join

Ruby

irb(main):001:0> i = [0, 1, 2]
=> [0, 1, 2]
irb(main):002:0> i*','
=> "0,1,2"
irb(main):003:0> i.join(',')
=> "0,1,2"

Python

>>> i = [0, 1, 2]
>>> ','.join([str(j) for j in i])
'0,1,2'

Hash/Dictionary

Ruby

irb(main):001:0> i = [['a', 'b'], ['c', 'd']]
=> [["a", "b"], ["c", "d"]]
irb(main):002:0> h=Hash[i]
=> {"a"=>"b", "c"=>"d"}
irb(main):003:0> h.each { |key, val| print "#{key}: #{val}\n" }
a: b
c: d
=> {"a"=>"b", "c"=>"d"}
irb(main):004:0> h.each { |j| print j*': ', '\n' }
a: b
c: d
=> {"a"=>"b", "c"=>"d"}
irb(main):005:0> h.keys.each { |key| print "#{key}: #{h[key]}\n" }
a: b
c: d
=> ["a", "c"]

Python

>>> i = [['a', 'b'], ['c', 'd']]
>>> d = dict(i)
>>> d
{'a': 'b', 'c': 'd'}
>>> for i in d: print(i)
c
a
>>> for i in d.items(): print(i)
('c', 'd')
('a', 'b')
>>> for i in sorted(d): print(i)
a
c
>>> for i in sorted(d.items()): print(i)
('a', 'b')
('c', 'd')
>>> for key, val in sorted(d.items()): print(key+": "+val)
a: b
c: d

Regular expression options

Ruby

irb(main):001:0> "Hoge\nhoge".scan(/^h.*/)
=> ["hoge"]
irb(main):002:0> "Hoge\nhoge".scan(/^h.*/i)
=> ["Hoge", "hoge"]
irb(main):003:0> "Hoge\nhoge".scan(/^h.*/im)
=> ["Hoge\nhoge"]
irb(main):004:0> "Hoge\nhoge".scan(/\Ah.*/i)
=> ["Hoge"]

i: ignore case, m: multiline (. matches ‘\n’).

^ matches at the beginning of each line.

\A matches beginning of string.

Python

>>> import re
>>> re.findall(r'^h.*', 'Hoge\nhoge')
[]
>>> re.findall(r'^h.*', 'Hoge\nhoge', re.M)
['hoge']
>>> re.findall(r'^h.*', 'Hoge\nhoge', re.M | re.I)
['Hoge', 'hoge']
>>> re.findall(r'^h.*', 'Hoge\nhoge', re.M | re.I | re.S)
['Hoge\nhoge']

M(MULTILINE): multline (^ matches at the beginning of each line), I(IGNORECASE): ignore cases, S(DOTALL): also multiline (. matches any character at all).

Ruby’s gsub

Ruby

irb(main):001:0> "abc".gsub(/^(.)/){ "#{$1}." }
=> "a.bc"

Python

>>> import re
>>> re.sub(r'^(.)', r'\1.', "abc")
'a.bc'

Ruby’s each_with_index

https://stackoverflow.com/questions/25569577/python-equivalent-of-rubys-each-with-index

Ruby

irb(main):001:0> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
irb(main):002:0> a.each_with_index { |v, i| puts("#{i} : #{v}") }
0 : a
1 : b
2 : c
=> ["a", "b", "c"]
irb(main):003:0> print a.each_with_index.map { |v, i| "#{i} : #{v}\n" }.join
0 : a
1 : b
2 : c
=> nil

Python

>>> a = ['a', 'b', 'c']
>>> for i, v in enumerate(a):
...    print("{} : {}".format(i, v))
...
0 : a
1 : b
2 : c
>>> print(''.join(["{} : {}\n".format(i, v) for i, v in enumerate(a)]), end='')
0 : a
1 : b
2 : c

Embedding expressions in string

Ruby

irb(main):001:0> a = [10, 20, 30]
=> [10, 20, 30]
irb(main):002:0> "hoge: #{a[0]}"
=> "hoge: 10"
irb(main):003:0> "hoge: #{a[0] + 100}"
=> "hoge: 110"

Python

>>> a = [10, 20, 30]
>>> "hoge: {a[0]}".format(**locals())
'hoge: 10'

Python 3.6 and later

>>> a=[10, 20, 30]
>>> f"hoge: {a[0]}"
'hoge: 10'
>>> f"hoge: {a[0] + 100}"
'hoge: 110'

Command line arguments

Ruby

% ruby -e 'p ARGV' 0 1
["0", "1"]

Python

% python3 -c 'import sys; print(sys.argv)' 0 1
['-c', '0', '1']

Command execution (backticks in ruby/shell)

Ruby

print(`ls -l`)
print(%x{ls -l})
print(%x!ls -l!)

Python

import os
print(os.popen('ls -l').read(), end="")

File/directory check

Ruby

irb(main):001:0> File.exist?('Makefile')
=> true
irb(main):002:0> File.file?('Makefile')
=> true
irb(main):003:0> File.exist?('src')
=> false
irb(main):004:0> `mkdir src`
=> ""
irb(main):005:0> File.exist?('src')
=> true
irb(main):006:0> File.directory?('src')
=> true

Python

>>> import os
>>> os.path.exists('Makefile')
True
>>> os.path.isfile('Makefile')
True
>>> os.path.exists('src')
False
>>> os.popen('mkdir src')

>>> os.path.exists('src')
True
>>> os.path.isdir('src')
True

glob

Ruby

Dir.glob('*').each { |i| p i }
Dir['*'].each { |i| p i }

Python

import glob
for i in glob.glob("*"):
    print(i)

Array/List

Ruby

irb(main):001:0> a = []
=> []
irb(main):002:0> a.empty?
=> true
irb(main):003:0> a << 0
=> [0]
irb(main):004:0> a += [1, 2, 3]
=> [0, 1, 2, 3]
irb(main):005:0> a.empty?
=> false
irb(main):006:0> a.delete_if { |i| i == 0 }
=> [1, 2, 3]
irb(main):007:0> a
=> [1, 2, 3]
irb(main):008:0> a += [1, 2, 3]
=> [1, 2, 3, 1, 2, 3]
irb(main):008:0> a.uniq
=> [1, 2, 3]

Python

>>> a = []
>>> not a
True
>>> a.append(0)
>>> a
[0]
>>> a.extend([1, 2, 3])
>>> a
[0, 1, 2, 3]
>>> not a
False
>>> a = [i for i in a if not i == 0]
>>> a
[1, 2, 3]
>>> a.extend([1, 2, 3])
>>> a
[1, 2, 3, 1, 2, 3]
>>> a
[1, 2, 3, 1, 2, 3]
>>> set(a)
{1, 2, 3}
>>> list(set(a))
[1, 2, 3]
>>> import numpy
>>> numpy.unique(a)
array([1, 2, 3])
>>> list(numpy.unique(a))
[1, 2, 3]

Array/List (size, loop)

Python

>>> a = list(range(1, 4))
>>> a
[1, 2, 3]
>>> len(a)
3
>>> len(range(1, 4))
3
>>> for i in a:
...   print(i)
...
1
2
3
>>> for i in range(1, 4):
...   print(i)
...
1
2
3

Ruby

irb(main):003:0> a=(1...4).to_a
=> [1, 2, 3]
irb(main):004:0> a=(1..3).to_a
=> [1, 2, 3]
irb(main):006:0> a.size
=> 3
irb(main):007:0> (1..3).size
=> 3
irb(main):010:0> (1..3).each { |i| puts(i) }
1
2
3
=> 1..3
irb(main):013:0> a.each { |i| puts(i) }
1
2
3
=> [1, 2, 3]

Array/List (selection)

Python

>>> a=list(range(16))
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
>>> a[0:4]
[0, 1, 2, 3]
>>> a[:4]
[0, 1, 2, 3]
>>> a[10:]
[10, 11, 12, 13, 14, 15]
>>> a[10:15]
[10, 11, 12, 13, 14]
>>> a[10:16]
[10, 11, 12, 13, 14, 15]

Ruby

> a=(0..16).to_a
> a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
> a=(0...16).to_a
> a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
> a[0..4]
=> [0, 1, 2, 3, 4]
> a[0...4]
=> [0, 1, 2, 3]
> a[...4]
=> [0, 1, 2, 3]
> a[10..]
=> [10, 11, 12, 13, 14, 15]
> a[10..15]
=> [10, 11, 12, 13, 14, 15]
> a[10...15]
=> [10, 11, 12, 13, 14]

a[10..] or a[10..nil] cannot be unsed in ruby 2.5.

Double colon (::) in python

Pytnon

>>> a = list(range(16))
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
>>> a[::2]
[0, 2, 4, 6, 8, 10, 12, 14]
>>> a[::3]
[0, 3, 6, 9, 12, 15]
>>> a[1::3]
[1, 4, 7, 10, 13]
>>> a[2::3]
[2, 5, 8, 11, 14]
>>> a[10::3]
[10, 13]
>>> a[10::-1]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> a[10::-2]
[10, 8, 6, 4, 2, 0]
>>> a[10::-3]
[10, 7, 4, 1]

More precisely, a[i:j:k] means slice of a from i to j with step k. Double colon (::) just appears when j is omitted. Note that i and k also can be omitted.

Ruby

irb(main):001:0> a = (0...16).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
irb(main):002:0> a.each_slice(2).map(&:first)
=> [0, 2, 4, 6, 8, 10, 12, 14]
irb(main):003:0> a[1..-1].each_slice(2).map(&:first)
=> [1, 3, 5, 7, 9, 11, 13, 15]
irb(main):004:0> (0...16).step(2).to_a
=> [0, 2, 4, 6, 8, 10, 12, 14]
irb(main):005:0> 0.step(15, 2).to_a
=> [0, 2, 4, 6, 8, 10, 12, 14]

// in python

Pytnon

>>> 101/2
50.5
>>> 101//2
50

Ruby

> 101.to_f/2
=> 50.5
> 101/2
=> 50
> //.class
=> Regexp

mkdir -p (recursive mkdir and no error if existing)

Ruby

irb(main):001:0> require 'fileutils'
=> true
irb(main):002:0> FileUtils.mkdir_p('foo/bar/')
=> ["foo/bar/"]

Python

>>> import os
>>> os.makedirs("foo/bar/", exist_ok=True)

pythons’s defaultdict

Ruby

a=Hash.new { |h, k| h[k] = [] }
(0..2).each { |i| a[i] << i }
p a

output:

{0=>[0], 1=>[1], 2=>[2]}

Python

from collections import defaultdict
a=defaultdict(list)
for i in range(3):
  a[i].append(i)
from pprint import pprint
pprint(a)

output:

defaultdict(<class 'list'>, {0: [0], 1: [1], 2: [2]})

Ruby’s modifier `if’ and ternary syntax/ Python’s conditional expression (ternary syntax)

Ruby

puts "hoge" if true
a = 1 ; puts a == 1 ? 'hoge' : 'hogege'

Python

print("hoge" if True else "hogege")
a=0; b=0; a+=1 if False else b+=1 # invalid syntax
a=0; b=0; a+=1 if False else 1000 # a will be 1000)

Exception

Ruby

begin
    begin
        1/0
    rescue Exception=>e
        p e
        puts e
        puts('Exception occurs (inner)')
        raise 'Something bad (inner)'
    ensure
        puts('Ensures something (inner)')
    end
rescue Exception=>e
    p e
    puts e
    puts('Exception occurs (outer)')
    raise Exception.new('Something bad (outer)')
ensure
    puts('Ensures something (outer)')
end

Output

#<ZeroDivisionError: divided by 0>
divided by 0
Exception occurs (inner)
Ensures something (inner)
#<RuntimeError: Something bad (inner)>
Something bad (inner)
Exception occurs (outer)
Ensures something (outer)
t.rb:8:in `rescue in <main>': Something bad (inner) (RuntimeError)
        from t.rb:11:in `<main>'

Python

try:
    try:
        1/0
    except ZeroDivisionError:
        print("Exception occurs (inner)")
        raise Exception("inner")
    finally:
        print("finally... (inner)")
except Exception:
    print("Exception occurs (outer)")
    raise Exception("outer")
finally:
    print("finally... (outer)")

Output

Exception occurs (inner)
finally... (inner)
Exception occurs (outer)
finally... (outer)
Traceback (most recent call last):
  File "t.py", line 4, in <module>
    1/0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "t.py", line 7, in <module>
    raise Exception("inner")
Exception: inner

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "t.py", line 12, in <module>
    raise Exception("outer")
Exception: outer

pack/unpack

Ruby

irb(main):001:0> "\xff\xff".unpack('c*')
=> [-1, -1]
irb(main):002:0> [-1, -1].pack('c*')
=> "\xFF\xFF"

Python

>>> memoryview(b'\xff\xff').cast('b').tolist()
[-1, -1]
>>> import struct
>>> struct.unpack('bb', b'\xff\xff')
(-1, -1)
>>> [i[0] for i in struct.iter_unpack('b', b'\xff\xfe')]
[-1, -2]
>>> struct.pack('bb', *[-1, -1])
b'\xff\xff'

pack(’B*’) / string binary array to bytes

Ruby

% ruby -e "p [([1]*9).map(&:to_s).join].pack('B*')"
"\xFF\x80"
% ruby -e "p ['111111111'].pack('B*')"
"\xFF\x80"

Python

It seems that no such method exists.

>>> struct.pack("H", int("111111111", 2))
b'\xff\x01'
>>> struct.pack("H", int("111111111", 2) << 7)
b'\x80\xff'
>>> struct.pack("H", int("1111111110000000", 2))
b'\x80\xff'
>>> struct.pack(">H", int("1111111110000000", 2))
b'\xff\x80'

“%b” in Ruby

Ruby

irb(main):001:0> '%02x' % 10
=> "0a"
irb(main):002:0> '%08b' % 7
=> "00000111"

Python

>>> "%02x" % 10
'0a'
>>> "{:08b}".format(7)
'00000111'

%b in Python means byte sequence.

Process each line

Ruby

STDIN.each do |i|
    pp i
end
File.open(ARGV[0]).each do |i|
    pp i
end
File.open(ARGV[0]) do |fp|
    fp.each do |i|
        pp i
    end
end
File.open(ARGV[0]) { |fp|
    fp.each { |i|
        pp i
    }
}

Python

import sys
import pprint
for line in sys.stdin:
       pprint.pprint(line)
import sys
import pprint
for line in open(sys.argv[1]):
       pprint.pprint(line)
import sys
import pprint
with open(sys.argv[1]) as fp:
       for line in fp:
              pprint.pprint(line)
import sys
import pprint
fp = open(sys.argv[1])
try:
       pprint.pprint([line for line in fp])
finally:
       fp.close()

CSV I/O (read/write)

% echo 0,1 > test.csv
% echo 2,3 >> test.csv
% echo 4,\'5 6\' >> test.csv
% cat test.csv
0,1
2,3
4,'5 6'

Ruby https://docs.ruby-lang.org/ja/latest/class/CSV.html

irb(main):001:0> require 'csv'
=> true
irb(main):002:0> a = CSV.read('test.csv').to_a
=> [["0", "1"], ["2", "3"], ["4", "'5 6'"]]
irb(main):003:0> CSV.open('test2.csv', 'w') { |fp| a.each { |i| fp << i } }
=> [["0", "1"], ["2", "3"], ["4", "'5 6'"]]

Python https://docs.python.org/3/library/csv.html

>>> import csv
>>> a=[i for i in csv.reader(open("test.csv"))]
>>> a
[['0', '1'], ['2', '3'], ['4', "'5 6'"]]
>>> fp=csv.writer(open("test2.csv"," w"))
>>> [fp.writerow(i) for i in a]

Python https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html

>>> import pandas as pd
>>> a=pd.read_csv("test.csv")
>>> a
   0      1
0  2      3
1  4  '5 6'
>>> a.values
array([[2, '3'],
       [4, "'5 6'"]], dtype=object)
>>> open("test3.csv","w").write(a.to_csv(header=None,index=None))
12

xlsx file I/O (read/write)

Ruby https://github.com/weshatheleopard/rubyXL

Python https://openpyxl.readthedocs.io/en/stable/

import openpyxl
import sys
import pprint
wb = openpyxl.load_workbook(sys.argv[1])
print(wb.worksheets)
ws = wb.worksheets[0]
pprint.pprint([[ws.cell(i, j).value for j in range(1, 3)] for i in range(1, 41)])