質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.92%

クラスを用いる理由がわかりません(Python)

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 4
  • VIEW 6,748

score 8

クラスの有用性

Pythonを触り始めて10日程度の初心者です。
この度ネット上で見つけたプログラム(おそらくこちらのサイトです:https://qiita.com/Kiro02/items/851a6b3f5cad37d77875)
にクラスが用いられていました。
オブジェクト指向がいまだに理解できていないこともあるのですが
このプログラムにおいてクラスが用いられている理由がよくわからないです。
以下がそのプログラムで指定したワードの画像検索結果を保存する、という動作をします。

プログラム

import os
import sys
import traceback
from mimetypes import guess_extension
from time import time, sleep
from urllib.request import urlopen, Request
from urllib.parse import quote
from bs4 import BeautifulSoup

MY_EMAIL_ADDR = ''

class Fetcher:
    def __init__(self, ua=''):
        self.ua = ua

    def fetch(self, url):
        req = Request(url, headers={'User-Agent': self.ua})
        try:
            with urlopen(req, timeout=3) as p:
                b_content = p.read()
                mime = p.getheader('Content-Type')
        except:
            sys.stderr.write('Error in fetching {}\n'.format(url))
            sys.stderr.write(traceback.format_exc())
            return None, None
        return b_content, mime

fetcher = Fetcher('任意のメールアドレス')

def fetch_and_save_img(word):
    data_dir = 'data/'
    if not os.path.exists(data_dir):
        os.makedirs(data_dir)

    for i, img_url in enumerate(img_url_list(word)):
        sleep(0.1)
        img, mime = fetcher.fetch(img_url)
        if not mime or not img:
            continue
        ext = guess_extension(mime.split(';')[0])
        if ext in ('.jpe', '.jpeg'):
            ext = '.jpg'
        if not ext:
            continue
        result_file = os.path.join(data_dir, "ajisai_" + str(i) + ext)
        with open(result_file, mode='wb') as f:
            f.write(img)
        print('fetched', img_url)


def img_url_list(word):
    """
    using yahoo (this script can't use at google)
    """
    url = 'http://image.search.yahoo.co.jp/search?n=60&p={}&search.x=1'.format(quote(word))
    byte_content, _ = fetcher.fetch(url)
    structured_page = BeautifulSoup(byte_content.decode('UTF-8'), 'html.parser')
    img_link_elems = structured_page.find_all('a', attrs={'target': 'imagewin'})
    img_urls = [e.get('href') for e in img_link_elems if e.get('href').startswith('http')]
    img_urls = list(set(img_urls))
    return img_urls

if __name__ == '__main__':
    word = sys.argv[1]
    fetch_and_save_img(word)

知りたいこと

オブジェクト指向を詳しく知るのは難しいと思うので、このプログラムにおいてなぜクラスを用いるのか、用いらずに関数のみを用いた場合どのような支障をきたすのか、教えていただきたいです。
よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • yomogian

    2017/11/09 16:22

    コメントありがとうございます。その点については認識しておりましたが、参照したWEBサイトのアドレスがわからず、見つけられないためやむなくこのような形になりました。著作権的問題回避のために一定期間が過ぎたら削除または編集しようと思っています。

    キャンセル

  • raccy

    2017/11/09 18:17

    http://karaage.hatenadiary.jp/entry/2017/08/23/073000 ここでしょうか?そうであれば、引用元を質問文に明記してください。

    キャンセル

  • KSwordOfHaste

    2017/11/09 18:28 編集

    「著作権的問題回避のために一定期間が過ぎたら削除または編集」=>この認識は改めていただきたいと思います。「著作権問題の危険があるなら最初から質問しない」、一旦質問したなら「めったなことでは質問を消さない」のが大事と思います。一旦質問文に書いたことは編集しても残ります(元の内容は全員に見える)し、内容を不用意に変更・削除すると情報共有の意味が失われます。

    キャンセル

回答 3

+6

このスクリプトの出典は以下でしょうか?

Tensorflowを再学習させて「〇〇判別機」を作る - Qiita

このプログラムにおいてなぜクラスを用いるのか、用いらずに関数のみを用いた場合どのような支障をきたすのか

なぜクラスを用いるのかはともかく、クラスを用いなかった場合どうなるかはぜひご自身で試してみることをお勧めします。
このクラスは実質的にメソッド fetch のみからなりますから、関数でもいいじゃんと感じることは自然だと思います。
fetchの引数 url は呼び出すたびに変わるものですから引数にするのが自然ですが、fetchの際の他のパラメータは半固定ですよね。それを一々引数で渡すのはわずらわしいし、かといってグローバル変数にすると、ua 等のパラメータを変えたいときに柔軟性が乏しくなります。なので、uaをインスタンス変数で持つことは、uaが半固定であることを反映して使い勝手のよい設計であると思いました。

また、fetch関数は汎用性の高い機能であるので、uaパラメータが固定であったり、グローバル変数で指定していると、他の環境に持っていくことが難しくなります。

さらに、ua以外のパラメータ(HTTPリクエストヘッダ)を指定したくなった場合も、現在のスクリプトを拡張して自然に、互換性を損なわずに、改良ができます。

以上のような理由から、作者はクラスにしたのではないかと思いますが、作った本人ではないので、意図の部分はあくまで推測です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/13 18:09

    おそらくそちらの出店で間違いないです。
    わかりやすい解説ありがとうございました。
    理解するのには時間がかかると思いますが、これから頑張ってみたいと思います。

    キャンセル

checkベストアンサー

+4

このプログラムにおいてクラスが用いられている理由がよくわからないです

ご質問はごもっともで、自然な疑問です。
そもそも現状では、クラスじゃなく関数だけでも、
「支障をきたす」ほどではないと思います。

じゃあ、オブジェクト指向(OO)を使う意味は何なの、という疑問が残るでしょう。
難しくなりすぎないよう、使い分けの基準だけごく簡単に示します。


手続き型とオブジェクト指向

小規模であれば、手続き型の方が簡単に早く作れますが、
大規模になると、オブジェクト指向の方が保守が楽です。

多少作成コストが高くなっても、抽象化技法を使って、
変更コストを下げるのがオブジェクト指向の目的です。

これは「うさぎとカメ」の話で、オブジェクト指向は、
後でシステムを変更したいときにこそ真価を発揮します。

じっさい今は、(クラス)ライブラリやフレームワークが、
OOで書かれることが多いことが、長期運用に向くことを実証しています。

なぜ変えやすいかというと、「クラス」が変更しやすい仕組みだからです。
ただし、自動的にはそうならず、変更を意識して組む必要があります。

たとえば、「単一責任の原則」という設計原則があって、
クラスを責務(変更理由)の単位にした設計にすれば、
変更時に変更部分がクラスに局所化されているので、変更しやすいです。


関数型とオブジェクト指向

Pythonは関数型とOOと両方のパラダイムで書けます。(RubyやJSもそう)
関数型とOOは、どちらも難解なので、やはり要点だけ説明しますね。

この両者は、手続き型よりもコードを抽象化して、宣言性を高める手法で、
処理が中心なのが関数型で、データ(型)が中心なのがOOです。

そして、参照透過によって抽象化するのが関数型、
カプセル化によって抽象化するのがオブジェクト指向、という違いがあります。

具体的にコード上では、(再代入禁止し)引数で受け渡すのが関数型、
インスタンス変数で状態を持つのがOO、という違いが見られます。

さて、どう使い分けるかといえば、処理を多様化したいのか、
データを多様化したいのか、という目的の違いによります。

扱うデータ型が単調で処理を多様化したいなら、関数型の方が向きます。

たとえば文字列処理や言語処理系など記号処理は、
基本的に入出力の連結を繰り返す構造にしやすいので、
関数型の方が簡潔に書けます。WebもHTMLが基本的に文字列なので、
関数型の方がシンプルになる場合が結構あると思います。

Python、Ruby、JavaScriptが、関数型の性格を持つのも、
偶然というよりは、もともとWebに向いてるからでしょう。

逆にデータ型が多様で状態遷移も複雑な場合、OOが向きます。
GUIのソフトやゲームは、たいてい状態の塊なので、OOが向いています。
じっさい、C#やJavaのようなOO中心の言語がよく用いられます。


プログラムで指定したワードの画像検索結果を保存する

さて最後に、最初の話題に戻り、今までの考察を応用してみましょう。

まず、一時的に使うだけで拡張予定がないなら、
手続き型で十分です。小規模なら一番楽で早いです。

拡張予定があり、ワードの文字列処理を多様化する、
たとえば、類似表現も提示する、欠けている語を補完する、
また検索や画像の処理方法を多様化する、たとえば、
検索結果(の文字部分)で再度検索する部分を作る、
画像を縮小したり加工する……、など処理の多様化には関数型が向きます。

あるいは、画像の分類を増やす、複数の検索サービスやAPIを使う、
画像だけでなく文章、動画、音声なども探す、さらに拡張子で変える、
また分類タグなどメタデータを持たせ、それで保存の仕方などを変える、
容量節約重視などの検索モードを付ける、GUIを付ける……、
などデータと状態の多様化をする方向で拡張するなら、OOが向きます。

……もし、したいことに両方の要素があったら?
関数型とOOを併用しても良いのです。とくにPythonやRubyは併用しやすい。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/13 18:09

    わかりやすい解説ありがとうございました。
    理解するのには時間がかかると思いますが、これから頑張ってみたいと思います。

    キャンセル

0

オブジェクト指向を使うことによって、コードを読む人の理解を助けることができます。
今のコードの場合、Fetcherというクラス名はfetchを行うためのメソッドを持つと連想されることができます。
このクラスにはもっといろいろと行うことを付け足すことができるはずです。
例えば、.update_ua(new_ua)というメソッドでどこかでアドレスを変更することなどが考えられます。

プログラミングでは同等の機能をいくつもの方法で実装できます。
素朴に関数として実装された場合と比べると、ockeghemさんが指摘されているようにuaという属性に自由度をもたせつつ、グローバル変数の空間を汚さないことが実現できます。
同じ目的でも、今回のクラスでの実装以外にクロージャ(関数を生成する関数)を用いることもできます。
以下にコードを示します。
この場合、一度与えたuaを今後変更することは自然ではなくなります。
それと引き換えに、fetcher.fetch(url)の代わりにfetch(url)とコードが少しスッキリします。

実装の方法が数多ある中で、適切な実装をすることでコードは読みやすくなり、バグりにくくなります。
プログラミングする際には入力に対して正しい出力を出すことだけが重要というわけではありません。
同じコードが他のところで再利用されたり、長い間変化しながら使用され続ける可能性をも考えることで、オブジェクト指向を用いることの必要性について評価することができるかと思います。

def generate_fetch(ua):
    def fetch(url):
        req = Request(url, headers={'User-Agent': ua})
        try:
            with urlopen(req, timeout=3) as p:
                b_content = p.read()
                mime = p.getheader('Content-Type')
        except:
            sys.stderr.write('Error in fetching {}\n'.format(url))
            sys.stderr.write(traceback.format_exc())
            return None, None
        return b_content, mime
    return fetch

fetch = generate_fetch('任意のメールアドレス')
byte_content, _ = fetch(url)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/13 18:09

    わかりやすい解説ありがとうございました。
    理解するのには時間がかかると思いますが、これから頑張ってみたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る