この記事は、Pythonista3 Advent Calendar 2022 の04日目の記事です。
一方的な偏った目線で、Pythonista3 を紹介していきます。
ほぼ毎日iPhone(Pythonista3)で、コーディングをしている者です。よろしくお願いします。
以下、私の2022年12月時点の環境です。
--- SYSTEM INFORMATION ---
**System Information**
* Pythonista 3.3 (330025), Default interpreter 3.6.1
* iOS 16.0.2, model iPhone12,1, resolution (portrait) 828.0 x 1792.0 @ 2.0
他の環境(iPad や端末の種類、iOS のバージョン違い)では、意図としない挙動(エラーになる)なる場合もあります。ご了承ください。
ちなみに、model iPhone12,1
は、iPhone11 です。
先にこっちを紹介しろ😡
人生が豊かになるキーボードぉー☺️
今回は私がPythonista3 にて、高頻度使用しているスクリプトを紹介します。
初回で紹介せず、本当に申し訳ない気持ちでいっぱいです。本当にそれくらい便利になるので、紹介するのが楽しみです。
みんなで人生豊かになりましょう!
PyKeys でカスタムして、人生豊かに
GIF では、何を押しているか分かりづらいですね。。。
自分で設定した要素を押し文字を入力し、コピーしたものを「📝」を押して、ペーストしています。
設定する要素は、自分で選べます。ペーストも、わざわざ画面長押ししてpopup の「Paste」を選択せずに、ワンアクションで貼り付けができます。
keyboard
モジュール
keyboard — Utilities for the Pythonista Keyboard — Python 3.6.1 documentation
keyboardモジュールは、Pythonistaのカスタムキーボード("PyKeys")を拡張するための様々な関数を提供します。
Pythonista3 にカスタムできるキーボードが用意されており、keyboard
モジュールを使って色々とできるわけですね。
Documentation にはui.View
と書かれていて、難しそうな印象がありますね。
大丈夫です。安心してください☺️
もう、Pythonista3 に最低限のコードは用意されています。
サンプルコードExamples/Keyboard/Special Characters.py
以前2日目の記事にて、Examples
フォルダを紹介しました。Keyboard
内にたくさんのサンプルコードがあります。
Special Characters.py
を借用 & 一部変更をし実装します。
コード全貌
基本的に変更点は
-
characters
- 使いたい文字列
-
def layout
- キーのサイズを気持ち小さめに
-
clipboard
-
(?)
ヘルプの部分に、「📝」ペースト
-
程度で、残りはほぼサンプルコードを使っています。
私の環境では、Reformat Code を使うと、最終行に)
が実現してしまいます。細かく追ってないのですが。。。
import keyboard
import ui
import clipboard
# You can modify or extend this list to change the characters that are shown in the keyboard extension:
characters = [
'📝', '#', '-', '*', '/', '%', '`', '<', '>', '[', ']', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0'
]
class CharsView(ui.View):
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self.background_color = '#333'
self.scroll_view = ui.ScrollView(frame=self.bounds, flex='WH')
self.scroll_view.paging_enabled = True
self.scroll_view.shows_horizontal_scroll_indicator = False
self.add_subview(self.scroll_view)
self.buttons = []
for c in characters:
button = ui.Button(title=c)
button.font = ('<System>', 16)
button.background_color = (1, 1, 1, 0.1)
button.tint_color = 'white'
button.corner_radius = 4
button.action = self.button_action
self.scroll_view.add_subview(button)
self.buttons.append(button)
def layout(self):
rows = max(1, int(self.bounds.h / 36))
bw = 32 # 44
h = (self.bounds.h / rows) - 4
x, y = 2.5, 2
for button in self.buttons:
button.frame = (x, y, bw, h)
y += h + 4
if y + h > self.bounds.h:
y = 2
x += bw + 4
self.scroll_view.content_size = (
(len(self.buttons) / rows + 1) * (bw + 4) + 40, 0)
def button_action(self, sender):
if sender.title == '📝':
text = clipboard.get()
else:
text = sender.title
if keyboard.is_keyboard():
keyboard.play_input_click()
keyboard.insert_text(text)
else:
print('Keyboard input:', text)
def main():
v = CharsView(frame=(0, 0, 320, 40))
if keyboard.is_keyboard():
keyboard.set_view(v, 'current')
else:
# For debugging in the main app:
v.name = 'Keyboard Preview'
v.present('sheet')
if __name__ == '__main__':
main()
おこだわりを聞いておくれー
Extended Keyboard で恩恵を受けつつ、Python とMarkdown 双方がいい感じに書けるような配置にしてみています。
Extended Keyboard 上で、長押しすれば出てくるけども、使用頻度が高いキーに簡単にアクセスできるように。
いま、こうしてAdvent Calendar をPythonista3 で書けているのも、PyKeys あってこそですね☺️
Key | Python | Markdown |
---|---|---|
# |
コメント | ヘッダー |
- |
減算・マイナス | 箇条書きリスト |
* |
乗算 | 箇条書きリスト・強調 |
/ |
除算・ファイルパス区切り | - |
バッククォート | - | code |
> |
- | 引用 |
[ ・]
|
配列 | リンク |
これを派生させGLSL やJavaScript 用のキーボード設定なども作っています。
設定で「フルアクセスを許可」
ペースト機能をPyKeys で使用には、許可設定が必要です。
- 設定App(Pythonista3 App 内のsetting ではありません)を開く
- (下にずっーとスクロールしていった)Pythonista3 アイコンをタップ
- 「キーボード」 をタップ
- 「PyKeys」と「フルアクセスを許可」をアクティブ
「PyKeysに設定しても、ペーストができない😭」って方は、設定を確認してみてください。
ファイル新規作成で人生豊かに
ファイル名はどうでもいいから「とりあえず実行して、挙動や結果を知りたい」って場面ありませんか?
Untitled.py
でもいいけど(Pythonista3 のデフォルトファイル名)_1.py
, _2.py
... と続いていくのも気持ちがいいものではありません。また、見返してみると、Untitled
祭りで何がなんだかわからなくなりますし(矛盾)。
(またもや登場の)Editor Action を使って、いい感じに新規ファイル作成するスクリプトです。
- 新規ファイル作成Editor Action をタップ
- コンソール画面上に、作成案内のログが出現
- キーボードで、整数値入力
- return
-
YYMMDD_HHmm.py
として新規作成 - 新規タブで開かれる
新規ファイルは、Editor Action を実行時にアクティブなファイル階層に作成されます(ファイルパス取得できない場合、Documents
直下)。
コードの全貌
コード量の多さに面食らってしまうかもですが、大半がテンプレ用に貼り付ける文字列たちなので、実際はさほど多くありません。
from pathlib import Path
import arrow
from objc_util import ObjCClass
import editor
import console
# todo: set code templates
blank_value = ''
copy_value = str(editor.get_text() if editor.get_text() else blank_value)
ui_value = '''\
import ui
class View(ui.View):
def __init__(self):
self.bg_color = 1
def did_load(self):
pass
def will_close(self):
pass
def draw(self):
pass
def layout(self):
pass
def touch_began(self, touch):
pass
def touch_moved(self, touch):
pass
def touch_ended(self, touch):
pass
def keyboard_frame_will_change(self, frame):
pass
def keyboard_frame_did_change(self, frame):
pass
if __name__ == '__main__':
view = View()
view.present()
#view.present(hide_title_bar=True)
#view.present(style='fullscreen', orientations=['portrait'])
'''
shader_value = '''\
precision highp float;
uniform float u_time;
uniform vec2 u_sprite_size;
uniform float u_scale;
uniform sampler2D u_texture;
uniform vec4 u_tint_color;
uniform vec4 u_fill_color;
varying vec2 v_tex_coord;
void main(){
float t = u_time;
vec2 uv = v_tex_coord;
vec2 p = (uv- vec2(0.5)) *2.0;
uv = uv / 2.0 + vec2(0.5);
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)discard;
gl_FragColor = vec4(uv.x, uv.y, 0.0, 1.0);
//gl_FragColor = texture2D(u_texture,uv.xy);
}
'''
def get_feedback_generator():
"""
call feedback ex:
`UIImpactFeedbackGenerator.impactOccurred()`
"""
style = 4 # 0-4
UIImpactFeedbackGenerator = ObjCClass('UIImpactFeedbackGenerator').new()
UIImpactFeedbackGenerator.prepare()
UIImpactFeedbackGenerator.initWithStyle_(style)
return UIImpactFeedbackGenerator
class TemplateItem:
def __init__(self, prompt, code, extension='.py'):
self.prompt = prompt
self.code = code
self.extension = extension
def get_prompt(templates):
prompt = ''
for n, template in enumerate(templates):
prompt += f'{n} => {template.prompt},\n'
prompt += 'select a num, create a template file.\n'
return prompt
def get_dirpath():
fliepath = editor.get_path()
if fliepath:
dir_path = Path(f'{fliepath}').parent
else:
_home = Path('/').home()
dir_path = _home / 'Documents'
return dir_path
def get_nowtime():
utc = arrow.utcnow().to('JST')
now_str = utc.format('YYMMDD_HHmm')
return now_str
def create_flie(templates):
prompt = get_prompt(templates)
feedback = get_feedback_generator()
try:
choose_num = int(input(prompt))
except:
print('Select the number(int) displayed. ')
feedback.impactOccurred()
return
if choose_num == None:
print('I can\'t find the number I chose.')
feedback.impactOccurred()
return
choose_template = templates[choose_num]
now_time = get_nowtime()
root_dir = get_dirpath()
new_file = Path(root_dir, f'{now_time}{choose_template.extension}')
new_file.write_text(choose_template.code, encoding='utf-8')
console.clear()
editor.open_file(str(new_file), new_tab=True)
console.hide_output()
feedback.impactOccurred()
def main():
blank = TemplateItem('blank', blank_value)
copy = TemplateItem('copy', copy_value)
ui = TemplateItem('ui', ui_value)
shader = TemplateItem('shader', shader_value, '.js')
template_list = [blank, copy, ui, shader]
create_flie(template_list)
if __name__ == '__main__':
main()
コードのないよう
上から下に順を追って説明していきます。文法よりも、何をしてるのかベースですので、書き換える際の参考になればと思います。
エラーハンドリングがかなりガバです。気軽に気になる所は教えてください🙇
copy_value
editor
モジュールを使い、編集中ファイルのコードをコピーしています。
文字として取得できない場合や、ファイルパス失敗でも空のファイル生成するようにif
で処理させています。
ui_value
shader_value
ui
モジュール(次回の記事予定)やシェーダー(GLSL)のテンプレートとして、準備してます。
使いいいテンプレートを作りたい場合は、ここを書き換えるか、参考にして追加する事も可能です。
class TemplateItem
不適切な書き方かもしれませんが、オブジェクトとして.
呼び出しをしたかったので、class で作っています。
辞書のdir['key']
がしっくりこなかったのが理由です。
def get_prompt(templates):
console画面に操作指示として出す役割です。enumerate
でindex としてカウントさせて、テンプレート一覧の配列呼び出しと同期できるようにしてます。
def get_dirpath():
もしもファイルパス取得が失敗した場合、Script Library 直下にファイルを作成できるようにしてます。
def get_nowtime():
arrow — Python 3.6.1 documentation
arrow
モジュールがPythonista3 には、標準で入っているのでtime
モジュールがNG な理由はありません(雰囲気)。
def create_flie(templates):
このスクリプトの心臓部です。
Pythonista3 的な言及ですが、editor
モジュールにもeditor.make_new_file([name, content])
として、ファイル作成関数は用意されています。
しかし、.py
ファイルしか生成してくれないご様子なのです(自動保存してくれるので便利なのですが)。
今回の処理ではpathlib.Path.write_text
で、事前に設定しているpathlib
のオブジェクトにwrite_text
してあげるかたちをとっています(なんとなくpathlib
が好き)。
また、
console — Utilities for Console Output and Various System Services — Python 3.6.1 documentation
console
モジュールを使い、console 画面の文字を消したり引っ込めたりしています。
def main():
テンプレートの選択ができるように、TemplateItem
のインスタンスを配列に格納しています。
TemplateItem
の引数として
-
prompt
- console に表示させたい文字列
-
code
- テンプレートとして、新規ファイルに書き込みたい文字列
-
extension
- 新規ファイルの拡張子
- 指定しない場合は
.py
また、ui
と変数名を指定しまっていますが、ui.View
使わないし、main
関数内スコープだからヨシ!としてしまってます(よくない)
次回は
このスクリプトたちで、私の人生は豊かになっています☺️
そんな、豊かになる秘訣をみなさまに共有ができて嬉しいです。
iPhone でコードを書くという行為が、苦行のように見える方もいらしゃるかもしれませんが、こうしたスクリプトに支えられ楽しくやってます😇
次回は、ui
モジュールについての記事です。PyKeys カスタマイズの処理や、テンプレートの中でも登場してきたui
を知るとPythonista3 の楽しさがより一層広がると思います。
ここまで、読んでいただきありがとうございました。
せんでん
Discord
Pythonista3 の日本語コミュニティーがあります。みなさん優しくて、わからないところも親身に教えてくれるのでこの機会に覗いてみてください。
書籍
iPhone/iPad でプログラミングする最強の本。
その他
- サンプルコード
Pythonista3 Advent Calendar 2022 でのコードをまとめているリポジトリがあります。
コードのエラーや変なところや改善点など。ご指摘やPR お待ちしておりますー
なんしかガチャガチャしていますが、お気兼ねなくお声がけくださいませー
やれるか、やれないか。ではなく、やるんだけども、紹介説明することは尽きないと思うけど、締め切り守れるか?って話よ!(クズ)
— pome-ta (@pome_ta93) November 4, 2022
Pythonista3 Advent Calendar 2022 https://t.co/JKUxA525Pt #Qiita
- GitHub
基本的にGitHub にコードをあげているので、何にハマって何を実装しているのか観測できると思います。