ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

ABC 142: D問題の解説 (Python3)

問題:AtCoder Beginner Contest 142: D - Disjoint Set of Common Divisors

公式解説PDF:https://img.atcoder.jp/abc142/editorial.pdf

公式解説動画:AtCoder Beginner Contest 142 - YouTube

解説

要約すると、(gcd(A, B)を素因数分解したときの素因数の個数)+1が答え。


入力例2を考えます。

「420と660の正の公約数の中からいくつか約数を選んで、選んだやつのどの異なる2つの整数も互いに素となる最大の約数の個数は何か?」という問題になります。

420と660の正の公約数は、必ず最大公約数以下の値になります。よって約数のとりうる範囲は1 ~ 最大公約数になります。420と660 の最大公約数gcd(420, 660)は60です。

60の約数を列挙してみます。

1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60

このうち、どの2つの整数も互いに素となる約数の個数が最大となる選び方は、

1, 2, 3, 5 の4つです。

見やすく表にして整理すると、

60の約数 1 2 3 4 5 6 10 12 15 20 30 60
答え 1 2 3 5

たとえば15はなぜいけないのでしょうか?それは15を素因数分解すると 15 = 3^1 * 5^1 なので、15を選ぶと3と5が選べなくなってしまいます。 よって15を選ぶより3と5を選んだほうが多くを選べるということになります。

12や20なんかも同様の理由で選べません。

4はどうでしょうか?これは素因数分解すると 4 = 2^2になります。つまり4を選ぶと2を選べなくなります。この2を選ばないパターンの1 3 4 5は、どの2つの整数も互いに素となっているのでOKです。よって2の代わりに4を選ぶのはありですが、選ぶメリットは特にないですよね。素数しか選ばないというルールのほうが問題を簡単化できます。

ということで入力例2の場合では、gcd(420, 660)で最大公約数を求めて、その最大公約数を素因数分解したときの素因数を選べば、どの2つの整数も互いに素となります。あとついでに1も約数なのでこれも加えます。これが答えになります。


制約 A, B \leqq 10^{12}を考えます。Nがでかすぎるので、 O(N)の解法はTLEします。

この問題におけるNとは、AとBの最大公約数gcd(A, B)の値です。1~gcd(A, B)の範囲で素因数を数えることになります。gcd(A, B)の最大値は 10^{12}なので、 O(N)は無理です。

ですが実は、最大公約数Nが何かの整数pで割り切れるときは、 N = p * q と書けますので、 p \leqq \sqrt{N}となります(例: N=10^{12} のとき、 p=10^6 q=10^6 )。なのでNまで調べる必要はなく、 O(\sqrt{N})で解くことができます。

このあたりの考え方はエラトステネスの篩とかで素数を数える問題を知っていて慣れていれば、 O(\sqrt{N})で解けそうだなと当たりがつきます。

コーナーケース

  • 最大公約数が1のとき、1を出力して終了

実装

# -*- coding:utf-8 -*-
import sys
from fractions import gcd
from collections import Counter


def prime_factorization(n):
    """nを素因数分解する

    Return d(dict):
        素因数をキー、素因数の指数部を値とするディクショナリを返す

    Example:
        n=140 -> d = {2: 2, 5: 1, 7: 1}
    """
    d = Counter()
    m = 2
    while m*m <= n:
        while n%m == 0:
            n //= m
            d[m] += 1
        m = m + 1 if m == 2 else m + 2  # m=3からは、m+=2する(偶数は明らかに素数ではないので)

    if n > 1:
        d[n] += 1
    return d


def solve():
    A, B = list(map(int, sys.stdin.readline().split()))
    cd = gcd(A, B)

    if cd == 1:  # コーナーケース
        print(1)
        return

    # (素因数分解の素因数の個数)+1が答え
    prime_d = prime_factorization(cd)
    print(len(prime_d)+1)


if __name__ == "__main__":
    solve()

計算量は O(\sqrt{N})です 。

蛇足

なんか偉そうに「この問題に慣れていれば~」とか書きましたが、本番でTLEしてるんですよね。精進します。

「優れた発想はなぜゴミ箱に捨てられるのか?」読書メモ

優れた発想はなぜゴミ箱に捨てられるのか? - Amazon

キーワード

  • E4V (Eyes for Value)
    • Step1: 価値を創る
    • Step2: 価値を伝える
    • Step3: 実現への道のりを創る
  • TOC
    • Theory Of Constraints: 制約理論
    • ✕:現場のカイゼンを一つ一つ積み上げることが、企業全体の業績向上につながる
    • ◯:システム全体のパフォーマンスは制約で決まる
    • 律速原因のボトルネックを探す感じ?
  • 市場の教育
    • イノベーティブな商品は市場に理解されにくい。価値を正しく伝えて市場を教育する
    • 市場の教育を考える6つの質問
      1. 何と価値を比べるのか?
      2. 何と価格を比べるのか?
      3. お客様が最も購入したいと思う場面は?
      4. 最も適した流通チャネルは?
      5. 参入障壁をどうやって創るか?
      6. 誰と組めばビジネスモデルを強固にできるか?
  • イノベーションの価値
  • 3つの目
    • WOW!と言われるための価値を見つける3つの道具
      1. 顧客の目
      2. 市場の目
      3. 商品の目
  • 顧客の目
    • マイナス・マイナスの顧客の目
    • 顧客にとっての大きなマイナスをなくせば、大きなWOW!になる
    • 4つの質問
      1. 顧客は誰ですか?
      2. 顧客にとっての重要なマイナスは何ですか?
      3. 取り除くとWOW!と言われる重大なマイナスは何ですか?
      4. それは今までのどんな限界を取り除きますか?
  • 市場の目
    • プラス・プラスの市場の目
    • 普通の人が諦めている要望の中に、多くの人が本当は望んでいる要望が隠されている
    • 4つの質問
      1. ある要望を満たすために、あらゆる苦労を厭わない顧客グループはありますか?
      2. 彼らはどんなプラスの要望を満たそうとしていますか?
      3. その要望を満たすと大きなプラスの市場になると思われるのはどれでしょうか?
      4. それは今までのどんな限界を取り除きますか?
  • 商品の目
    • 振り切って考える商品の目
    • 顧客の目、市場の目の順番に考えることで、十分に顧客と市場の視点は考えられている。最後に商品価値を考える
    • 4つの質問
      1. 商品が構成されているパラメータは何ですか?
      2. それらのパラメータを大きく上下に変化させたり、なくしたり追加すると、どんな価値をもたらしそうですか?
      3. 大きなWOW!をもたらしそうなものはどれですか?
      4. それは今までのどんな限界を取り除きますか?
  • 「今までは〇〇○だと思ってませんでしたか?でもこれからは違います」
    • WOW!の決めゼリフ
  • WOW!カタログ
    • 既存商品のカタログをベースに、あたかもすでにWOW!と言われた新商品があるという想定でカタログを創っていく
  • WOW!ロードマップ
    • 10年後のWOW!カタログを創る
    • バックキャストでWOW!を積み重ねていく
      • 車種ごとに構想・開発を繰り返すのではなく、のちに製品化する車種のことまで視野に入れて、一括で構想・開発を進めていくマツダの一括企画
  • バックキャスト
    • バックキャストとは、未来のある時点に目標を設定し、そこから振り返って現在やるべきことを考える方法
  • ロマンとソロバン
    • マツダの言葉
    • ロマン:従業員の「やりたい」や「働き(続け)たい」というロマン的価値
    • ソロバン:経営・財務的なソロバン的価値
  • WOW!チーム
    • 会社の様々な部門に参加してもらう。
  • 変化の4象限
    • お客様の変化を検証する変化の4象限
      • 「買う買わない」と「プラスマイナス」の4象限
    • 4つの質問
      1. 買うことによって得られるプラスは?
      2. 買うことによって生じるマイナスは?
      3. 買わないことによって得られるプラスは?
      4. 買わないことによって生じるマイナスは?
  • ビジネスの可能性
    • ビジネスの可能性について評価する8項目
      1. 市場の大きさはどのくらいか?
      2. 利益はどれだけ見込めるのか?
      3. 模倣はどのくらい難しいのか?
      4. 競合が追いつくにはどのくらいかかりそうか?
      5. 投資と運用のコストはどのくらいか?
      6. 実現を難しくしている社内の制約は何か?
      7. 投資回収にどのくらいかかりそうか?
      8. 売上、利益、シェアのプランは?
  • 成功する根拠
    • 人は数字で納得するのではなく、その数字が示された根拠で納得する
    • 仮定を素早く検証し、修正していくことが成功のカギ
  • ミステリー分析
    • 失敗を学びに変える7つの分析
      1. 問題は何ですか?
      2. もともと何が起こると期待していましたか?
      3. それを起こすために、どんなことをしましたか?
      4. 実際に起きてしまったことはなんですか?
      5. 何が原因で思わぬ結果を引き起こしたのでしょうか?
      6. この原因を解消するうまい方法はありますか?
      7. この解消策をおこなうと、期待していたことが起きそうですか?
    • 思ったより良くなった・悪くなったでも、両方分析が必要
    • ワイガヤでやると良い
  • ODSC
    • Objectives, Deliverables, Success Criteria
      • 目的、成果物、成功基準
    • 3つの質問
      • 目的は何ですか?
      • 成果物は何ですか?
      • 成功基準は何ですか?
  • バックキャスト工程表
    • やるべきタスクを後ろからたどって段取りを考える
    • 段取り八分
      1. その前にやることは何ですか?
      2. 本当にそれだけですか?
      3. 〇〇をしたら、△△ができるんですね?
  • 断れない提案
    • UnRefusable Offer: URO
    • 相手がNOと言う理由をあらかじめ考え、それをことごとく潰していくことで相手にNOと言わせない状況を作ること
  • 相手の抵抗<抵抗の6階層>
    1. 取り組もうとしている問題が問題とは思えない
      • 問題を明らかにする
    2. 解決しようとしている方向性に合意しない
      • 「望ましい状況」を定義し、相手にそれの合意を得る
    3. その解決策で、問題が解決するとは思えない
      • 「望ましい状況」を提案する解決策がカバーしているかを検討する
    4. その解決策を実行すると、ネガティブな問題が発生する
      • リスクは隠さず説明する。「契約解消は(条件付きで)いつでも可能」など
    5. その解決策を実行するのは、障害があるので現実的ではない
      • 相手には相談という形で、相手の背後にいる意思決定者には提案する形をとる
    6. 知らないことに対する恐れがある
      • 相手にとってメリットが大きく、リスクが小さい第一歩を用意する
  • 詳しい取り組み
    • リビングの窓
    • 着物
    • その他もろもろ

ワークシート

変化の4象限

プラス マイナス
買う
買わない

成功の根拠を明らかにする仮定の検証

事業性のチェック項目 想定 その理由は? 検証(はい、いいえ、新たな発見)
①市場の大きさはどのくらいか?
②利益はどれだけ見込めるのか?
③模倣はどのくらい難しいのか?
④競合が追いつくにはどのくらいかかりそうか?
⑤投資と運用のコストはどのくらいか?
⑥実現を難しくしている社内の制約は何か?
⑦投資回収にどのくらいかかりそうか?
⑧売上、利益、シェアのプランは?

抵抗の6階層で考える断れない提案

相手の抵抗は何か 抵抗を乗り越えるために何を準備すべきか
①取り組もうとしている問題が問題とは思えない
②解決しようとしている方向性に合意しない
③その解決策で、問題が解決するとは思えない
④その解決策を実行すると、ネガティブな問題が発生する
⑤その解決策を実行するのは、障害があるので現実的ではない
⑥知らないことに対する恐れがある

感想

TOCというイノベーションプロセスの本。イノベーションを生み出すための方法論。会社の人におすすめされて読みました。技術的な本を読むことはあっても、こういうビジネス的な本を読む機会はあまりないので、新鮮でした。製品開発するのでこういうビジネスやマネジメントにフォーカスした本も読まないといけないのかもしれない。年齢も年齢ですし。

今回本を読むにあたって、

  • キーワードをメモする
  • そのキーワードについて口頭で説明できるようになれば、その本を理解したと同然である

というスタンスで読んでみました。

本を読んでいるときはキーワードをメモして、その後再度キーワードが書かれている箇所を読み直しまとめるという作業をしました。効率が良いとは言えないかも。全体で6時間くらいかかった?思ったより時間かかってますね。

f:id:takeg:20190926202100j:plain
読書中のメモ

AttributeError: module 'pip' has no attribute 'get_installed_distributions' のエラー対応

Google Colabでインストールされているパッケージを確認しようとしました。以下はPythonのコードです。

installed_packages = pip.get_installed_distributions()

すると以下のようなエラーが発生しました。

AttributeError: module 'pip' has no attribute 'get_installed_distributions'

エラーでググったら、まんまのエラー対処法が。

python – pip 10.0.0バージョンのpip.get_installed_distributions()に代わる機能はありますか? - コードログ

以下でインポートできるようになります。

try:
    from pip._internal.utils.misc import get_installed_distributions
except ImportError:  # pip<10
    from pip import get_installed_distributions

pipのバージョンが10.0.0からget_installed_distributions()関数が移動したっぽいので、あるいは、

if pip.__version__ >= "10.0.0":
    from pip._internal.utils.misc import get_installed_distributions
else:
    from pip import get_installed_distributions

でもいけました。

で、インストールされているパッケージの確認は以下でできます。

installed_packages = get_installed_distributions()

installed_packages_list = sorted(["%s==%s" % (i.key, i.version)
     for i in installed_packages])

print(installed_packages_list)
# ['absl-py==0.8.0', 'alabaster==0.7.12', 'albumentations==0.1.12', 'altair==3.2.0', 'astor==0.8.0', 'astropy==3.0.5', 'atari-py==0.1.15', 'atomicwrites==1.3.0', 'attrs==19.1.0', ...

参考

競技プログラミング解説記事まとめ

最後に編集した日:2023/01/18

ABC C問題

ABC D問題

ABC E問題

ABC F問題

ARC

Educational DP Contest

Other Contest

AOJ

ABC 115: D問題の解説 (Python3)

問題:AtCoder Beginner Contest 115: D - Christmas

公式解説PDF:https://img.atcoder.jp/abc115/editorial.pdf

公式解説動画:なし

有志解説動画:【競技プログラミング】AtCoder Beginner Contest 115 D問題をJuliaで解く - YouTube

解説

考察問題であり、数学です。

方針としては、「レベルiバーガーの下からX層にあるパティPの総数」を f(i, X) とし、その漸化式を導くことで問題を解きます。


とりあえず各レベルバーガーを書いてみます。わかりやすさのためパティを赤色にしています。

レベル バーガー 層の総数 パティの総数
0 P 1 1
1 BPPPB 5 3
2 BBPPPBPBPPPBB 13 7
3 BBBPPPBPBPPPBBPBBPPPBPBPPPBBB 29 15

レベル L バーガー (L≥1) とは、バン 1 枚、レベル L−1 バーガー、パティ 1 枚、レベル L−1 バーガー、バン 1 枚、をこの順に下から積み重ねたものである。という性質が重要で、この性質から、

  • レベル2バーガーにはレベル1バーガーが2つ存在している
  • レベル3バーガーにはレベル2バーガーが2つ存在している
  • レベル4バーガーにはレベル3バーガーが2つ存在している
  • ...

という性質を頭にいれておきます。


レベルNバーガーの層(パティとバン)の総数を a_iとします。 a_iはいくらでしょうか?

レベルNバーガーは、「レベルN-1バーガーを2つ+ 2枚のバン + 1枚のパティ」でできます。よって、

 a_i = 2 a_{i-1} + 3

です。 a_iは奇数であることもわかります。


レベルNバーガーのパティの総数を p_iとします。 p_iはいくらでしょうか?

レベルNバーガーは、「レベルN-1バーガーのパティの数*2 + 1枚のパティ」でできます。よって、

 p_i = 2 p_{i-1} + 1

です。


今、解きたい問題は「ルンルンがレベルNバーガーのX層を下から食べる。食べたパティの枚数はなにか?」です。これは言い換えれば「レベルiバーガーの下からX層にあるパティPの総数」を求めることと同値です。

「レベルiバーガーの下からX層にあるパティPの総数」を f(i, X) とします。

例を列挙すると、

  •  f(0, 0) = 0
  •  f(0, 1) = 1
  •  f(1, 0) = 0
  •  f(1, 1) = 0
  •  f(1, 2) = 1
  •  f(1, 3) = 2
  •  f(1, 4) = 3
  •  f(1, 5) = 3
  • ...

です。


さて、戦略としては f(i,X)の漸化式を立てることです。

レベルNバーガーはその性質上、真ん中にP(パティ)がくることがわかっています。

また、真ん中より右側には「レベルN-1バーガー + バン」ということがわかります。

また、真ん中より左側には「レベルN-1バーガー + バン」ということがわかります。

ということで、3つの場合分けをすればなんかいけそうだとあたりがつきます。

  • Xが真ん中より右側のとき
  • Xが真ん中のとき
  • Xが真ん中より左側のとき

の3パターンで漸化式を立てます。

 a_iは必ず奇数より、真ん中のPは右から \frac{a_i + 1} {2}の位置にあります。

わかりやすくするため、真ん中の位置を median_i = \frac{a_i + 1} {2}とします。

すると漸化式は、

  • i = 0のとき

{} $$ f(0, X) = \left\{ \begin{array}{} 1 & ( X \geqq 1 ) \\ 0 & ( X < 1 ) \end{array} \right. $$

  • i >= 1 のとき

{} $$ f(i, X) = \left\{ \begin{array}{} f(i-1, X-1) & ( X < median_i ) (Xが右側) \\ p_{i-1} + 1 & ( X = median_i ) (Xが真ん中) \\ p_{i-1} + 1 + f(i-1, X-median_i) & ( X > median_i ) (Xが左側) \end{array} \right. $$

となります。

なぜこうなるのか?というのはまあ、以下のような例をお絵かきしてみて法則性を見つけ出します。


f:id:takeg:20190921151530p:plain
Xが右側のとき


f:id:takeg:20190921151532p:plain
Xが真ん中のとき


f:id:takeg:20190921151830p:plain
Xが左側のとき

あとはこれを解くプログラムを書きます。


ちなみに、

  •  a_i =2^{i+2} - 3
  •  p_i = 2^{i+1} - 1

と一般項を変形できるらしいので、こちらを使うことで高速化できます。

コーナーケース

  • 特になし

実装1(再帰関数)

# -*- coding:utf-8 -*-
import sys
sys.setrecursionlimit(10000)  # 再帰関数のRecursionError対策


def solve():
    N, X = list(map(int, sys.stdin.readline().split()))

    As = [1]  # レベルiバーガーの厚さ(層の総数)(必ず奇数)
    Ps = [1]  # レベルiバーガーのパティの総数

    for i in range(N):
        As.append(As[i]*2 + 3)  # レベルが1上がると、総数は2倍+3になる
        Ps.append(Ps[i]*2 + 1)  # レベルが1上がると、パティの数は2倍+1になる

    def f(n, x):
        """レベルnバーガーの下からx層食べたときの、食べたパティの総数"""
        if n == 0:
            return 0 if x <= 0 else 1

        median = (As[n]+1)//2

        if x < median:
            return f(n-1, x-1)
        elif x == median:
            return Ps[n-1] + 1
        elif x > median:
            return Ps[n-1] + 1 + f(n-1, x-median)

    ans = f(N, X)

    print(ans)


if __name__ == "__main__":
    solve()

計算量は O(N)です 。

実装2(DP)

漸化式を解くといったらDPなので、DPで解こうとしたら入力例3でMemoryErrorが起きました。Xの取りうる値がでかすぎてリストの作成でコケます。以下のプログラムではACできません。

# -*- coding:utf-8 -*-
import sys


def solve():
    N, X = list(map(int, sys.stdin.readline().split()))

    As = [1]  # レベルiバーガーの厚さ(層の総数)(必ず奇数)
    Ps = [1]  # レベルiバーガーのパティの総数

    for i in range(N):
        As.append(As[i]*2 + 3)  # レベルが1上がると、総数は2倍+3になる
        Ps.append(Ps[i]*2 + 1)  # レベルが1上がると、パティの数は2倍+1になる

    # dp[i][x] := レベルiバーガーの下からx層に含まれているパティの総数
    dp = [[0]*(X+1) for _ in range(N+1)]
    dp[0][0] = 0
    for i in range(1, X+1):
        dp[0][i] = 1

    # 漸化式を解く
    for i in range(1, N+1):
        median = (As[i]+1)//2
        for x in range(X+1):
            if x < median:
                dp[i][x] = dp[i-1][x-1]
            elif x == median:
                dp[i][x] = Ps[i-1] + 1
            else:
                dp[i][x] = Ps[i-1] + 1 + dp[i-1][x-median]

    print(dp[N][X])



if __name__ == "__main__":
    solve()

「テスト駆動Python」写経 CHAPTER1

www.amazon.co.jp

1 はじめてのpytest

pytestを使ったテスト駆動開発についての本です。

pytestを実行する

test_one.py

def test_passing():
    assert (1, 2, 3) == (1, 2, 3)

テストを実行します。

$ pytest test_one.py
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 1 item                                                                                                                                                                                                                      

test_one.py .                                                                                                                                                                                                                  [100%] 

===================================================================================================== 1 passed in 0.01 seconds ====================================================================================================== 

より詳細に知りたい場合は、-vオプションを追加します。

$ pytest -v test_one.py
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\pytestx\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 1 item                                                                                                                                                                                                                      

test_one.py::test_passing PASSED                                                                                                                                                                                               [100%] 

===================================================================================================== 1 passed in 0.01 seconds ====================================================================================================== 

test_two.py

def test_falling():
    assert(1,2,3) == (3,2,1)

テストを実行します。

$ pytest -v test_two.py
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\pytestx\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 1 item                                                                                                                                                                                                                      

test_two.py::test_falling FAILED                                                                                                                                                                                               [100%] 

============================================================================================================= FAILURES ============================================================================================================== 
___________________________________________________________________________________________________________ test_falling ____________________________________________________________________________________________________________ 

    def test_falling():
>       assert(1,2,3) == (3,2,1)
E       assert (1, 2, 3) == (3, 2, 1)
E         At index 0 diff: 1 != 3
E         Full diff:
E         - (1, 2, 3)
E         ?  ^     ^
E         + (3, 2, 1)
E         ?  ^     ^

test_two.py:2: AssertionError
===================================================================================================== 1 failed in 0.04 seconds ====================================================================================================== 

pytestはファイルやディレクトリを指定しない場合、現在のディレクトリとそのサブディレクトリでテストを検索し実行します。pytestが実行されるのはtest_で始まるファイルと、_testで終わるファイル。

# ファイルを指定してテスト実行
$ pytest -v test_one.py

# 複数のファイルを指定してテスト実行
$ pytest -v tasks/test_three.py tasks/test_four.py

# ディレクトリを指定してテスト実行
$ pytest -v tasks

# 現在のディレクトリ以下すべてテスト実行
$ pytest -v

pytestのテストディスカバリ

pytestの実行するテストを検索する部分を「テストディスカバリ(test discovery)」といいます。

pytestの命名規則に準拠した名前がついたものは、pytestのテストが実行されます。その命名規則は以下の3つ。

  1. 「test_*.py」または「*_test.py」というファイル名
  2. 「test_*」という名前のメソッドや関数
  3. 「Test*」という名前のクラス

test_one.pyという名前のファイルに、「hoge()」という名前の関数があってもそれはテストされない。

このテストディスカバリルールは変更することが可能。

テストを1つだけ実行する

ファイルの中の関数を指定して、テストしたい関数のみをテストすることができます。

$ pytest -v tasks/test_one.py::test_passing

pytestのオプション

-h, --help

$ pytest --help

pytestのヘルプです。

--collect-only

$ pytest --collect-only
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 6 items                                                                                                                                                                                                                     
<Module test_one.py>
  <Function test_passing>
<Module test_two.py>
  <Function test_falling>
<Module tasks/test_four.py>
  <Function test_asdict>
  <Function test_replace>
<Module tasks/test_three.py>
  <Function test_defaults>
  <Function test_member_access>

=================================================================================================== no tests ran in 0.02 seconds ==================================================================================================== 

実行されるテストを表示します。テスト実行前にチェックするのに役立ちます。

-k EXPRESSION

$ pytest -k "asdict or defaults" --collect-only

-k オプションは、実行するテスト関数を、式を使って検索できるようにするフィルタ。

# asdictまたはdefaultsと名前がつくやつを指定
$ pytest -k "asdict or defaults" --collect-only
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 6 items / 4 deselected / 2 selected                                                                                                                                                                                         
<Module tasks/test_four.py>
  <Function test_asdict>
<Module tasks/test_three.py>
  <Function test_defaults>

=================================================================================================== 4 deselected in 0.02 seconds ==================================================================================================== 

# asdictまたはdefaultsと名前がつくやつのみテスト実行
$ pytest -v -k "asdict or defaults"
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\pytestx\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 6 items / 4 deselected / 2 selected                                                                                                                                                                                         

tasks/test_four.py::test_asdict PASSED                                                                                                                                                                                         [ 50%] 
tasks/test_three.py::test_defaults FAILED                                                                                                                                                                                      [100%] 

============================================================================================================= FAILURES ============================================================================================================== 
___________________________________________________________________________________________________________ test_defaults ___________________________________________________________________________________________________________ 

    def test_defaults():
        """Using no parameters should invoke defaults."""
>       t1 = Task()
E       TypeError: __new__() missing 4 required positional arguments: 'summary', 'owner', 'done', and 'id'

tasks\test_three.py:17: TypeError
========================================================================================= 1 failed, 1 passed, 4 deselected in 0.04 seconds ========================================================================================== 

-m MARKEXPR

マーカーを使ってテスト関数の一部にマークを付けると、それらの関数をまとめて実行できるようになります。たとえば、それぞれ別ファイルに含まれているtest_replace()とtest_member_access()を実行するために、それらにマークをつけます。

マーカーには好きな名前をつけることができます。run_these_pleaseというマークをつけるには、以下のようにします。

import pytest
...
@pytest.mark.run_these_please
def test_member_access():
...

test_replace()にも同じマークをつけます。

# マークをつけた関数のみをテスト実行する
$ pytest -v -m run_these_please
======================================================================================================== test session starts ========================================================================================================
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 -- C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\pytestx\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 6 items / 4 deselected / 2 selected                                                                                                                                                                                         

tasks/test_four.py::test_replace PASSED                                                                                                                                                                                        [ 50%] 
tasks/test_three.py::test_member_access FAILED                                                                                                                                                                                 [100%] 

============================================================================================================= FAILURES ============================================================================================================== 
________________________________________________________________________________________________________ test_member_access _________________________________________________________________________________________________________ 

    @pytest.mark.run_these_please
    def test_member_access():
        """Check .firld functionality of namedtuple."""
>       t = Task("buy milk", "brian")
E       TypeError: __new__() missing 2 required positional arguments: 'done' and 'id'

tasks\test_three.py:26: TypeError
========================================================================================================= warnings summary ========================================================================================================== 
C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\pytestx\lib\site-packages\_pytest\mark\structures.py:332
  C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\pytestx\lib\site-packages\_pytest\mark\structures.py:332: PytestUnknownMarkWarning: Unknown pytest.mark.run_these_please - is this a typo?  You can register custom marks 
to avoid this warning - for details, see https://docs.pytest.org/en/latest/mark.html
    PytestUnknownMarkWarning,

-- Docs: https://docs.pytest.org/en/latest/warnings.html
=================================================================================== 1 failed, 1 passed, 4 deselected, 1 warnings in 0.04 seconds ==================================================================================== 

-x, --exitfirst

pytest -x

pytestは通常、テスト関数でassertが失敗したり例外が発生したらテストの実行はそこで中止され、そのテストは失敗となります。そして次のテストを実行します。テストが失敗したら次のテストを実行せずにテストセッション全体を底で中止したい場合、この-xオプションを使います。

$ pytest -x
======================================================================================================== test session starts ========================================================================================================
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 6 items                                                                                                                                                                                                                     

test_one.py .                                                                                                                                                                                                                  [ 16%] 
test_two.py F

============================================================================================================= FAILURES ============================================================================================================== 
___________________________________________________________________________________________________________ test_falling ____________________________________________________________________________________________________________ 

    def test_falling():
>       assert(1,2,3) == (3,2,1)
E       assert (1, 2, 3) == (3, 2, 1)
E         At index 0 diff: 1 != 3
E         Use -v to get the full diff

test_two.py:2: AssertionError

========================================================================================== 1 failed, 1 passed, 1 warnings in 0.05 seconds =========================================================================================== 

test_one.pyは成功し、test_two.pyで失敗し、そこでテストセッション全体が終了していることがわかります。

--tb=no

$ pytest --tb=no

テスト失敗時のトレースバックをオフにします。失敗したテストを簡潔に表示したい場合につかいます。

$ pytest --tb=no
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 6 items                                                                                                                                                                                                                     

test_one.py .                                                                                                                                                                                                                  [ 16%] 
test_two.py F                                                                                                                                                                                                                  [ 33%] 
tasks\test_four.py ..                                                                                                                                                                                                          [ 66%] 
tasks\test_three.py FF                                                                                                                                                                                                         [100%] 

========================================================================================== 3 failed, 3 passed, 1 warnings in 0.05 seconds =========================================================================================== 

--maxfail=num

$ pytest --maxfail=2

「テストがnum個失敗したらテストセッション全体を中止する」ときに使います。 --maxfail=2 で、テストが2つ失敗したらテストセッション全体を中止します。

-s, --capture=method

# 出力のキャプチャを無効にし、出力が標準出力に書き出される
$ pytest -s
# または、
$ pytest --capture=no

デフォルトではprint()文は標準出力に表示されませんが、この-sを使うとprint()文を標準出力に出力します。

test_one.py

def test_passing():
    assert (1, 2, 3) == (1, 2, 3)
    print("End!")

テストを実行します。

# デフォルトでは、print()文は出力されない
$ pytest test_one.py
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 1 item                                                                                                                                                                                                                      

test_one.py .                                                                                                                                                                                                                  [100%] 

===================================================================================================== 1 passed in 0.02 seconds ====================================================================================================== 

# -sオプションをつけると、print()文が出力される
$ pytest -s test_one.py
======================================================================================================== test session starts ======================================================================================================== 
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 1 item                                                                                                                                                                                                                      

test_one.py End!
.

===================================================================================================== 1 passed in 0.02 seconds ====================================================================================================== 

他にも --capture=sysや --capture=fd などがあるが、それらはよくわからないしあんまり使わなそう。

--lf, --last-failed

$ pytest --lf

最後に失敗したテストだけが再び実行されます。失敗しているテストだけを実行するときに便利。

--ff, --failed-first

$ pytest --ff

基本的に--last-failedと同じで、最後に失敗したテストを実行した後、残りの成功しているテストを実行します。

-v, --verbose

$ pytest -v

通常よりも詳細に情報が出力されます。詳しいエラー内容を知りたいときに便利。

-q, --quit

$ pytest -q --tb=line test_two.py
F                                                                                                                                                                                                                              [100%] 
============================================================================================================= FAILURES ============================================================================================================== 
C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1\test_two.py:2: assert (1, 2, 3) == (3, 2, 1)
1 failed in 0.03 seconds

出力される情報が少なくなります。--tb=lineオプションと組み合わせることで失敗しているテストの行だけが表示されるようになります。

-l, --showlocals

失敗しているテストのローカル変数とそれらの値がトレースバックで表示されます。

$ pytest -l tasks/test_four.py
======================================================================================================== test session starts ========================================================================================================
platform win32 -- Python 3.7.4, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\XXXX\workspace\my-practice\PythonTestingWithPytest\ch1
collected 2 items                                                                                                                                                                                                                     

tasks\test_four.py .F                                                                                                                                                                                                          [100%]

============================================================================================================= FAILURES ============================================================================================================== 
___________________________________________________________________________________________________________ test_replace ____________________________________________________________________________________________________________ 

    @pytest.mark.run_these_please
    def test_replace():
        """_replace() should change passed in fields."""
        t_before = Task("finish book", "brian", False)
        t_after = t_before._replace(id=10, done=True)
        t_expeceted = Task("finish book", "brian", True, 11)
>       assert t_after == t_expeceted
E       AssertionError: assert Task(summary=...e=True, id=10) == Task(summary='...e=True, id=11)
E         At index 3 diff: 10 != 11
E         Use -v to get the full diff


t_after    = Task(summary='finish book', owner='brian', done=True, id=10)
t_before   = Task(summary='finish book', owner='brian', done=False, id=None) 
t_expeceted = Task(summary='finish book', owner='brian', done=True, id=11)  

tasks\test_four.py:24: AssertionError

========================================================================================== 1 failed, 1 passed, 1 warnings in 0.04 seconds =========================================================================================== 

assertが失敗したときの、t_after, t_before, t_expectedの3つの変数の中身が表示されていることがわかります。

--tb=style

$ pytest --tb=no

失敗しているテストのトレースバックを出力する方法を変更します。

--tb=shortは、assertの行とエラーが評価された行だけが表示されます。

--tb=lineは、エラーが一行にまとめられます。

--tb=noは、トレースバックが完全になくなります。

--durations=N

$ pytest --durations=3

テストが実行された後に、最も時間がかかったN個のテスト/セットアップ/ティアダウンを表示します。テスト全体を高速化するときに役に立ちます。

--durations=0の場合は、最も時間がかかったものから順に全てのテストが表示されます。

--version

$ pytest --version

pytestのバージョンとインストールされているディレクトリを表示します。

$ pytest --version
This is pytest version 5.0.1, imported from C:\Users\XXXX\AppData\Local\Continuum\anaconda3\envs\pytestx\lib\site-packages\pytest.py

よく使うcondaコマンド

Conda Cheat Sheet

Conda Cheat Sheet

condaコマンド

仮想環境の作成

# conda create -n [仮想環境名]
> conda create -n py3x
# pythonのバージョンを指定したい場合は conda create -n [仮想環境名] python=3.6 など
> conda create -n py36 python=3.6

仮想環境の確認

> conda info -e

仮想環境の削除

> conda remove -n py37 --all

仮想環境の切り替え

# conda activate [仮想環境名]
> conda avtivate py36
# conda は省略可能
> activate py36

仮想環境から抜ける

> conda deactivate

パッケージのインストール

# djangoを仮想環境にインストールする
> conda install django
# djangoのバージョン2.1を指定したい場合は
> conda install django==2.1

仮想環境の保存(仮想環境のエクスポート)

# conda env export -n [仮想環境名] > [保存ファイル名(.yml)]
> conda env export -n pytestx > pytestx.yml

仮想環境の再構築(仮想環境のインポート)

# conda env create -f=[仮想環境のymlファイル]
> conda env create -f=pytestx.yml

参考

編集履歴

  • 2020/01/14、Conda Cheat Sheetの項目を追加