備忘録

何かあったとき用に

凍結解除のお話

以前こんなこと言ってましたけどなんやかんやあって凍結解除されたのでそのお話

何があったのか

5 月 14 日の朝 4 時ぐらい*1に凍結かロック, または両方の判定食らったらしくアカウントがお陀仏になりました.
ちぃたんや神楽めあなどのアカウントが一斉凍結*2されたのでそれに巻き込まれた形ですね.
"凍結かロック, または両方の判定" と書いたのは Twitter を開いた後 "ロック画面の表示 → ロック解除 → 数秒後に凍結表示" となったのでロック解除されてから凍結されたのか, 同時に行われたのかがわからなかったのでそう表記しました (おそらく後者の可能性が高いと思います, 個人的な感想ですが…).

解除までの経緯

凍結されたあと, すぐに異議申し立てフォームから異議申し立てしました. Twitter の日本支社は悪い噂しか聞かないのと個人的な恨みがあったので, 英語フォームから本社に直接異議申し立てしました. まあ, それが今回解除が長引いた原因なんですけどね…
異議内容は忘れましたが確か "なんで凍結されたか知らんが教えろ" みたいな内容だったような気がします. それで, 返信はこれです

f:id:KashEight:20190803235915p:plain

読んだらわかるかと思いますが, この方法はいわゆるロック解除の方法で凍結異議とは別のものです. 件名は "Appealing an account suspension" なのにね.
一応, これは返信すると返ってくるタイプのメールなので返信してます.
そのときの回答はこちらです

f:id:KashEight:20190804002815p:plain

そのまま読むと, "ルールの複数回違反による凍結" です. もちろん, これだけでは納得しないので再度異議を送り直しました.
そのときの回答

f:id:KashEight:20190804005408p:plain

まあいわゆる "永久凍結されてるのに別のアカウント持ってるやろお前" みたいな感じで凍結されたようです.
ならばと, "永久凍結した最初のアカウントで異議申し立てして解除されればいけるのでは?" という浅はかな考えで今回凍結したアカウントと違うアカウントで異議申し立てしました. もちろん, 結果はダメでしたけど.
で, ある時 "日本語フォームで異議申し立てしてみよう" と考えになって異議申し立てを行いました. それと同時に "それならば, 今回のアカウントも日本語フォームから送ってみよう" と思い, こちらの方でも異議申し立てを行いました.
前者の方は 1 日ほどで返ってきて内容は "無理です" ということ (過去に何度も蹴られてるので当たり前だが). 後者の方は 9 日かかって返ってきました. そのときの内容がこちらです

f:id:KashEight:20190804011549p:plain

いやいやいや…

8, 9 割ぐらい諦めてたのでこれ見た瞬間めっちゃびっくりしました. 21 時ぐらいにこれ送られてきてその時は寝てたんですが, 携帯の通知がきて見たらこれだったので一瞬で目が覚めました.

そういうわけでアカウントが無事復活しました. 素直に日本語フォームから異議申し立てを送れば 2 ヶ月もかからず解除はできたと思いますが, まあ結果的に解除はできたので良しとしましょう.

結論

Twitter の利用環境が日本ならためわらず日本語フォームで異議申し立てしましょう. 日本支社と本社で回答が違うところを見ると, どっちとも状況を同じく把握しているわけではないっぽいので.

あと, ネタで相手を通報するのはやめようね!てか, 相手がクソ嫌いなやつで凍結させたくてしょうがない人はともかく, 面白半分のネタで通報するのはやめてくれ, マジで面倒だから, いやほんとに.

*1:凍結されても凍結される直前の TL は表示されるので TL 上に残ってた最後のツイート時間から判別しました. ちなみにその時間は丁度 Discord も落ちてたみたいです (そのときの最終ツイートが Discord が落ちた報告でした, 不名誉).

*2:ソース: Twitterでアカウント凍結祭りか ちぃたん☆や仮面女子、Vtuberなども凍結(2019年5月14日)|BIGLOBEニュース

家具作った

計5時間ぐらい (片付け含めると6時間ちょい) かかったので昼飯は16時ぐらいに食べた. (作業開始が10時くらいなので)

以上.

Nuxt2 + TypeScriptの環境構築

この記事はKaigen Discord Advent Calendar 2018 13日目の記事です。

前置き

Nuxt.jsでTypeScriptを導入した環境を構築するのにかなり苦労したので同じことをしようと思う人と自分に向けてまとめておく。
最終的なテンプレートはGitHubにあげていますのでご参考に。

環境

  • Node.js: 10.14.1
  • Nuxt: 2.3.4

構築過程

プロジェクト作成

まず

$ yarn create nuxt-app

でNuxtプロジェクトを作る。構成は以下の画像のようにした。

f:id:KashEight:20181206205904p:plain

今回は環境構築だけなのでサーバサイドフレームワークやUIフレームワークは使わず, また, APIをいじいじするわけでもないのでAxiosも入れない。Universalアプリケーション作るわけでもないのでSPA。lintはかけたいのでeslintやprettierは今回のプロジェクトで導入した。

プロジェクトを作成したらとりあえず

$ yarn run dev

で動かしてみる。
Nuxt v2.3.4ではこのような画面が出たら成功。

f:id:KashEight:20181206213350p:plain

TypeScriptの導入

TypeScriptとWebpack用のloaderとnodeの型宣言ファイルを導入する

$ yarn add -D typescript ts-loader @types/node

Nuxt(というよりかはVueファイル)で使用するパッケージを導入

$ yarn add vue-class-component vue-property-decorator vuex-class

vue-class-componentやvue-property-decoratorをベースとしたnuxt-class-componentnuxt-property-decoratorがnuxt-communityで公開されているがどちらともメンテナンスが滞ってる(nuxt-property-decoratorは最近メンテナンスが再開された?)し, どちらともベースとなるもので事足りるのとそっちの方が機能が多いので別に導入しなくていいと思う。

TypeScriptをビルドできるようにする

TypeScriptをビルドできるようにWebpackの設定をいじってみる。
Nuxtではnuxt.config.jsのbuildプロパティ*1でWebpackの設定をいじるほか, モジュール*2という形でもWebpackの設定をいじることができる。
前者の方法では軽く設定を変える程度で複数プロジェクトで同じようなことをしない場合, そうではない場合は後者の方法を用いるのがいいでしょう。
今回のような場合は後者の方法で実装したほうがよさそう。
幸いにもNuxt2ではないがTypeScriptのテンプレートがあったのでそれを参考にNuxt2用に実装してみる。
まず, modules/typescript.jsとmodulesフォルダとjsファイルを作り以下のコードを記入する。(一応, tsxにも対応できるようにした)

module.exports = function() {
    // Nuxtで.ts/.tsxファイルを解決させる
    this.nuxt.options.extensions.push('ts', 'tsx')
    // Webpackの設定
    this.extendBuild(config => {
        // .ts/.tsxをビルドできるようにts-loaderを追加する。
        config.module.rules.push({
            test: /\.tsx?$/,
            exclude: /(node_modules)/,
            loader: 'ts-loader',
            options: {
                appendTsSuffixTo: [/\.vue$/]
            }
        })
        // Webpackのextensionsに.tsが入っていない場合解決できるようにする
        if (!config.resolve.extensions.includes('.ts')) {
            config.resolve.extensions.push('.ts')
        }
        // Webpackのextensionsに.tsxが入っていない場合解決できるようにする
        if (!config.resolve.extensions.includes('.tsx')) {
            config.resolve.extensions.push('.tsx')
        }
    })
}

次にモジュールを読み込めるようにnuxt.config.jsに以下を追記する。

modules: ['~modules/typescript.js']

tsconfig.jsonは以下のようにした。

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom", "es2015", "es2016", "es2017"],
        "module": "es2015",
        "moduleResolution": "node",
        "alwaysStrict": true,
        "experimentalDecorators": true,
        "noImplicitAny": false,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "sourceMap": true,
        "removeComments": true,
        "suppressImplicitAnyIndexErrors": true,
        "allowSyntheticDefaultImports": true,
        "allowJs": true,
        "baseUrl": ".",
        "paths": {
            "~/*": ["./*"],
            "@/*": ["./*"]
        },
        "typeRoots": [
            "node_modules/@types",
            "types"
        ]
    },
    "exclude": [
        "node_modules"
    ]
}

vscodeだと以下のようなファイルエラーが出ますが無視して構いません。(理由はよくわからないです…分かる人は教えてくれれば幸いです。)

[ts] 入力ファイルを上書きすることになるため、ファイル <rootPath>/modules/typescript.js' を書き込めません。

2/20追記: コメントにて

tsconfing.jsonのcompilerOptionsにoutdirかoutFileを指定するとエラーがなくなるようです。
変換しない場合は、"outFile": "" とするかまたは存在しないデタラメなフォルダを指定すればいいようです。

という指摘をいただきました。
確認はまだしていないのですがこれでそのvscodeのエラーは回避できるようです。
7ccさんありがとうございます!

最後に, VueでTypeScriptが使用できるように型宣言ファイルをtypes/index.d.tsのように作成して以下を記入する。

declare module '*.vue' {
    import Vue from 'vue'
    export default Vue
}

TypeScriptを使用する

pages/index.vueでTypeScriptを使用してみる。ソースは以下のようにした。

<template>
  <section class="container">
    <div>
      <logo/>
      <h1 class="title">
        {{ title }}
      </h1>
      <h2 class="subtitle">
        {{ fuwa }}
      </h2>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          class="button--green">Documentation</a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey">GitHub</a>
      </div>
    </div>
  </section>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import Logo from '~/components/Logo.vue'

@Component({
    components: {
        Logo
    }
})
export default class extends Vue {
    title = 'hoge'
    desc = 'fuwa'
}
</script>

<style>
.container {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.title {
  font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,
    'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  display: block;
  font-weight: 300;
  font-size: 100px;
  color: #35495e;
  letter-spacing: 1px;
}

.subtitle {
  font-weight: 300;
  font-size: 42px;
  color: #526488;
  word-spacing: 5px;
  padding-bottom: 15px;
}

.links {
  padding-top: 15px;
}
</style>

ビルドしてみる

一度, yarn run lint --fixyarn run devをしてビルドしてみよう。
localhost:3000に接続してみて以下の画像のようになったら成功。

f:id:KashEight:20181207011810p:plain

これでTypeScriptの導入は終了した。

TypeScriptにlintをかける

TypeScriptを導入したはいいけれども.ts/.tsxファイルや.vueファイル内のscript部分にはlintはかからない。
そこでtslintやtypescript-eslint-parserを導入してlintをかけようと思う。今回はeslintを導入しているのとtslintとvueの相性が悪いのでtypescript-eslint-parserを使ってlintをかける。
まず必要となるパッケージを追加する。

$ yarn add -D typescript-eslint-parser eslint-plugin-typescript

nuxt.config.js

config.module.rules.push({
    enforce: 'pre',
    test: /\.(js|vue)$/,
    loader: 'eslint-loader',
    exclude: /(node_modules)/
})

を以下のように変更

config.module.rules.push({
     enforce: 'pre',
     test: /\.(js|ts|vue)$/,
     loader: 'eslint-loader',
     exclude: /(node_modules)/
})

.eslintrc.jsに以下を追記

overrides: [
    {
        files: ["*.ts", "*.vue"],
        parserOptions: {
            parser: "typescript-eslint-parser"
        },
        plugins: ['vue', 'prettier', 'typescript']
    }
]

これでTypeScriptにlintがかかるようになった。

余談

この記事を作るきっかけとしてyarn create nuxt-appでeslintとprettierを導入して初期の状態でビルドするとエラーを吐いて導入できなかったのがあった。
原因は.vueファイル内のscriptの言語がTypeScriptだと初期のeslintのパーサであるbabel-eslintで解析してしまうためそれを回避するために記事中に書いたTypeScriptにlintをかけるような方法を取らなければいけなかった。
この記事を書いてる最中にNuxtのバージョンが上がったため新しいバージョンで試したところ, エラーを吐かずにすんなりビルドを通した。
今までの苦労はなんだったんだろう…。

脚注

楽しいHypixel: PaintBall編

この記事はKaigen Discord Advent Calendar 5日目の記事です。 PaintBall編とか言ってるけど続くかはわかりません。

前書き

この記事はHypixelのPaintBallの魅力と楽しみ方の一つを自分なりに伝えるための記事なのでそんな本格的なことは書かないつもりです。
あと(こんな紹介記事書いてるけど)別にPaintBallが上手いわけではないのでKillstreakの選び方やPerkの選び方などは参考程度に留めておいてください。

PaintBallってなんぞや

(知ってる人は飛ばして構いません)
ゲームルールは

"2チームに分かれて雪玉投げあって相手のチームの残機を先に0にしたチームの勝利"

…これだけ見るとただの雪合戦と何が違うのか, そしてなんの面白さがあるのかわかりにくいかもしれません。
普通のPaintBallならつまらないでしょうけどここはあの"Hypixel", 普通ではないんです。
じゃあ何が違うのか?説明していきましょう。

豊富なShop

まずはHypixel恒例のShopを見ていきましょう。
Classic Games Lobbyへ行きPaintBallのShopを覗いてみます。 f:id:KashEight:20181201020354p:plain
5つの項目がありますが特に重要なのは上の3つです。それぞれ見ていきましょう。

Hat

f:id:KashEight:20181201020642p:plain
(テクスチャバグっててごめんなさい)
見る限りたくさんあって頭(Hatだけに)が混乱するかと思いますがこれらは大きく2種類にわけることができます。
一つは安価で購入できるBlockHat, もう一つは高価なSkullHatです。(BlockHat, SkullHatはそれぞれわかりやすくするために自分がつけた名称なので正しい名称ではないのでご注意を)
この二つの説明の前にまずはHatの説明をしましょう。

Hatとは

Hatは購入して装着することで試合にてHatに応じた効果を得ることができます。ただエフェクトを出す(意味のないネタ)Hatやゲームに大きな影響をもたらすHatまで多種多様なHatがこのPaintBallでは存在しており, ゲームをプレイする上では必要不可欠な存在です。
Hatを装着するにはShopで, 購入したHatをクリックするだけで装着, 次の試合から効果を得られます。逆に言えば装着するまでその効果は得られないということです。
勿論装着できるHatは一人一つまで, でないとゲームバランスは崩壊するからね。(元から壊れてるとか言わない)

このHatの量を見て大抵の方は「けどこんなたくさんの中から一つとか選べないよぉ~><」ってなると思います。
そこでBlockHat, SkullHatと分け, それぞれの中で必要なものをピックアップしましょう。まずはこの二種類の説明から。

BlockHatはブロック型のHatで安価(4,200コイン)で購入できるものです。コインがない序盤に購入しやすいです。
一方, SkullHatはプレイヤーの頭の形をしたHatでかなり高価(50,000/75,000コイン)です。クールタイムがありますがその分価値にあった性能を出してくれるのでコインが溜まったら買うのが良いと思います。

次にこの二種類の中から必要なものを紹介します。

BlockHat

  • Speed Hat
    -> 常時Speed2が付与, PaintBallのマップは広いので速く移動できるのは試合の勝敗を大きく分けます。

BlockHatはこの一つだけで十分です。というかこれを選ばないでどう試合すればいいかレベルなので必ず買いましょう。

SkullHat

  • Rezzus Hat
    -> スニークすると5秒間雪玉にホーミング性能がつく, クールタイムは70秒
  • Paintballkitty Hat
    -> 常時Speed2が付与, スニークすることで5秒間Speed3が付与される, クールタイムは70秒

SkullHatは基本的に高いのもあって最初のうちは手を出すことは難しいかもしれません。最初のうちはSpeed Hatでプレイしてコインに余裕が出てきて買おうと思ったらこの二つのどちらかを買えばいいと思います。
私のオススメは二番目のPaintballkitty Hatです。Speed Hatの上位互換版なのでHatを変えても違和感は全く無いです。
Rezzus HatはSpeed2は付与されませんが雪玉のホーミング性能が最強です。狭いマップではこれに敵うHatはないでしょう。

Perk

f:id:KashEight:20181201030640p:plain
Hatとは違って一気に数が少なくなりました。
けれどもPaintBallの中で一番重要な部分であり一番コインを必要とするのがこのPerkです。
ではこのPerkとはなにか, そしてどれを選べばいいのかこれから説明します。

Perkとは

Perkはレベル制となっておりHatとは違って買い切りではありません。
レベルの上限はそれぞれのPerkによって違い, 短いもので5レベル, 長いもので50レベルまであります。
全部上限まで開放するのはかなりの時間をPaintBallに捧げる必要はあるので現実的ではありません, そこでこの中からHatのように必要なものをピックアップしましょう。

  • Fortune
    -> 敵をKillした際にKillstreakをさらに1個貰える確率が増える。一レベル上げるごとに5%増加。上限は20レベル(100%)
  • Superluck
    -> 敵をKillした際に雪玉を10個貰える確率が増加する。一レベル上げるごとに5%増加。上限は20レベル(100%)
  • Headstart
    -> 初めから持っているKillCoinを増やすことができる。一レベル上げるごとに1KillCoinを得る。上限は5レベル(5KillCoins)

他にもGodfatherやEnduranceがありますがとりあえずこの三つを上限にすることを目標にすればいいかと思います。
どれを先に上げるかですが自分はFortune, Headstart, Superluckの順にあげるのがおすすめです。どのPerkにも言えることですが買うごとに値段は上昇していくため最適解としては

Fortune(途中まで) -> Headstart(3か4レベルまで) -> Superluck -> Fortune(最後まで)

が良いかもしれませんね。

Killstreak

f:id:KashEight:20181201034039p:plain
数が多くなりましたが, これも同じように説明していきましょう。

Killstreakとは

敵をKillしたときにもらえるKillCoinを使って使用できるスキルです。雪玉を遠くに速く投げることができるKillstreakから敵を全滅させるKillstreakまで色々なKillstreakが存在します。
ちなみに, 先程の画像以外にも初めから所持しているKillstreakもあるので画像が全てのKillstreakというわけではないです。
試合で使えるKillstreakをピックアップしていきましょう。

  • Strong Arm
    -> 30秒間雪玉の飛ぶ速度が早くなり, 遠くに飛ぶようになる。消費KillCoinは4, しかも初めから所持している。
  • Triple Shot
    -> 30秒間投げた雪玉が3方向に飛ぶようになる。Strong Armと併用可。消費KillCoinは3
  • Nuke
    -> カウントダウンが始まり5秒後に「リスポーン時の無敵時間中以外」の敵を全員Killする。Killした敵一人ごとに1KillCoin手に入る。クールタイムは60秒, 消費KillCoinは25
  • +10
    -> 味方の残機を10増やす。初めから使える+3(消費KillCoinは5)の上位互換, これで勝敗が大きく分かれることがある。消費KillCoinは15
  • Force Field
    -> 15秒間無敵になる。コストが高いがその分敵をKillすれば元は取れる。ただしKillstreakを使った攻撃(TNT Rain, Revengeなど)には無敵はきかない。クールタイムは60秒?(情報がないのでわからない), 消費KillCoinは50

マップにもよるけどほとんどのマップで使えるKillstreakを列挙しました。これ以外にもLightningやEnder Pearlと言ったマップによってはかなり強いKillstreakもあるので適宜使い分けよう。特にNukeは狭いマップやプレイヤーが少ない試合では効果を発揮できないことが多いです。

豊富なマップ

HypixelのPaintBallのマップは少人数用から大人数用まで色々なマップが存在しています。
マップは人の好みが分かれますがとりあえずの参考として自分の好きなマップを上げたいと思います。

  • Mansion
    -> 少人数マップ, かなり狭いのもあってLightningが大いに活躍できる。
  • Herobrine
    -> 大人数マップ, リスキル等でここで100Killを叩き出すことはよくある。自分も何回か出した。
  • Juice
    -> 少人数マップ, Lightningが活躍できるので楽しい。
  • Victorian
    -> 大人数マップ, Herobrineと同じくリスキルができる。

初心者のうちはマップに慣れないだろうけど慣れてきたらどのマップに自分は合ってるかわかるので是非ともいろんなマップに挑戦していただきたい。
まずは試合でKill数1位を目指して頑張ろう!

まとめ

PaintBallは初心者には取っ付きにくいですがコツさえ掴めば誰でもできるものです。
自分も最初はKillされてばかりでしたがやっていくうちに逆にKillをする方になっていたのでどんなことにも当てはまりますが徐々に慣れていくことが大切だと思います。
もしPaintBallに興味を持ったならば是非とも参加してみてください!というかしてほしい, ただでさえ人が少ないから。

PySide2で特定の子ウィジェットを取得し, 処理する

ただ単純に子ウィジェットを取得するにはchildren()関数を使えばいいのだが, 特定の子ウィジェットを探し出し処理するのにけっこう骨が折れた。
何通りか試していい答えが出たので試した通りを例に上げて忘れないうちに書いておく。

その0: 下準備

下準備としてこういうコードを用意した(長いのでgistにしました):

gist.github.com

これを実行させてみるとこういう感じの画面となる
f:id:KashEight:20180729015844p:plain

ここからボタン:"button2"だけを取得してテキストを変えてみようと思う。

その1: findChildren()関数を使う

目的の関数を探す

まず始めにそのクラスの情報を見るためにPySide2の公式リファレンスを確認した。
全部のWidget/LayoutクラスにはベースとなるQtWidgets.(QWidget|QLayout)クラスが存在するのでそのクラスを見ることにした。
QtWidgets.QWidgetにはchildAt()という関数が存在したけれども, 詳細を見ると

Parameters:
x – PySide2.QtCore.int
y – PySide2.QtCore.int Return type:
PySide2.QtWidgets.QWidget

Returns the visible child widget at the position (x, y) in the widget’s coordinate system. If there is no visible child widget at the specified position, the function returns 0.

と記述されてたので探してるもの遠かったので候補として外した。

QtWidgets.(QWidget|QLayout)クラスを探しても特に目ぼしいものはなかったので2つのクラスに共通として使われているQtCore.QObjectを焦点を当てていこう。

そのクラスの公式リファレンスを確認してみるとfindChild(), findChildren()関数が該当しそうであった。

使ってみる

findChild()関数を使う

まず最初はfindChild()関数を使ってみることとする。
第一引数にはPyTypeObjectが指定されているのでQPushButton入れる。第二引数は探すObjectの名前*1なので空欄にしておく。
とりあえず親ウィジェットから試してみる。

wdt = MainWidget()
c = wdt.findChild(QPushButton)
print(f"widget: {c}, text: {c.text()}")

として実行してみるとコンソールには

widget: <PySide2.QtWidgets.QPushButton object at 0x000000FDF8A70688>, text: button1

と表示された。
公式リファレンスには返り値がPyObjectと書いてあるので, findChild()関数はObjectの名前がついたものを探すのに便利そうな感じ。

findChildren()関数を使う

findChild()関数と同じ要領でやるが, 返り値がPySequenceなのでコードを以下のように変更する。

wdt = MainWidget()
sequence = wdt.findChildren(QPushButton)
print(sequence) 
for c in sequence:
    print(f"widget: {c}, text: {c.text()}")

すると, コンソールには

[<PySide2.QtWidgets.QPushButton object at 0x0000005ACD691848>, <PySide2.QtWidgets.QPushButton object at 0x0000005ACD691888>, <PySide2.QtWidgets.QPushButton object at 0x0000005ACD691788>, <PySide2.QtWidgets.QPushButton object at 0x0000005ACD6916C8>]
widget: <PySide2.QtWidgets.QPushButton object at 0x0000005ACD691848>, text: button3
widget: <PySide2.QtWidgets.QPushButton object at 0x0000005ACD691888>, text: button4
widget: <PySide2.QtWidgets.QPushButton object at 0x0000005ACD691788>, text: button2
widget: <PySide2.QtWidgets.QPushButton object at 0x0000005ACD6916C8>, text: button1

と出た。
ここで注目してほしいのは親ウィジェットに対するボタンウィジェットの数である。
試しに親ウィジェットに対する子ウィジェットを取得するためにchildren()関数を使用してみると

[<__main__.ChildWidget object at 0x0000009EE28C0648>, <PySide2.QtWidgets.QVBoxLayout object at 0x0000009EE28C0988>, <PySide2.QtWidgets.QPushButton object at 0x0000009EE28C0608>]

となり, ウィジェットに登録されてるボタンウィジェットに対してfindChildren()関数で表示されるボタンウィジェットの数が一致していないことがわかる。
これはfindChildren()関数が子ウィジェットウィジェット, つまり, 孫ウィジェットまで探すようになっているのである。
一見, 何の問題もなさそうだがもし, 親ウィジェットと子ウィジェットに同じ内容のウィジェットが存在したり, 大量のウィジェットが存在したりする場合はどうだろうか。
前者は親子関係を調べたりsetObjectName()とかでObjectNameをつけて判別すればいいのだが, 後者の場合はどうにもならない。
PySide2のもととなるQtのfindChildren()にはoptionsを追加できる*2が, PySide2にはそれが存在しない*3
そこで, findChildren()を使わずにウィジェットを探すことにした。

その1のまとめ

  • ウィジェットを探すにはQtCore.QObject.(findChild()|findChildren())がつかえそう。
  • 使う場合, 孫ウィジェットも含むウィジェットも候補となる。
  • 用途としては, ObjectNameを設定しそれを探し出すことに使える。

その2: レイアウトからウィジェットを探す

ウィジェットを取得する

その1ではウィジェットから子ウィジェットを探したがその2ではレイアウトから子ウィジェットを取得してみようと思う。

レイアウトを取得する

まず親ウィジェットのレイアウトを取得するためにlayout()関数を使う。
次がなかなかややこしい。
レイアウトに登録されているウィジェットを探すためにchildren()関数を使ったのだが, 返ってきた値が

[]

となってしまった。

これで数日間悩んだのだが, 調べてみるとどうやらレイアウトの基礎となるQLayoutにウィジェットを追加する場合, "Widget"として追加されるのではなく, "WidgetItem"として追加されるのでchildren()では空のリストが返ってくるそう。
そのため, レイアウトにあるウィジェットを調べたいときはitemAt()関数を使い, ウィジェットの詳細を知るにはlayout().widget()とすればいい。
なお, itemAt()関数の引数はint, 返り値はQWidgetItemなので使用する際はcount()関数を使ってウィジェットアイテムの数を取得してforループで回してそれぞれのウィジェットを取得することとなる。

目的のウィジェットか判別する

forループを回して返ってきたウィジェットが目的のウィジェットか判別する。目的ではないウィジェットtext()関数を使用してもエラーの原因になりかねない。
ではどのようにして判別すればいいのか。
ここで, ウィジェットをどのように実装したのかを思い出してほしい。ウィジェットを作成する際, 一番最初にすることはなにか。
それはインスタンスである。
つまり, 返ってきたウィジェットが目的のウィジェットインスタンスであるかを確認すればいいので,

isinstance(<ウィジェット>, <目的のウィジェット>)

isinstance()関数を使えば良い。

最終的なコード

"button3"を押したら"button2"のテキストを変更する。

class GrandsonWidget(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        btn3 = QPushButton("button3")
        btn3.clicked.connect(self.on_clicked)
        btn4 = QPushButton("button4")
        grandson_layout = QVBoxLayout()

        grandson_layout.addWidget(btn3)
        grandson_layout.addWidget(btn4)

        self.setLayout(grandson_layout)

    def on_clicked(self):
        for i in range(self.parent().layout().count()):
            c = self.parent().layout().itemAt(i).widget()
            if isinstance(c, QPushButton):
                if c.text() == "button2":
                    c.setText("clicked!")

実行結果:
f:id:KashEight:20180730154500g:plain
これで最初の目的を達成することができた。

余談

その1, その2を見て「普通にchildren()使ってisinstance()で判別すればいいのでは。」と思う人はいるのではないか。
自分もこの記事を書いてる途中に気づいた, まあその場その場で使い分ければいいと思う。

参考にしたサイト

python - Get a layout's widgets in PyQT - Stack Overflow

脚注