40のおっさんのPython学習記録

20年以上前、学部の必修のC言語が全く理解できずに同級生に放り投げ、その後コーディングから遠ざかったガチ文系のおっさんが、ふと思い立ってPythonに挑戦しています。

10個整数を入力し最大の奇数を判定する問題、ただしパッケージ使用不可

おっさんです。MITのテキストは如何にも理系の教科書っぽくて、これまで読んできた入門書とは毛色が全く違って正直苦戦しています。

この数週間、おっさんは以下の問題にずっと苦しんでいました。10個整数値を入力しその中で最大の奇数を判定すると言う問題なのですが、条件があって、if、else、while、for文のみが使用可能です。この条件さえ外れてしまえば他のパッケージや関数を使えばおっさんでも短いコードはかけます。こんな感じです。

# MITテキストp.24指練習
# 10の整数の中で最大の奇数を判定する
import numpy as np
from numpy.random import *
numlist = list(randint(0,50,10))
print(f"入力された数列は:{numlist}")

revlist = np.array(sorted(numlist, reverse=True))

try:
    print(f"最大の奇数は{revlist[revlist%2==1].max()}でした。")
except:
    print("奇数が入力されてへんのちゃうんけボケ南港に沈めるぞコラ")

でも、こういう基本に立ち返った問題って非常に難しく、これよりも遥かに長ったらしいダラダラとしたコードを書いてムダなループ、無限ループを発生させて苦しんでいました。先週のもくもく会でmaxintを置くべしとヒントを貰って1歩進んで、ようやく今日マトモな短いコードとして完成しました。やっぱりおっさんはアホでセンスが無いです。

# MITテキストp.24指練習
# 10の整数の中で最大の奇数を判定する
import numpy as np
from numpy.random import *
numlist = list(randint(0,50,10))
print(f"入力された数列は:{numlist}")

a = 0
maxint=0
while a<10:
    if numlist[a]%2 == 0:
        a+=1
    else:
        if maxint > numlist[a]:
            a+=1
        else:
            maxint = numlist[a]
            a+=1

if maxint == 0:
    print("奇数が入力されていませんでした。")
else:
    print(f"最大の奇数は{maxint}でした。")

おっさん、自信を無くすの巻。

おっさんです。平日夜のもくもくに参加しています。ここはメンター的な方がいらしてて、素人の質問にも答えてくれる型式の場です。

入門書の2冊めを一応終わった感じもするので、3冊目は以前にも紹介したこれを進めていこうと思います。これは練習問題もあるので、練習問題を解いていく感じにしようかと思っています。

で今日は2章をやっているのですが、1問目の指練習問題のx、y、zの中で最大の整数を求める問題なんですが3時間目いっぱいかかってしまいました…やっぱおっさんセンスないんちゃうんかなあ…

# MITテキストp.18指練習

import numpy as np
x=int(input("正の整数xを入力しろやボケカス;"))
y=int(input("正の整数yを入力しろやボケカス:"))
z=int(input("正の整数zを入力しろやボケカス:"))

numlist = [x,y,z]
revlist = np.array(sorted(numlist, reverse=True))
print(revlist)

try:
    print(revlist[revlist%2==1].max())
except:
    print("奇数が入力されてへんのちゃうんけボケ南港に沈めるぞコラ")

新たな気づき(Chapter 13)

おっさんです。ヒノキの花粉症にやられており思考能力の低下が著しいです。おっさん、出張用のスマホAT&T iPhone 5をアンロックしたものを使い続けてきたんだけど、もう6年前の機種ということもあり、各種の動作が異様に遅くもう限界なので新しいスマホ買ったんです。BlackBerryのKeyONEというモデルのデュアルSIM機ですわ。日本で買うより安かったのでアメリカのAmazonで買うたわけですわ。

これが悲しいことに、SIMを指してもセルラーネットワークを安定して掴んでくれず、大半の時間は電波がありません状態なんですわ…アメリカのAmazonで買うてしもたせいで、返品がめんどくさそうでおっさん途方にくれています。どないしたらええねん…

ということで今日はファイル操作について学習しました。

  • open()は絶対/相対パスだけでなく、パスを読み込んだオブジェクトでも指定可能。
  • ファイルを開いたら、その開いた中身を使って処理をする前に.close()で閉じるのが鉄則。
  • 一方、with open() as構文を使うと、close()処理は必要ない。
file = "./fox.txt"
with open(file) as fileobj:
    text = fileobj.read()
    print(text)
  • コマンドライン引数を利用したい場合は、sysモジュールのargv属性の利用が必要なのでimport sysが必須。
  • コマンドライン引数とは、python code.py hoge hageの、hoge hageのみならず、code.pyも含む。
  • sys.argvにコマンドライン引数が渡される。上記の通りコードファイルが第1引数なのでargv[0]。hogeを利用したければargv[1]、hageを利用したければargv[2]で指定。
import sys
if len(sys.argv)<2: #1個目の引数はコードファイルなので、2個目にあるはずのパス名がない場合の処理
    print("読み込むファイル名を指定して下さい。")
    sys.exit()

file = sys.argv[1]  #[0]はコードファイルなので、[1]でパス名を取る
with open(file) as fileobj:
    text = fileobj.read()
    print(text)
  • read(x)とすると、x文字読む。もう一度同じオブジェクトにread(x)するとその次のx文字を読み込む。
file = "./fox.txt"
with open(file) as fileobj:
    while True:
        text = fileobj.read(10)  #10文字ずつ読むことを繰り返す
        if text:                 #文字列があれば出力
            print(text)
        else:                    #文字列がなくなったら終了
            break
  • readline()は1行ずつ読み込むことのできるメソッド。1行終わると次の行に進む。
file = "./rihaku_kanshi.txt"
with open(file) as fileobj:
    while True:
        line = fileobj.readline() #1行ずつ読み込む
        aline = line.rstrip()     #読み込んだ行の最後にある改行を削除
        if aline:
            print(aline)
        else:
            break
  • 上記を李白漢詩を読み込んだら以下の通りになる。改行コードを削除しないと行間に更にもう1行含まれる間延びした出力になるので要注意。
花間一壼酒  花間一壺の酒
獨酌無相親  独り酌んで相親しむもの無し
舉杯邀明月  杯を挙げて明月を迎え
對影成三人  影に対して三人と成る
月既不解飮  月既に飲を解せず
影徒隨我身  影徒らに我が身に随う
暫伴月將影  暫く月と影とを伴い
行樂須及春  行楽須らく春に及ぶべし
我歌月徘徊  我歌えば月徘徊し
我舞影零亂  我舞えば影零乱す
醒時同交歡  醒むる時は同に交歡し
醉後各分散  酔うて後は各分散す
永結無情遊  永く無情の遊を結び
相期遥雲漢  相期す遥かなる雲漢に
  • open()で読み込んだオブジェクトそのものが行単位で要素を取り出せるイテレータなので、next(fileobj)で1行ずつ取り出せる。以下のコードで同じことができる。
file = "./rihaku_kanshi.txt"
with open(file) as fileobj:
    while True:
        try:
            line = next(fileobj)
            aline = line.rstrip()
            print(aline)
        except StopIteration:
            break
  • for-in enumerateを使うと次のように書ける
file = "./rihaku_kanshi.txt"

with open(file) as fileobj:
    for i, line in enumerate(fileobj,1): #iの開始番号を0でなく1にするために,1を付加
        if line == "\n":
            continue
        aline = line.rstrip()
        print(f"{i}:{aline}")

出力結果は以下のような感じ。

1:花間一壼酒  花間一壺の酒
2:獨酌無相親  独り酌んで相親しむもの無し
3:舉杯邀明月  杯を挙げて明月を迎え
4:對影成三人  影に対して三人と成る
5:月既不解飮  月既に飲を解せず
6:影徒隨我身  影徒らに我が身に随う
7:暫伴月將影  暫く月と影とを伴い
8:行樂須及春  行楽須らく春に及ぶべし
9:我歌月徘徊  我歌えば月徘徊し
10:我舞影零亂  我舞えば影零乱す
11:醒時同交歡  醒むる時は同に交歡し
12:醉後各分散  酔うて後は各分散す
13:永結無情遊  永く無情の遊を結び
14:相期遥雲漢  相期す遥かなる雲漢に
  • ファイルに書き込む場合はopen()メソッドに"w"あるいは"a"のモードオプションをつける。"w"は既存の内容を消して上書き、"a"は追記。
  • with-asを使えばclose()しなくていいのは書き込みも同じ。
  • 以下の事例はpython code.py 文字列を実行すると、実行時間、書き込み文字列を区切り線とともにどんどんファイルに追記していく。
import sys
from datetime import datetime
file = "./log.txt"

#文字列の引数がない場合は処理を中断
if len(sys.argv)<2:
    sys.exit()

now  = str(datetime.now())
memo = sys.argv[1] #文字列の引数
line = "-"*10 #区切り線
with open(file, "a") as fileobj:
    fileobj.write(now+"\n")  #書き込み時間を入れる
    fileobj.write(memo+"\n") #引数を入れる
    fileobj.write(line+"\n") #区切り線を入れる
  • 書き込みの場合のダイアログを呼び出す場合はtkinterのasksaveasfilenameメソッドを呼び出す。
  • ファイルはなければ作成されるが、フォルダの場合はない場合は作成されずエラーになる。なので事前にフォルダの有無を確認する必要が出て来る。os.path.exists(パス)でファイル、フォルダが存在するかどうかが確認できる。Trueで存在、Falseで非存在。逆にファイルはある場合は上書きされることにもなりかねないので、os.path.exists(パス/ファイル名)でファイルの有無を調べ、注意をうながすこともありえる。
import os
from random import randint

#保存フォルダとパス
folder = "./data/"
file = folder+"sample.txt"

#ファイルを保存する
def filewrite():
    if not os.path.exists(folder): #フォルダの存在を確認
        os.makedirs(folder)        #なければフォルダを作成
    with open(file, "w") as fileobj: #ファイルはあれば既存のものが開き、なければ新規に作成される
        num = randint(0,100)
        fileobj.write(f"{num}が出ました。")
        print("ファイルを保存しました。")

#既存のファイルの有無をチェック。あれば注意をうながすメッセージを表示。
if os.path.exists(file):
    while True:
        answer = input("上書きしてもよいですか? (y/n)")
        if answer == "y":
            filewrite()
            break
        elif answer == "n":
            break
else:
    filewrite()

新たな気づき(Chapter 12-3)

おっさんです。花粉症で集中できずにどうしてもこのクラスを扱う12章がもくもく会の会場で完結できなかったのを、意地で自宅で全てやりました。無理やりやっつけることが目的化し、完全に理解できているかは怪しいです。でもとにかく前に進みましょう。

  • インスタンス変数にアンダースコアを2つ先頭につけるとクラスの外からのアクセス・参照ができなくなる。
class Person():
    def __init__(self, name):
        self.__name = name         #アンダースコア2つで外部参照不能になる
    def who(self):
        print(self.__name+"です。") #クラス内では利用可能

man=Person("宇佐美")
man.who()                          #宇佐美です。
print(man.__name)                  #直接アクセスはエラー
  • ただし、非公開にしたインスタンス変数も、ゲッター関数を用いて値を調べたり、セッター関数を用いて値を変更したり可能。ただ、1個の変数のアクセスに2個の関数を使うのは効率的とはいえないことから「プロパティ」の考え方を導入。デコレータ@propertyにつづいてゲッター関数を定義し、デコレータ@関数名.setterにつづいてセッター関数を定義する。この時ゲッターもセッターも関数名は共通にする。
@property
def 関数名(self):
    return self.__非公開変数  #プロパティの値を取得し返す
    
@関数名.setter
def 関数名(self, value):
    self.__非公開変数 = value #プロパティに値を設定
  • 以下の事例の場合、nameにはゲッターとセッターの両方を定義しているが、priceにはゲッターしか定義しておらずセッターがないので、shoes.priceで新たな値を設定しようとしてもエラーになる。
class Goods:
    #初期化メソッド
    def __init__(self, name, price):
        #非公開の__dataインスタンス変数(辞書)
        self.__data = {"name":name, "price":price}

    #nameプロパティ:ゲッター
    @property
    def name(self):
        return self.__data["name"]

    #nameプロパティ:セッター
    @name.setter
    def name(self, value):
        self.__data["name"] = value

    #priceプロパティ:ゲッター
    @property
    def price(self):
        price = self.__data["price"]
        price_str = f"{price:,}円"
        return price_str

    #priceプロパティのセッターは用意せず

shoes = Goods("Dream",6800)
print(shoes.name) #Dream
shoes.name = "Dream Girls"
print(shoes.name) #Dream Girls
print(shoes.price) #6,800円
shoes.price = 7000
print(shors.price) #priceにはセッター関数がないのでエラー
  • デコレータを使わずに、プロパティ変数 = property(ゲッター関数、セッター関数)で指定することもできる。具体的には以下のコードのようになるが、@で始まるデコレータが消えた代わりに、関数名にget_あるいはset_で始まるものになり、クラス定義の最後にproperty()を用いたプロパティの設定を行っている。アウトプットは上記の事例と同じ。
class Goods:
    #初期化メソッド
    def __init__(self, name, price):
        #非公開の__dataインスタンス変数(辞書)
        self.__data = {"name":name, "price":price}

    #nameプロパティ:ゲッター
    def get_name(self):
        return self.__data["name"]

    #nameプロパティ:セッター
    def set_name(self, value):
        self.__data["name"] = value

    #priceプロパティ:ゲッター
    def get_price(self):
        price = self.__data["price"]
        price_str = f"{price:,}円"
        return price_str

    #priceプロパティのセッターは用意せず

    #nameプロパティのゲッターとセッターを指定
    name = property(get_name, set_name)
    #priceプロパティのゲッターを指定(セッターは指定せず)
    price = property(get_price)

shoes = Goods("Dream",6800)
print(shoes.name) #Dream
shoes.name = "Dream Girls"
print(shoes.name) #Dream Girls
print(shoes.price) #6,800円
shoes.price = 7000
print(shors.price) #priceにはセッター関数がないのでエラー

新たな気づき(Chapter 12-2)

禿山で治水機能が低下したとしても杉なんか全部切り倒すべきだと思いませんか?おっさんは強くそう思っています。

  • スーパークラスAがあったとして、クラスAの機能を利用しつつも、クラスA自体に変更を加えたくない場合は新たなクラスBを作成し、クラスBからクラスAを読み込むことにより、クラスBではクラスAの機能に加えて新たな機能を実装できる。これを承継と言う模様。下の場合クラスAはBye!しか言えないけど、クラスBはByeもhelloも両方言える。クラスAにhelloを言おうとさせてもエラーになる。
>>> class A:
...     def bye(self):
...         print("Bye!")
...
>>> class B(A):             #スーパークラスとしてAを指定
...     def hello(self):
...         print("hello")
...
>>> obj=B()
>>> obj.hello()
hello
>>> obj.bye()
Bye!
>>> obj2=A()
>>> obj2.hello()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'hello' #クラスAにはhello機能はないよ
>>> obj2.bye()
Bye!
  • 外部ファイルのクラスをスーパークラスにすることも可能。from importで読み込めば良い。
from 外部ファイル import スーパークラス
class サブクラス(スーパークラス):
    以下サブクラス定義
  • スーパークラスのメソッドの一部を書き換えたい場合は、サブクラスからスーパークラスを呼び出した上で、同じ名前のメソッドを定義して上書きすることが推奨される。これをオーバーライドという。
#スーパークラス
class Greet():
    def hello(self):
        print("Hello!")
    def buy(Self):
        print("Bye!")

#サブクラス
class Greet2(Greet): #Greetをスーパークラスとして指定
    #スーパークラスのメソッドをオーバーライドする
    def hello(self, name=None): #スーパークラスと同じ名前のhelloを定義
        if name:
            print(name+"さんこんにちは!")
        else:
            super().hello() #スーパークラスのhello()をそのまま利用

obj1 = Greet2()
obj1.hello()      #nameに何も代入されなかったのでelse文が実行される
obj1.hello("井上") #nameの引数が指定されたのでif文が実行される。
#スーパークラス
class Person():
    def __init__(self, name, age):        #この初期化メソッドはサブクラスでは無効化される
        self.name=name
        self.age=age

#サブクラス
class Player(Person):
    def __init__(self, number, position): #最終的にこの初期化メソッドが生きる
        self.number=number
        self.position=position
  • また、サブクラスからスーパークラスの初期化メソッドを参照したい場合は、以下のように書く。
#スーパークラス
class Person():
    def __init__(self, name, age):
        self.name=name
        self.age=age

#サブクラス
class Player(Person):
    def __init__(self, name, age, number, position):
        super().__init__(name, age) #スーパークラスの初期化メソッドを呼び出す。selfは書かない
        self.number=number
        self.position=position

新たな気づき(Chapter 12-1)

花粉症がひどくて頭が全然働いてないところに、自分的に苦手なクラスをやっているので、全然前に進んでいる感じがしません。

  • クラスはオブジェクトの設計書であり、クラスで作ったオブジェクトをインスタンスと呼ぶ
  • 初期化メソッドとは、インスタンスが作られたときに自動的に実行されるもの。名前は慣例的に__init__とされ、第1引数はself。
class クラス名:
    クラス変数の定義

    @classmethod
    def クラスメソッド名(cls, 引数1, 引数2, ...):
        クラスメソッドの定義

    def __init__(self, 引数1, 引数2, 引数3="デフォルト値"...):
        初期化の処理

    def メソッド名(self, 引数1, 引数2, 引数3="デフォルト値"...):
        インスタンスメソッドの定義
  • 上記の初期化処理の位置に、「self.変数名 = 初期値」を置く。引数でデフォルト値を指定しそれを代入することも可能。
  • インスタンス名=クラス()でインスタンスを作成
  • インスタンス.変数でインスタンスの変数に何が入っているかにアクセスできる。
  • インスタンス.変数 = 値、でインスタンス変数に値を代入できる。
  • インスタンスが実行できること」を上記のコードの後段部分でインスタンスメソッドとして定義する。
  • インスタンスメソッドの定義も同じくselfを第1引数として置く。
  • インスタンス.メソッド()でインスタンスメソッドを実行できる。
  • クラス自身がクラス変数とクラスメソッドを持つことも可能。これらをクラスメンバーと呼ぶ
  • クラス自身が持つことでクラス内に複数のインスタンスを定義するときにデフォルトとして共有でき、クラスメソッドもインスタンスから実行できる。
  • クラス.変数名でクラス変数を参照できる。
  • クラスメソッドはdef文で定義するのはインスタンスメソッドと同じだが、defの前の行に@classmethodとデコレータを置く必要がある他、第1引数がselfではなくclsである点が違う。
class Car:
    #クラス変数
    maker="RENAULT" #メーカー
    count=0         #台数

    @classmethod
    def countup(cls):
        cls.count += 1
        print(f"出荷台数は{cls.count}台です。")

    #初期化メソッド
    def __init__(self, color="white"):
        Car.countup()       # クラスメソッドを呼び出し台数をカウントアップする。
        # 初期化メソッドが利用され、インスタンス数が増えるごとに車の台数が増える。
        self.mynumber = Car.count #出荷台数を自分番号として保存する。生産番号のようなもの。
        self.color = color  # 車の色の初期値を引数から受け取る
        self.mileage = 0    # 走行距離の初期値をセロにする

    #インスタンスメソッド
    def drive(self, km):
        self.mileage += km
        msg = f"{km}kmドライブしました。総走行距離は{self.mileage}kmです。"
        print(msg)

#クラス変数を参照
print(Car.maker)    #RENAULT
print(Car.count)    #最初だから0

car1 = Car()        # 白い車のインスタンスを作成。デフォルト
car2 = Car("red")   # 赤い車のインスタンスを作成。colorに代入される
car3 = Car("black") # 黒い車のインスタンスを作成。
print(Car.count)

# "インスタンス.変数名"でインスタンスの変数名にアクセスできる
print(car1.mileage) # 0
print(car2.color)   # red

#インスタンス変数の値を更新する
print(car1.color)   # white
car1.color = "blue"
print(car1.color)   # blue

# "インスタンス.メソッド()"でインスタンスメソッドを実行
car1.drive(15) #  0+15=15
car1.drive(20) # 15+20=35
car2.drive(50) #  0+50=50

car2.mileage = 100 #100で再設定
car2.drive(10)     #100+10=110
  • クラスを定義した後でも変数やメソッドは追加可能。クラス.クラス変数A=値aを入力し、そのクラス変数Aがクラス内に定義されていない場合は、クラス変数Aがクラスに追加される。これはメソッドでも同様。クラス.クラスメソッドA=メソッドaが入力され、クラスメソッドAがクラス内に定義されていない場合は、クラスメソッドがクラスに追加される。インスタンスにも同様の事が可能。
  • 追加したメンバーは「メンバー = None」もしくは「del メンバー」で削除可能。

転職してえ、とか言っていたら

転職キメてきました。おっさんでも転職できました。

とりあえずおっさん、有給消化でブエノスアイレスに行くことばかりを考えていて、Pythonどころではありません。まあ、ブエノスアイレスパレルモのカフェで沈没してPythonを学ぶってのもまあおつなもんかな。

ということで、これから退職というか、有給消化に関する交渉を頑張ります!