山pの楽しいお勉強生活

勉強の成果を垂れ流していきます

SQLAlchemyでAUTO_INCREMENTされた値を取得したい

結論

  • addした後にflushすると、addしたオブジェクトに設定されている。
    • 【未確認】exuecuteで実行する場合には返り値から取得できる?(参考

環境

  • MySQL 5.7(Cloud SQL
  • Python 3.6.6
  • PyMySQL 0.8.1
  • SQLAlchemy 1.2.0

コード

from sqlalchemy import Column
from sqlalchemy.dialects.mysql import INTEGER,TEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class tablea(Base):
    """テーブルの定義"""
    __tablename__ = 'tablea'

    id = Column(INTEGER, primary_key=True)
    id2 = Column(INTEGER, primary_key=True, autoincrement=True)
    name = Column(TEXT)

if __name__ == "__main__":
    # MySQLに接続。
    url = 'mysql+pymysql://user:password@hostname/dbname?charset=utf8'
    engine = create_engine(url, echo=True)

    engine.execute('DROP TABLE IF EXISTS {}'.format("tablea"))

    # テーブル作成
    Base.metadata.create_all(engine)

    # セッションの作成
    Session = sessionmaker(bind=engine)
    session = Session()

    tablea1 = tablea(id=100, name='太郎')
    tablea2 = tablea(id=200, name='次郎')
    session.add(tablea1)
    session.add(tablea2)

    # addしただけではDBに反映されない
    # autoincrementされるid2についても値はNone
    print('add後のid2の値')
    print(tablea1.id2) # None
    print(tablea2.id2) # None
    session.flush()
    print('flush後のid2の値')
    print(tablea1.id2) # 1
    print(tablea2.id2) # 2

    # 全件出力
    print("全件出力")
    for row in session.query(tablea).all():
        print(row.id, row.id2, row.name)

#    session.commit() # commitしなければrollbackされる

SQLAlchemyでAttributeError

経緯とか

  • 調査依頼を受けて調べてみた結果を記載。
  • どこかの誰かは助かるかも的な奴。

調査依頼の内容

  • SQLAlchemyでselectしようとしたら変な例外で落ちた。
  • SQLを実行する前に落ちている。
  • よくわからないオブジェクトでよくわからない例外が発生している。

概要

  • 変なオブジェクトを挿入しようとしたので例外が発生。
  • なんで変なオブジェクト挿入しようとしたのかというと、autoincrementされた値を取得したいため。

詳細

  • insertしようとして、intのカラムにオブジェクトを設定する。
  • オブジェクトを設定した際には型チェックしてくれないのでそのまま設定される。
  • addしただけではDBに挿入されず、次のフラッシュでデータベースに反映されるため、例外は発生しない。
  • selectを行う際にフラッシュの処理が走る。
  • そこでaddを実際にinsertを行うための前処理?が走る。
  • 設定された値の文字コードを変換する処理が走る。
  • 「設定された値 = オブジェクト」なので、変換できない。
  • 例外発生。

環境

  • Windows10
  • MySQL 5.7(Cloud SQL
  • Python 3.6.6
  • PyMySQL 0.8.1
  • SQLAlchemy 1.2.0

例外

Traceback (most recent call last):
  File "test.py", line 55, in <module>
    for row in session.query(tablea).all():
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\query.py", line 2726, in all
    return list(self)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\query.py", line 2877, in __iter__
    self.session._autoflush()
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\session.py", line 1428, in _autoflush
    self.flush()
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\session.py", line 2237, in flush
    self._flush(objects)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\session.py", line 2363, in _flush
    transaction.rollback(_capture_exception=True)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\util\langhelpers.py", line 66, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\util\compat.py", line 187, in reraise
    raise value
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\session.py", line 2327, in _flush
    flush_context.execute()
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 391, in execute
    rec.execute(self)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 556, in execute
    uow
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\persistence.py", line 181, in save_obj
    mapper, table, insert)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\orm\persistence.py", line 866, in _emit_insert_statements
    execute(statement, params)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\engine\base.py", line 948, in execute
    return meth(self, multiparams, params)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\sql\elements.py", line 269, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\engine\base.py", line 1060, in _execute_clauseelement
    compiled_sql, distilled_params
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\engine\base.py", line 1200, in _execute_context
    context)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\engine\base.py", line 1416, in _handle_dbapi_exception
    util.reraise(*exc_info)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\util\compat.py", line 187, in reraise
    raise value
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\engine\base.py", line 1193, in _execute_context
    context)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\sqlalchemy\engine\default.py", line 507, in do_execute
    cursor.execute(statement, parameters)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\cursors.py", line 168, in execute
    query = self.mogrify(query, args)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\cursors.py", line 147, in mogrify
    query = query % self._escape_args(args, conn)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\cursors.py", line 127, in _escape_args
    return dict((key, conn.literal(val)) for (key, val) in args.items())
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\cursors.py", line 127, in <genexpr>
    return dict((key, conn.literal(val)) for (key, val) in args.items())
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\connections.py", line 846, in literal
    return self.escape(obj, self.encoders)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\connections.py", line 839, in escape
    return converters.escape_item(obj, self.charset, mapping=mapping)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\converters.py", line 27, in escape_item
    val = encoder(val, mapping)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\converters.py", line 118, in escape_unicode
    return u"'%s'" % _escape_unicode(value)
  File "C:\Users\username\AppData\Local\Programs\Python\Python36\lib\site-packages\pymysql\converters.py", line 73, in _escape_unicode
    return value.translate(_escape_table)
AttributeError: 'ResultProxy' object has no attribute 'translate'

再現コード

from sqlalchemy import Column
from sqlalchemy.dialects.mysql import INTEGER,TEXT
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class tablea(Base):
    """テーブルの定義"""
    __tablename__ = 'tablea'

    id = Column(INTEGER, primary_key=True, autoincrement=True)
    name = Column(TEXT)

class tableb(Base):
    """テーブルの定義"""
    __tablename__ = 'tableb'

    id = Column(INTEGER, primary_key=True, autoincrement=True)
    tablea_id = Column(INTEGER)

if __name__ == "__main__":
    # MySQLに接続。
    url = 'mysql+pymysql://user:password@hostname/dbname?charset=utf8'
    engine = create_engine(url, echo=True)

    engine.execute('DROP TABLE IF EXISTS {}'.format("tablea"))
    engine.execute('DROP TABLE IF EXISTS {}'.format("tableb"))

    # テーブル作成
    Base.metadata.create_all(engine)

    # セッションの作成
    Session = sessionmaker(bind=engine)
    session = Session()

    # autoincrementのidカラムには何も指定せずに挿入
    tablea1 = tablea(name='太郎')
    session.add(tablea1)

    # autoincrementされたidを取得するために、MySQLのlast_insert_idを使用する
    lastindex = session.execute("select last_insert_id() as id")

    # 取得したIDを別テーブルに挿入
    # ※取得したオブジェクトをそのまま設定しているが、設定時、add時共にエラーとならない
    tablea2 = tableb(tablea_id=lastindex)
#    tablea2 = tableb(tablea_id=10)
    session.add(tablea2)

    # 全件出力
    print("全件出力")
    # 全然関係ないselect投げる際に例外発生
    for row in session.query(tablea).all():
        print(row.id, row.name)

#    session.commit() # commitしなければrollbackされる

学んだこととか

  • 型が恋しい。
    • intのカラムにオブジェクト突っ込んだ時に例外として欲しい。
  • session.query()はflushするが、session.execute()はflushしない?
    • 上記のコードを正しく動かすために、「session.execute("select last_insert_id() as id").fetchone().id」とすると動作はするが、意図した値にならない。
      • 0が返ってくる。(本当は1が返ってきて欲しい)
    • もし、このコードのまま使うのであればflush()してからlast_inset_id()を実行する必要がある。
    • ただ、flush()すると、挿入時に使用したオブジェクトに値を設定してくれるので、わざわざlast_insertで取得する必要はない。
  • スタックトレースをよく見る。
    • SQLAlchemyを動作させるところから1時間位?かかってしまったがよくスタックトレースみたら、「ResultProxy」をエンコードしようとして落ちてる事、flush時に発生している事がわかるので、どこかで「ResultProxy」を値に突っ込んでいる事が原因とわかったはず。
    • あるあるだが、よく見ること大事。

WSLでgoogle-images-downloadを使用する

概要

  • WindowsのWSL上でgoogle-images-downloadを使用して、画像を集める

用語

前提

  • WSL上にPythonがインストールされていること
    • Python2でも3でも良いが3が推奨との事(私は3.5.2を利用)
  • Windows上にChrome最新版がインストールされていること
    • 最新版でない場合にはChromeDriver取得の際に、適切なバージョンを選択してください。

手順

  • WSL上にgoogle-images-downloadをclone
    • git clone https://github.com/hardikvasa/google-images-download.git
  • setup
    • cd google-images-download && sudo python setup.py install
    • 私の場合は python3 setup.py install で実行
  • 試す
    • googleimagesdownload --keywords "りんご"
  • 確認
    • downloads/りんご 配下に100枚弱のファイルがあればOK
  • webdriverを取得
  • 適当な所に解凍
  • Chromeを呼び出すための設定
    • chrome」固定で呼び出すようなので、PATH設定してエイリアスchromeを設定する。
      • Seleniumのオプションで指定できたと思うが、割愛。
    • export PATH=$PATH:"/mnt/c/Program Files (x86)/Google/Chrome/Application"
    • alias chrome='chrome.exe'
    • WSL上で chromeWindows上でChromeが起動したらOK
  • 実行
    • ChromeDriverのパスは適切なものに変更してください↓
googleimagesdownload --keywords "apple" --limit 1000 --chromedriver /mnt/c/work/github/google-images-download/chromedriver_win32/chromedriver.exe
  • 確認
    • downloads/apple 配下に1000枚弱のファイルがあればOK

感想

  • 最初、自力でスクレイピングするつもりだったので、簡単に取得できて凄い。
  • が、多分Google側も構成変更したりと対応するだろうから、定期的に実行するのであればリポジトリは常に最新にしておく必要はあると思われる。
  • Chromedriverの手動ダウンロードが面倒。
    • Chromeがバージョンアップするたび?にChromeDriverの更新が必要。
    • Javaでもあったけど、seleneで使用してたwebdriver_managerを組み込んだら喜ばれるのだろうか?
  • Mac環境で setup.py install したらうまく行かなかった。。。わからないままやっている部分が多いので、Pythonの環境設定は嫌な感じ。

Atomのmarkdown-tocでタイトルが日本語の場合に動かない場合の対処

はじめに

Markdown書いていると見出し(toc)が欲しくなる
↓
Atomのプラグイン探す
↓
あった
↓
日本語で正しく動かない

っといった感じでAtomプラグインmarkdown-toc」を使ってみたものの、日本語で書いた際に動かなくて困る人がまあまあいるらしい。

ので対応方法を書いてみる。

結論

markdown-tocをforkして対応してくれてる人がいるので↓のコマンドでインストールしてAtom再起動。

apm install https://github.com/Sorix/markdown-toc/

以下は蛇足です。

リンクが正しくされない詳細

  • # hoge」 は 「[hoge](#hoge)」 と変換される(正常)
  • 日本語の場合 「# ほげ」 は [ほげ](#)」 と変換される(異常)

原因

リポジトリ見てみると。

  • この問題に対するissueはいっぱい!pull requestもいくつか
    • 各国の人が各言語対応してくれと言っていて割とカオス
  • そんな中Sorixという人が、各言語まとめて指定してしたpull request作成してくれている
  • けど、、マージしてくれないっぽい。
    • 多分、作者がテストできないし、困ってないからから。
  • なので、SorixさんがForkして、修正している奴をインストールすればOK。

どんな修正?

  • 一発ドカンではなく不正な文字?(記号か?)だけを除去している
  • この文字リストがどこからきたのかさっぱりわからないが、日本語、中国語、韓国語、ドイツ語?などは除去されなくなるっぽい。
  • よって、 「# ほげ」 は 「[ほげ](#ほげ)」 と変換されて正しく動作するようになる。

疑問

アンカー名が日本語

実を言うと、 - [インストール][#インストール] のようにアンカー名を日本語で指定するのは、仕様上は正しくありません。正しく動くのはブラウザが賢く振る舞っているからです。

- [インストール][#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB] のように URL Encode した文字列を指定するのが正しい書き方 です。

連続する空白の扱い

  • このプラグインだとタイトルに連続する空白が含まれている場合には1つにまとめて「-」と置換する処理が入っているが、GitHubwikiだと連続した「--」として扱うらしいので、正しくリンクされない。
  • これはビューワの仕様による気がする。(Markdown → HTML の正式な仕様ってあるのかしら?)
  • レアケースなので特に問題はないか?
  • 具体例 : 「# hoge hoge」 は 「hoge-hoge」 と変換されるが、GitHubwikiだと 「hoge--hoge」 となる。

参考

Python3とAWS LambdaでTwitterの名前に天気を表示する

はじめに

数日前に話題になっていた「Twitterの名前を5分毎に東京の天気☼☂☃と連動させるサーバレスプログラムを書いたら色々知らないことが出てきた話」を読んで、Pythonのいい練習になりそう!って事で自分でやってみた話です。

数日経って元記事見てみたら、既に何人かやっている人いるみたいです。(同じくPython3の人もいる) が、まぁ、他より割りと詳しく書いているので、試してみたい人はどうぞ。

楽しいお題?をくれた山本一成さんに感謝します。

前提

  • Python3がインストールされている
  • Twitterのアカウントを持っている
  • AWSアカウントを持っている

OpenWeatherMapAPIのAPIキーを取得

  • OpenWeatherMapにアクセス → Sign Up
    • 既に登録済みの人はSign In
  • API keysでKeyを確認
  • 検索したい都市名を取得
    • ここのJSONから調べる
    • 面倒なら「tokyo」、「osaka」、「nagoya-shi」、「london」とか
  • ブラウザで試す
    • ↓のURLに都市名とAPI Keyを入れこんで確認
    • http://api.openweathermap.org/data/2.5/weather?q=${都市名}&appid=${API Key}

Python3からOpenWeatherMapAPIを叩く

  • requests というモジュールを使う例が多かったのでpipで入れる
    • 後でLambdaに乗せる時ために、「-t」オプションをつけてスクリプトと同じ場所にインストール
    • pip install requests -t .
  • コードを書いて実行
import requests
import json

SEARCH_CITY = "tokyo"
OPEN_WEATHER_MAP_API_KEY = "取得したAPI Key"
API_URL = "http://api.openweathermap.org/data/2.5/weather?q={city}&APPID={key}"

# URIスキーム
url = API_URL.format(city=SEARCH_CITY, key=OPEN_WEATHER_MAP_API_KEY)
r = requests.get(url)

# 結果はJSON形式なのでデコードする
data = json.loads(r.text)
print(data)

TwitterAPI Tokenを取得

Twitterで名前更新

  • twitter というモジュールを使う例が多かったのでpipで入れる
    • 後でLambdaに乗せる時ために、「-t」オプションをつけてスクリプトと同じ場所にインストール
    • pip install twitter -t .
  • コードを書いて実行
import twitter

# メモしたキーとアクセストークンを設定
auth = twitter.OAuth(consumer_key="Consumer Key (API Key)",
                     consumer_secret="Consumer Secret (API Secret)",
                     token="Access Token",
                     token_secret="Access Token Secret")

t = twitter.Twitter(auth=auth)
t.account.update_profile(name="山pです。")

ちなみに

  • Tweetする場合
    • t.statuses.update(status="ついーとするないよう")

天気を含めてTwitterの名前更新

import requests
import json
import twitter


SEARCH_CITY = "tokyo"
OPEN_WEATHER_MAP_API_KEY = "取得したAPI Key"
API_URL = "http://api.openweathermap.org/data/2.5/weather?q={city}&APPID={key}"

# https://openweathermap.org/weather-conditions
WEATHER_EMOJI_MAP = {
    "01d": "☀",  # clear sky
    "02d": "🌤",  # few clouds
    "03d": "☁",  # scattered clouds
    "04d": "⛅",  # broken clouds
    "09d": "🌧",  # shower rain
    "10d": "🌦",  # rain
    "11d": "🌩",  # thunderstorm
    "13d": "🌨",  # snow
    "50d": "🌁"  # mist
}


def get_weather_icon():

    # URIスキーム
    url = API_URL.format(city=SEARCH_CITY, key=OPEN_WEATHER_MAP_API_KEY)
    r = requests.get(url)
    # 結果はJSON形式なのでデコードする
    data = json.loads(r.text)
    return data["weather"][0]["icon"].replace("n", "d")


def get_weather_emoji():
    return WEATHER_EMOJI_MAP.get(get_weather_icon())


def update_twitter_screen_name(name):

    # メモしたキーとアクセストークンを設定
    auth = twitter.OAuth(consumer_key="Consumer Key (API Key)",
                        consumer_secret="Consumer Secret (API Secret)",
                        token="Access Token",
                        token_secret="Access Token Secret")

    t = twitter.Twitter(auth=auth)
    t.account.update_profile(name=name)


def main():
    emoji = get_weather_emoji()
    twitter_name = "山p" + emoji
    update_twitter_screen_name(twitter_name)


if __name__ == '__main__':
    main()
  • 天気を絵文字に変換するのがかなり面倒なので、ここでは種類が少ないiconを使って変換してます。
  • 正確に天気が欲しい場合には↓を見て変換すれば良いと思います。

Lambadaに乗せるために準備

コードを変更

  • mainではなく、eventとcontextを受け取るhandler関数を作成
    • 名前はなんでもいいけどとりあえず。
  • Lambdaで環境変数を設定できるので、コードからKeyなどを除去して環境変数から取得するようにする。
import requests
import json
import twitter
import os

SEARCH_CITY = os.environ["SEARCH_CITY"]
OPEN_WEATHER_MAP_API_KEY = os.environ["OPEN_WEATHER_MAP_API_KEY"]
TWITTER_CONSUMER_KEY = os.environ["TWITTER_CONSUMER_KEY"]
TWITTER_CONSUMER_SECRET = os.environ["TWITTER_CONSUMER_SECRET"]
TWITTER_TOKEN = os.environ["TWITTER_TOKEN"]
TWITTER_TOKEN_SECRET = os.environ["TWITTER_TOKEN_SECRET"]

API_URL = "http://api.openweathermap.org/data/2.5/weather?q={city}&APPID={key}"

# https://openweathermap.org/weather-conditions
WEATHER_EMOJI_MAP = {
    "01d": "☀",  # clear sky
    "02d": "🌤",  # few clouds
    "03d": "☁",  # scattered clouds
    "04d": "⛅",  # broken clouds
    "09d": "🌧",  # shower rain
    "10d": "🌦",  # rain
    "11d": "🌩",  # thunderstorm
    "13d": "🌨",  # snow
    "50d": "🌁"  # mist
}


def get_weather_icon():

    # URIスキーム
    url = API_URL.format(city=SEARCH_CITY, key=OPEN_WEATHER_MAP_API_KEY)
    r = requests.get(url)
    # 結果はJSON形式なのでデコードする
    data = json.loads(r.text)
    return data["weather"][0]["icon"].replace("n", "d")


def get_weather_emoji():
    return WEATHER_EMOJI_MAP.get(get_weather_icon())


def update_twitter_screen_name(name):
    # 取得したキーとアクセストークンを設定する
    auth = twitter.OAuth(consumer_key=TWITTER_CONSUMER_KEY,
                         consumer_secret=TWITTER_CONSUMER_SECRET,
                         token=TWITTER_TOKEN,
                         token_secret=TWITTER_TOKEN_SECRET)

    t = twitter.Twitter(auth=auth)
    t.account.update_profile(name=name)


def handler(event, context):
    emoji = get_weather_emoji()
    twitter_name = "山p" + emoji
    update_twitter_screen_name(twitter_name)

Zip圧縮

  • ここでわざわざライブラリをスクリプトと同じ場所に置いていることが生きてくる
  • フォルダ毎ではなくて、ファイルを直で圧縮
    • コマンド例 : zip -r upload.zip *

Lambadaに乗せる

参考

Python3でZipの圧縮解凍操作

前記事のPython3でファイル操作に続いて、ZIPの圧縮解凍。 自分が業務内でどうでもいいスクリプト作る際に見るためのメモです。 Python勉強中なので間違っていたり、もっとPythonらしく書ける部分があればコメント欄やTwitterでツッコミお願いします。

コード

Githubにもあげてます。(使用しているファイルも。)

結局、Windows10環境でpyminizipを使用することはできなかった。。。別のモジュール使えばいいのかな?

import os
import zipfile
import pyminizip

from datetime import datetime

d = datetime.now().strftime("%Y%m%d%H%M%S")
outputDir = "./output/" + d + "/"
os.mkdir(outputDir)

# --------------
# ZIP解凍
# --------------

# 基本形(とりあえず全部解凍)
with zipfile.ZipFile("./data/simple.zip", "r") as zf:
    zf.extractall(outputDir) # 引数を指定しないとカレントに出力

# パスワード付きzipを全部解凍
with zipfile.ZipFile("./data/password.zip", "r") as zf:
    zf.extractall(outputDir, pwd="password".encode("ascii")) # encodeしないとエラー

# ファイル指定して解凍する
with zipfile.ZipFile("./data/somefile.zip", "r") as zf:
    print(zf.namelist()) # namelistでファイル名確認

    # バイトで書き込むので「b」で開く
    with open(outputDir + "somefile.txt", "wb") as writeFile:
        writeFile.write(zf.read("somefile/file2.txt")) # readはバイトが返る

    # テキストで取得したい場合はopenで開いてデコード?
    # zipfile.open は文字コード指定できない
    with zf.open("somefile/file2.txt") as targetFile:
        for line in targetFile:
            print(line.decode("UTF-8").rstrip())

# --------------
# ZIP圧縮
# --------------

# 基本的な圧縮
with zipfile.ZipFile(outputDir + "compress1.zip", "w") as zf:
    # 第2引数がない場合は第1引数の通りに格納される。(この場合だと「/data/text1.txt」となる。)
    zf.write("./data/text1.txt", "text1.txt")
    zf.write("./data/text2.txt","nest/text2.txt")

# パスワード付きは標準ライブラリではできない
# pyminizipモジュールを使用する
# https://github.com/smihica/pyminizip
# ↓で入ればよいが入らない場合には下記参照
# pip install pyminizip
# Windows10の場合↓のエラーが出たので、下記の通り「Microsoft Visual C++ Build Tools」をインストール
#  Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
#  その後zlibが必要だと言われたが解決できず。↓が参考になるかも
#  https://stackoverflow.com/questions/42529239/
# Ubuntuの場合
#  sudo apt-get install zlib1g-dev

pyminizip.compress("./data/text1.txt", outputDir + "compress2.zip", "password", 5)

Python3でファイル操作

何番煎じだ的な感じですが、自分が業務内でどうでもいいスクリプト作る際に見るためのメモです。 Python勉強中なので間違っていたり、もっとPythonらしく書ける部分があればコメント欄やTwitterでツッコミお願いします。

コード

Githubにもあげてます。(使用しているファイルも。)

import os

from datetime import datetime

defaultTextPath = "./data/shift_jis-crlf.txt"
#defaultTextPath = "./data/utf8-lf.txt"

# 基本形(OSのデフォルト?文字コードで読む)
with open(defaultTextPath) as f:
    print(f.read()) # 一括で読み込む

# 行毎に読み込む
with open(defaultTextPath) as f:
    for line in f:
        print(line)

# 行毎に読み込む
# メモリに全部乗る事、改行が含まれる事に注意
with open(defaultTextPath) as f:
    for line in f.readlines():
        print(line.rstrip()) # 最後の改行も含まれるので注意。「rstrip()」で除去
        # 違いはよくわからないが、f.read().splitlines() がオススメされてた
        # https://stackoverflow.com/questions/15233340/getting-rid-of-n-when-using-readlines

# 行毎に読み込む
# 使うことあるのか?
with open(defaultTextPath) as f:
    line2 = f.readline() # 1行づつ読み込む(改行文字も含まれる)
    while line2:
        print(line2.rstrip())
        line2 = f.readline()

# 文字コードを指定する場合にはencoding
# https://docs.python.jp/3.5/library/functions.html#open
# 検索するとcodecsモジュールを使うなどと出てくるが「codecs」は古い!
# python 2でもio.openでpython 3と同じ動きとのこと)
with open("./data/euc-crlf.txt", "r", encoding="euc_jp") as f:
    print(f.read())

# ----------------------
# 書き込み
# ----------------------

d = datetime.now().strftime("%Y%m%d%H%M%S")
outputDir = "./output/" + d + "/"
os.mkdir(outputDir)

# 基本形(OSのデフォルト?文字コードで書き込み)
with open(outputDir + "fisrt.txt", "w") as writeFile:
    writeFile.write("あいうえお\nかきくけこ\nさしすせそ\n") # Windowsの場合改行コードも何故か\r\nで出力

# 1行ごとに書き込み(OSのデフォルト?文字コードで書き込み)
with open(outputDir + "second.txt", "w") as writeFile:
    # Windowsの場合改行コードも何故か\r\nで出力
    writeFile.write("あいうえお\n")
    writeFile.write("かきくけこ\n")
    writeFile.write("さしすせそ\n")

# 文字コードを指定して書き込み
with open(outputDir + "third.txt", "w", encoding="UTF-8") as writeFile:
    writeFile.write("あいうえお\nかきくけこ\nさしすせそ\n") # Windowsの場合でも改行コードは\nで出力

# --------------------
# 文字コード変換して出力
# --------------------

# 文字コード変換
with open("./data/euc-crlf.txt", "r", encoding="euc_jp") as f:
    with open(outputDir + "euc-utf8.txt", "w", encoding="UTF-8") as writeFile:
        writeFile.write(f.read())

# 文字コードと改行コード変換
with open("./data/utf8-crlf.txt", "r", encoding="UTF-8") as f:
    with open(outputDir + "utf8-shift_jis-lf.txt", "w", encoding="shift_jis") as writeFile:
        writeFile.write(f.read().replace("\r\n","\n"))

追記(2018/02/28) Twitterでツッコミを頂いたのでコードを修正しております。ありがとうございます!