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

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

新たな気づき(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を学ぶってのもまあおつなもんかな。

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

生存報告

おっさんです。生きています。挫折したわけではありません。

おっさんは先週1週間海外出張に行っていました。海外出張、手間がかかるんですよ。出張の準備をやって、帰ってきたら報告書をまとめなきゃならんのです。転職前の会社は出張報告なんか必要ない、実務で表現できている限り問題ない、という会社だったんですけどね、おっさんの今の会社はいちいち報告会をやらないかんのですよ。報告書を書いた上で報告会ですよ。おっさんの仕事的には海外出張しなきゃならんのですけど、おっさんの仕事に関係のない人たちにとっては海外出張なんかただ金のかかるイベントでしかなくて、報告を通じて会社に還元しなければならないとかいう文化なんだそうですよ。おっさんの出張とあんたらの業務関係ないじゃん。遊びにいってるわけじゃないんだよ。40越えてエコノミークラスに乗って、1日4〜5件ミーティングするってどれだけ苦痛だと思ってるの。めんどくせえ。転職してえ。ということで、1週間出張に行くと、準備に一月(これは以前の会社と変わらず)、報告に2週間(これは以前の会社に比べてまるまる追加負担)というかんじで、Pythonの勉強はまるまる滞ってます。

今日ももくもく会にいってましたけどね、もくもくと出張報告を書いてましたよ。もう一体何なんだろうね。

あー、さっさと今の会社辞めたい。

ということでおっさんの生存報告でした。

今週末のもくもく会からはPythonの勉強再開できると思います。

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

おっさんです。最近明らかにアクティビティが低下しています。Pythonに飽きているというわけではありません。2月は旅行に行って1週末つぶしたり、別の週末は金曜日に飲みすぎたせいで週末をひどい二日酔いで潰したりしているせいです。おっさんなのでそもそも二日酔いになりやすい上に、ほんの少し疲れているだけで、この程度で酔わへんやろと言う量で泥酔するようになりましあt。最近、おっさんは酒は毒だなと言う思いを日に日に強くしています。今週末と来週末も長期海外出張で潰すので、また停滞しそうです。海外出張中に現地オサレカフェにMacBook持ち込んでやればいいだろとも思いますが、海外出張中って基本的には疲れ果ててるので、ホテルでだらけるしか僕にはもう選択肢はありません。それと、もう一つマジな話としては、前回のみんpyでも激しく躓いた関数とクラスのあたりに入っているので、どうしても理解に苦しんでいるので進捗が遅くならざるをえない局面というのはありますな。さて、今週末も張り切っていきましょう。

  • イテラブルとは、要素を順に取り出せるオブジェクト。リスト、タプル、辞書、文字列。セット・集合は順番がないからイテラブルではない。
  • イテレータは、要素を1個取り出すごとにどこまで取り出したかという情報を保持。
  • iter(iterable)でイテラブルからイテレータを作ることができる。
  • next(iterator)で要素を取り出す。1度目で1要素目、2度目で2要素目、3度目で3要素目が取り出される。全ての要素を取り出し終えると繰り返さず、その後はエラーになる。以下の様に3つしか要素がない場合4度目をやるとエラーになる。
>>> colors = ['red','yellow','green']
>>> color_iter=iter(colors)
>>> next(color_iter)
'red'
>>> next(color_iter)
'yellow'
>>> next(color_iter)
'green'
>>> next(color_iter)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
  • ジェネレータ関数を定義してイテレータを作成することも可能。ジェネレーター関数はyieldで値を返す。
def signal_generator():
    yield 'red'
    yield 'green'
    yield 'yellow'

signal = signal_generator()

print(next(signal))
print(next(signal))
print(next(signal))
print(next(signal))

for-inの利用も可能なので、全ての要素を一発で取り出すことも可能。

  • returnもyieldも関数の定義の中で値を返すのは同じ役割ではあるが、returnの場合は関数の処理を全て終了し値を返すが、yieldは関数の処理を一旦停止し値を返し、次のきっかけがあるまでは何の処理も値を返したりもしないので、メモリ処理量が少なく住む。
  • ジェネレーター関数を用いることで、文字通り数列を順に生成することができる。
def num_generator():
    n=0
    while True:
        num=n**2
        yield num
        n+=1

def do_something(num):
    return (num,num%2,num%3)

gen=num_generator()
for i in range(1,10):
    num=next(gen)
    result=do_something(num)
    print(result) #1回目〜10回目の計算結果が表示される

print(result) #10回目の計算結果が再度表示される

num=next(gen)
result=do_something(num)
print(result) #11回目の計算結果が表示される

print(result) #11回目の計算結果が再度表示される
  • 一番外の囲み記号を()とすることで、ジェネレータ式を書くことができる。リストにするとまとめて全部出せる。こんな感じ。
>>> square_gen = (i**2 for i in range(0,10))
>>> next(square_gen)
0
>>> next(square_gen)
1
>>> next(square_gen)
4
>>> next(square_gen)
9
>>> list(square_gen)
[16, 25, 36, 49, 64, 81]
  • ジェネレータは値を取り出すだけではなく、コルーチンという仕組みを使って外部から値を送信することも可能。
def coro():
    hello = yield "Hello" #yieldで値を返すだけではなく、sendされた値が代入される
    yield hello
    yield hello

c = coro()
print(next(c))         #Hello
print(c.send("World")) #World
print(next(c))         #World
print(next(c))         #StopIterationエラー

上の例だと、hello〜の行含めて3行しかないので、4回目のprintはエラーになる。

  • サブジェネレーターというか、ジェネレーターの中にジェネレーターをぶら下げることもできる。
def main_gen(n):
    yield from range(n,0,-1)
    yield from "abc"
    yield from [10,20,30]
    yield from sub_gen()

def sub_gen():
    yield 'x'
    yield 'y'
    yield 'z'

gen=main_gen(3)

print(next(gen)) #3
print(next(gen)) #2
print(next(gen)) #1
print(next(gen)) #a
print(next(gen)) #b
print(next(gen)) #c
print(next(gen)) #10
print(next(gen)) #20
print(next(gen)) #30
print(next(gen)) #x
print(next(gen)) #y
print(next(gen)) #z
print(next(gen)) #StopIteration

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

久しぶりですね。行きています。週末って意味では先々週は旅行に行ってたのと、旅行帰りの週に飲み会が続き、ヘロヘロになっているところに金曜の飲み会で泥酔し、二日酔いがひどく、週末はひたすら休養を取らざるをえないと言う実におっさんらしい情けない状況でした。只今、ウィークデーの夜のもくもく会の会場からお送りしています。尚おっさん、再来週は1週間海外出張なので、週末はほぼ移動する必要があり、ここで2週末はほぼ何もできないことが確定。そして出張中はどうせ疲れ果てて何もする気が起きないに決まっている。おっさんはおっさんなので、海外出張に行くと疲れ果てて、1日の予定が終わるともうホテルでくたばるしかすることがない。本当にダメだ。

  • 関数は数値や文字列と同じように扱うことができる。既定義の関数func1()がある場合、func2=func1と書けば(カッコは書かない)、以後func2()はfunc1()と同じ動きをする。
  • また、以下のように関数を関数の引数にすることもできる。
def calc(func,arg=1):
    price = func(arg)      #funcに代入されるchild(arg)もしくはadult(arg)が実行される。
    return price

def child(arg):
    return 400 * arg

def adult(arg):
    return 1200 * arg

age=input("年齢は?:")
#age = 8
num=input("何人?")
#num =3

if int(age)<12:
    print(f"{age}歳が{num}人なので{calc(child, num):,}円を払え")
else:
    print(f"{age}歳が{num}人なので{calc(adult, num):,}円を払え")

Python Tips: Python でクロージャを使いたい - Life with Python
上のサイトを参考にさせていただいたのだけれど、

def gen_circle_area_func(pi):
    """円の面積を求める関数を返す"""
    def circle_area(radius):
        # ここの pi は circle_area_func に渡された pi が使われる
        return pi * radius ** 2

    return circle_area

# 円周率を 3.14 として円の面積を計算する関数を作成
circle_area1 = gen_circle_area_func(3.14)
print(circle_area1(1))  # => 3.14
print(circle_area1(2))  # => 12.56

# 次は、円周率の精度をぐっと上げて
# 円周率 3.141592 で円の面積を計算する関数を作成
circle_area2 = gen_circle_area_func(3.141592)
print(circle_area2(1))  # => 3.141592
print(circle_area2(2))  # => 12.56638

僕にはcircle_area1(1)で半径1がgen_circle_area_func(3.14)の中のradiusに引き渡される理屈が全くわからない。本当に直感で理解できない。こういうもんなの?

  • ラムダ式は lambda 引数:処理内容 と書く、例えば
lambda x,y:x+y

であれば、xとyが渡されるとx+yが実行される。

(lambda x,y:x+y)(1,2)

の様に書くことでx+y=1+2=3が実行される。要は、

def func(x,y):
    return x+y

を1行で書いたようなもん。じゃあ、だから何、って感じなのだけれど、lambda式でよく利用されるのがソートへの応用。

list = [[10,4],[3,6],[4,6],[5,0],[4,9],[2,0]]
list.sort(key=lambda x:(x[0],x[1])) #1番目でソートし、2番めでソート
print(list)

上の事例の場合はx:(x[0],x[1])と書くことで、まずは1列目x[0]でソートした上で、2列めx[1]をソートする。とりあえず、x:x[1]と書くことで、xがリストを指し、x[i]がリストのi+1番目の要素と認識される模様。この辺の間隔がマジで意味がわかんねえよ。マヂ無理。

  • map関数もラムダ式を利用する事のできる関数。map(関数, イテラブル)と書く。単純言うと、map関数を使うと、イテラブルとして与えられたリスト等の中の要素の全てに関数で記述した処理を行うことができる。例えば数のリストに対してその数をすべて2倍にする等。

>>||
>>> nums = [4,3,8,6,2]
>>> nums2= list(map(lambda x:x*2,nums))
>>> print(nums2)
[8, 6, 16, 12, 4]
|