Python の勉強 スロットゲーム編 〜その6:数値と文字の違い、文字列のフォーマット〜

プログラミング

今回は少し細かい話で、タイトルにある通り数値と文字の違いについての確認になります。細かいですがプログラムを勉強する上で、知っておかなく必要がある大切な内容です。

数値と文字(列)は別物

2」(整数)や「3.0」(小数点数)の様に表される値で算術に用いることができます。スロットゲームでは所持コイン数や、絵柄が揃ったときの倍率にも使用しています。
Python に限らずほとんどのプログラミング言語では整数と浮動小数点数を分けているのも特徴です。整数と小数点数の両方を用いて計算した場合、結果は小数点数になります。

整数と小数点数を用いた算術結果の確認

一方、文字(列)は「'あいうえお'」や「"かきくけこ"」のように、シングルクォーテーション(')または、ダブルクォーテーション(")で囲って表現されます。文字(列)は足し算をすることで、文字列を結合できるという特徴があります。

文字列を足し算してつなげている様子

この例だけだと、別物と思うのは当然だと思います。しかし「2」と「'2'」、「3.0」と「"3.0"」ではどうでしょうか?Python では、一部の例外を除いて数値と文字(列)を同じ式の中で扱うことが許されておりません。実際に確認してみましょう。ここでは、何回もエラーになるので、対話モードで確認してます。

1 + '2.0'         #==> TypeError: unsupported operand type(s) for +: 'int' and 'str'
'3.123' - 0.123   #==> TypeError: unsupported operand type(s) for -: 'str' and 'float'
5.0 * '6.0'       #==> TypeError: can't multiply sequence by non-int of type 'float'
5 * '6.0'         #==> '6.06.06.06.06.0'
'6.0' * 5         #==> '6.06.06.06.06.0'
'7' / 8           #==> TypeError: unsupported operand type(s) for /: 'str' and 'int'
数値と文字列の算術結果

もうお分かりいただけたと思いますが、上記の “一部の例外” とは “文字列と整数の掛け算” のことでした。このケースだけ、文字列を繰り返した結果になります。面白いですねw

これはプログラミング言語によって扱いが違います。

本文下の方に、私の身近なプログラミング言語についてまとめておきますね。

型(type)

ここまで日本語では説明に「整数」「浮動小数点数」「文字(列)」を使ってきました。これを Python 風に呼称するにはどうしたらいいでしょうか?そのヒントは、上記のエラー内容に隠れています。

エラー内容を細かく見てみると、「type(s)」の後に「int」「float」「str」と出力されています。これが Python 内での……動(やや)もすると、ほとんど全てのプログラミング言語での呼称になります。
値の種類を「型」(type)というもので分類し、代表的なものには以下のようなものがあります。そして「int型」(いんとがた)とか、「str(ing)型」(すとりんぐがた)のように “型” と合わせて会話に登場します。
※型(type)は自作することもできるため無限に存在します。

意味Python での表記
intinteger の略。整数。1234
float浮動小数点数。2.03.14
strstring の略。文字列'hello'"world"
boolboolean の略。真偽値。
真(True)と偽(False)しかない。
TrueFalse
listリスト、配列。
型を問わず、復数個の値を保持できる。
[1, 2.0, 'a', True]
dict辞書。
キー(key)と値(value)のペアを複数個保持できる。
{0: 'hello', 'w': 'world'}
set集合。
順不同、重複しない複数個の値を保持できる。
{0, 1, 'hello', 'world'}
tupleタプル。
リストっぽいけど、変更不可(イミュータブル)。
(1, 2.0, ‘a’, True)
代表的な型(type)の一覧

型の調べ方

Python では「type」を使って型を調べることができます。その様子を下記します。ほとんどは「あれ、この変数に入ってるのって何型の値だっけ?」という場合かと思いますので、あえて変数に代入してから調べてます。
print() を使って結果を表示することもできます。しかし type() が目立たなくなってしまうので、今回も対話モードで確認しました。

a = 1
type(a)                               #==> <class 'int'>
b = 2.0
type(b)                               #==> <class 'float'>
c = 'hello'
type(c)                               #==> <class 'str'>
d = True
type(d)                               #==> <class 'bool'>
e = [1, 2.0, 'a', True]
type(e)                               #==> <class 'list'>
f = {0: 'hello', 'w': 'world'}
type(f)                               #==> <class 'dict'>
g = {0, 1, 'hello', 'world'}
type(g)                               #==> <class 'set'>
h = (1, 2.0, 'a', True)
type(h)                               #==> <class 'tuple'>

# 変数にいれなくても確認できます
type('hello')                         #==> <class 'str'>
変数の型(type)を確認する様子

(おまけ)Python は小数点数の扱いがニガテ……

小数点数を用いた計算「0.5 + 0.25 == 0.75」「1.11 + 2 == 3.11」は成り立つと思いますか?普段の暮らしでこれを疑うことはないでしょう。これは Python でも当然……ではないんです!実は Python は小数点数を用いた計算がとてもニガテです。実際にプログラムを書いて確認してみましょう。

if 1.5 + 2.25 == 3.75:
    print('正しい')           # 結果はこちらになる。これは正しい。
else:
    print('違う!!')

if 1.11 + 2.00 == 3.11:
    print('正しい')
else:
    print('違う!!')         # 結果はこちらになる!
値によって間違った答えになってしまう様子

コンピュータは2進数、つまりゼロ(0)とイチ(1)だけの世界というのは聞いたことがあると思います。そのため整数については「39(10進数) →100111(2進数)」「255(10進数)→11111111(2進数)」のように簡単に表せます。では小数点数についてはどう考えたらいいのでしょうか?
これを説明するのは少し長くなりますので、別の記事にしたいと思います。ここではコンピュータが小数点数を扱うのがニガテだということ、そして Python も同じ様にニガテだということを覚えておいてください。

では、小数点数の計算を正確にやりたい場合はどうしたらいいのでしょうか?答えは「Decimal型」(でしまるがた)です。Python の公式ページ(こちら)の文言を借りると「正確に丸められた十進浮動小数点算術をサポート」されています。
まず「from decimal import Decimal」と書く必要がある等、使い方にクセがあります。今回のスロットゲームでは必要ないため、この程度の紹介とさせていただきます。

from decimal import Decimal

print(type(Decimal('1.11')))        #==> <class 'decimal.Decimal'>

if Decimal('1.5') + Decimal('2.25') == Decimal('3.75'):
    print('正しい')                  # 正しく計算され、結果はこちらになります
else:
    print('違う')

if Decimal('1.11') + Decimal('2.00') == Decimal('3.11'):
    print('正しい')                  # 正しく計算され、結果はこちらになります
else:
    print('違う')
decimal を使って計算している様子

あなたが金融や通貨に関するアプリケーションを作るようになった際には必要になります。その頃にはあなたのプログラミング能力も相当高くなっていることでしょうから、ご自身で decimal について詳しく調べてみてください。

数値を文字列、文字列を数値に型変換

では、「現在の所持コインは 273 枚です」の様に、文字列と数値を合わせて使いたいときにはどうしたらいいでしょうか?「273」が文字列だったら、上記の例にならって足し算するだけでいいですが、所持コイン数は数値として管理されています。

そんな時に登場するのが「型変換」や「キャスト」と言われる手法です。Python での「型変換(キャスト)」の様子を確認してみましょう。

a = 123
type(a)

a = str(a)   # str への変換は str() を使用する
type(a)      #==> <class 'str'>

a = int(a)   # int への変換は int() を使用する
type(a)      #==> <class 'int'>

a = float(a) # float への型変換は float() を称する
type(a)      #==> <class 'float'>

b = 'aiueo'  # 型変換(キャスト)できない場合はエラーが発生
b = int(b)   #==> ValueError: invalid literal for int() with base 10: 'aiueo'

ちなみに int型、str型への型変換はスロットゲーム中でも使用されています。

int()、str() が使われている場所

“フォーマット” は型変換をせずに文字列を生成する方法

あれこれ書いてきましたが、今回の目的(数値と文字列を使って、新しい文字列を表現する)には適したやり方ではありません。間違ってはいないのだけど悪手に分類されます。では、どういうやり方が推奨されているかと言うと……

  • フォーマット済み文字列リテラル(f-string)を使う
  • 文字列の format() メソッドを使う
  • 文字列書式設定方法を使う(ただし、古いやりかた)

になります。それぞれの凝った使い方は公式ページ(こちら)に詳しく記載がありますので、そちらを参照していただくとして、ここでは簡単な使い方を確認しましょう。

フォーマット済み文字列リテラル

文字列の頭に f か F を付け、式を {expression} と書くことで、 Python の式の値を文字列の中に入れ込む方法です。生成される文字列も容易に想像できるので一番オススメな方法になります。

test_result_math = 80
test_result_avg = 68.2343

print(f'数学のテストで {test_result_math} 点を取りました。')               # 単純な埋め込み
print(f'クラスの平均点は {test_result_avg:.2f} 点です')                   # 小数点以下の桁数制御
print(f'平均点との差は {test_result_math - test_result_avg:.2f} 点です')  # 式を埋め込み

print(f'{test_result_math:010}')    # ゼロ埋め
print(f'{test_result_avg:010.2f}')  # ゼロ埋め+小数点以下の桁数制御
print(f'{test_result_avg:.3g}')     # 全体の桁数制御
print(f'{test_result_avg:.2e}')     # 指数表記(小数点以下の桁数制御)
print(f'{test_result_avg:.2%}')     # パーセント表記(小数点以下の桁数制御)

print(f'My name is {"Michael":13}. Nice to meet you.')          # 領域を確保
print(f'My name is {"Christopher":>13}. Nice to meet you.')     # 右寄せ
print(f'My name is {"Matthew":^13}. Nice to meet you.')         # 中央寄せ
print(f'My name is {"Joshua":<13}. Nice to meet you.')          # 左寄せ
print(f'My name is {"Daniel":_^13}. Nice to meet you.')         # 寄せ+埋め
フォーマット済み文字列を用いた様子

これくらい覚えておけば(忘れてもこの記事を見ればOK)困ることはないと思います。

文字列の format() メソッド

これは、上記 f-string の一つ前のやり方になります。この形式を使っているプログラムもまだまだ多いかも知れませんね。やれることは同じですが、書き方が変わります。

test_result_math = 80
test_result_avg = 68.2343

print('数学のテストで {} 点を取りました。'.format(test_result_math))
print('クラスの平均点は {:.2f} 点です'.format(test_result_avg))
print('平均点との差は {:.2f} 点です'.format(test_result_math - test_result_avg))

print('平均:{}、点数:{}'.format(test_result_avg, test_result_math))             # 複数、指定
print('平均:{1}、点数:{0}'.format(test_result_math, test_result_avg))           # 順序付き
print('平均:{a}、点数:{b}'.format(a=test_result_avg, b=test_result_math))       # 名前付き
文字列の format() メソッドを使った様子

文字列書式設定方法

最後に「%」を使った古いやり方です。

test_result_math = 80
test_result_avg = 68.2343

print('平均:%.2f、点数:%d' % (test_result_avg, test_result_math))
print('平均:%(avg).2f、点数:%(result)d' % {'avg': test_result_avg, 'result': test_result_math})
「%」を使った古いやり方を実践する様子

最後に

スロットゲームのプログラムには、フォーマット文字列を使ったいいやり方と、足し算を使った推奨されないやり方が混在しています。次回は、フォーマットを用いたやり方を復習するために、足し算を使って文字列を作っている箇所をフォーマットを用いたやり方に修正しましょう。

(おまけ)プログラミング言語毎の文字列の掛け算の違い

実行に当たっては paiza.IO を利用して確認いたしました。

Python

Python は文字列が連続して出力されます。

# coding: utf-8
# Your code here!

a = 2
b = '3.0'
c = a * b

print(type(a))      #==> <class 'int'>
print(type(b))      #==> <class 'str'>
print(type(c))      #==> <class 'str'>
print(c)            #==> 3.03.0

JavaScript

JavaScript は勝手に数値に変換して計算してくれます。
以下の例で「a」を「"あ"」など数値以外にするとエラーが発生します。

process.stdin.resume();
process.stdin.setEncoding('utf8');
// Your code here!

a = 2;
b = '3.0';
c = a * b;

console.log(typeof a);      //==> number
console.log(typeof b);      //==> string
console.log(typeof c);      //==> number
console.log(c);             //==> 6

PHP

PHP も JavaScript と同じく勝手に数値に変換して計算してくれます。

数値以外にするとエラーになるのも同じ。

<?php
// Your code here!

$a = 2;
$b = '3.0';
$c = $a * $b;

echo gettype($a) . PHP_EOL;     //==> integer
echo gettype($b) . PHP_EOL;   	//==> string
echo gettype($c) . PHP_EOL;   	//==> double
echo $c          . PHP_EOL;   	//==> 6
?>

Ruby

Ruby ではエラーになります。Ruby は文末に「;」がなかったり「def」を使ったりと、Python に似ている部分が多いです。日本人が作ったプログラミング言語としても有名です。スタートアップ企業で採用されることも多いです。

# Your code here!

a = 2
b = '3.0'
c = a * b     #==> String can't be coerced into Integer (TypeError)

p a.class     #==> Integer
p b.class     #==> STring

Java

かの有名な Java も同様にエラーになります。こちらは一昔前まで、プログラミングの登竜門みたいになってましたね。

import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        // Your code here!
        
        var a = 2;
        var b = "3.0";
        var c = a * b;    //==> Main.java:9: error: bad operand types for binary operator '*'
        
        System.out.println(((Object)a).getClass().getName());  //==> java.lang.Integer
        System.out.println(b.getClass().getName());            //==> java.lang.String
    }
}

C#

C# でもエラーになります。C# 以前に C++ というプログラミング言語があります。「++」を2つ重ねて「#」にした……という噂を聞いたことがあります。

public class Hello{
    public static void Main(){
        // Your code here!
        
        var a = 2;
        var b = "3.0";
        var c = a * b;      //==> Main.cs(7,17): error CS0019: Operator `*' cannot be applied to operands of type `int' and `string'
        
        System.Console.WriteLine(a.GetType());      //==> System.Int32
        System.Console.WriteLine(b.GetType());      //==> System.String
    }
}

Clojure

Clojure でもエラーになります。Java を元に作られているのでエラー内容に “java” が登場します。
Clojure は “神の言語” と名高い LISP を模したプログラミング言語です。作者のコメントがこちらで翻訳されていました。

; Your code here!

(let [a 2
      b "3.0"
      c (* a b)]          ;;==> Syntax error (ClassCastException) compiling at (Main.clj:1:1). 
                          ;;    class java.lang.String cannot be cast to class java.lang.Number
  (println (type a))      ;;==> java.lang.Long
  (println (type b)))     ;;==> java.lang.String

コメント

タイトルとURLをコピーしました