画像を ASCII 表示させる方法
はじめに
テストに用いるデータなど、サーバ上にあるイメージ(画像)を確認したいときないですか?このファイルなんだっけ?みたいな。そんなとき、jp2a を使えば画像を ASCII 表示できます。テキストで表示されるので、手軽にコンソールからも確認できるってことです。
Talinx/jp2a: Converts jpg/png images to ASCII
続きを読むPythonで読み取り専用コレクションを実装する
今回は、Pythonで読み取り専用コレクションのメンバ変数を実装する方法を解説します。
概要
通常、読み取り専用のメンバ変数を実装するときは、メンバ変数をプライベートにしつつ、getterのみ宣言して、setterを宣言しないようにします。
class Name: def __init__(self): self._firstname = 'Fugataro' @property def firstname(self): return self._firstname name = Name() print(name.firstname) name.firstname = 'Hogemi' # Fugataro # AttributeError: can't set attribute
注意が必要なのは、list
やdict
などのコレクション型変数をメンバ変数とする場合です。
上記と同様に、getterのみ宣言して、setterを宣言しないことにより、コレクション型のメンバ変数を別のコレクション型変数に置き換えることは出来ません。
class NumbersList: def __init__(self): self._data = [1, 2, 3] @property def data(self): return self._data numbers = NumbersList() print(numbers.data) numbers.data = [4, 5, 6] # [1, 2, 3] # AttributeError: can't set attribute
しかし、append()
やremove()
などでコレクション型メンバ変数の中身を書き換えることが出来てしまっています。これは、getterのみの宣言により、メンバ変数そのものの置き換えは出来ないようになりましたが、コレクション型メンバ変数の関数はいまだ機能しているため、結果としてそれら関数により、中身の書き換えが出来てしまっているのです。
class NumbersList: def __init__(self): self._data = [1, 2, 3] @property def data(self): return self._data numbers = NumbersList() print(numbers.data) numbers.data.append(4) numbers.data.extend([5, 6]) numbers.data.remove(1) print(numbers.data) # [1, 2, 3] # [2, 3, 4, 5, 6]
通常、この書き換えを防ぐために、別途用意されている読み取り専用のコレクションクラスを利用します。
Pythonの場合、読み取り専用のコレクションクラスは用意されていません。しかし、基本的なコレクションであるlist
、dict
、set
に対して、以下を用いることにより、代用可能です。
通常 | 読み取り専用 |
---|---|
list | tuple |
dict | types.MappingProxyType |
set | frozenset |
実装方法
list
、dict
、set
に対して、対応する代用のコレクションにてキャストすればよいです。以下にサンプルソースを記載します。
tuple
class ReadOnlyList: def __init__(self): self._data = tuple([1, 2, 3]) @property def data(self): return self._data l = ReadOnlyList() print(l.data) print(l.data[0]) print(l.data[1]) print(l.data[2]) for i in l.data: print(i) l.data.append(4) # (1, 2, 3) # 1 # 2 # 3 # 1 # 2 # 3 # AttributeError: 'tuple' object has no attribute 'append'
types.MappingProxyType
import types class ReadOnlyDict: def __init__(self): d = {'msg': 'Hello,World!', 'param': 1} self._data = types.MappingProxyType(d) @property def data(self): return self._data d = ReadOnlyDict() print(d) print(f'msg: {d.data["msg"]}') print(f'param: {d.data["param"]}') for key in d.data: print(key) for value in d.data.values(): print(value) for key, value in d.data.items(): print(key, value) d.data['name'] = 'Foge' # <__main__.ReadOnlyDict object at 0x0000024B239D7D88> # msg: Hello,World! # param: 1 # msg # param # Hello,World! # 1 # msg Hello,World! # param 1 # TypeError: 'mappingproxy' object does not support item assignment
frozenset
class ReadOnlySet: def __init__(self): s = set((1, 2, 3)) self._data = frozenset(s) @property def data(self): return self._data s = ReadOnlySet() print(s.data) for i in s.data: print(i) s.data.add(4) # frozenset({1, 2, 3}) # 1 # 2 # 3 # AttributeError: 'frozenset' object has no attribute 'add'
Pythonで抽象クラスを実装する
今回は、Pythonで抽象クラスを実装する方法を解説します。
抽象クラスとは?
プログラミング言語によりある程度違いはありますが、抽象クラスとは、以下の特徴を持ったクラスになります。
- 継承されることを前提としています。
- 空実装のメソッドが定義されています。(これを抽象メソッドと呼びます。)
- このクラスを継承したクラスは、抽象メソッドを再定義(オーバーライド)しなければなりません。
よって、抽象クラスは子クラスに対してメソッドの定義を強制する機能を持っています。
用途としては、多態性(ポリモーフィズム)を実装するときに、メソッドの実装漏れ、引数または戻り値の違いによるエラーの回避、などがあります。
実装方法
ここでは、抽象クラスと抽象メソッドの実装方法を解説します。
まず、抽象クラスの実装方法はABCMeta
というメタクラスを用います。抽象クラスにしたいクラスのメタクラスにABCMeta
を設定することにより、そのクラスが抽象クラスになります。(ちなみに、ABC
とはAbstract Base Class
の略だそうです。)
from abc import ABCMeta class vehicle(metaclass = ABCMeta): pass
また、PythonにはABC
というクラスがあり、このクラスを継承することでも抽象クラスを実現することが可能です。
from abc import ABC class vehicle(ABC): pass
このABC
の実態は、ABCMeta
をメタクラスに設定しているだけのクラスです。よって、初めのコードと実質同じです。
次に抽象メソッドを実装するときは、@abstractmethod
デコレータを使用します。このデコレータを付与されたメソッドが抽象メソッドになります。抽象メソッドには実際の処理を記述しないので、pass
またはraise NotImplementedError()
を実装しておきます。(NotImplementedError()
とは、未実装エラーを表す例外です。)
from abc import ABCMeta from abc import abstractmethod class vehicle(metaclass = ABCMeta): """抽象クラスvehicleの定義。""" @abstractmethod def start(self): raise NotImplementedError() @abstractmethod def stop(self): raise NotImplementedError()
ABCMeta
には、抽象メソッドが実装されている場合にインスタンスを生成しようとすると、例外が発生する仕様になっています。よって、上記vehicle
のインスタンスを生成しようとすると、以下のように例外が発生します。
v = vehicle()
# TypeError: Can't instantiate abstract class vehicle with abstract methods start, stop
なお、ABCMeta
をメタクラスとしているが抽象メソッドが実装されていないクラスや、抽象メソッドは実装されているがABCMeta
をメタクラスとしていないクラスのインスタンスは生成出来てしまいます。抽象クラスを実装するときは、ABCMeta
をメタクラスにすることと抽象メソッドの実装をセットで行ってください。
以下のように、抽象クラスを継承して抽象メソッドをオーバーライドすれば、インスタンスの生成が出来るようになります。
from abc import ABCMeta from abc import abstractmethod class vehicle(metaclass = ABCMeta): """抽象クラスvehicleの定義。""" @abstractmethod def start(self): raise NotImplementedError() @abstractmethod def stop(self): raise NotImplementedError() class car(vehicle): """vehicleを継承したクラスcarの定義。""" def start(self): print("car start.") def stop(self): print("car stop.") class motorcycle(vehicle): """vehicleを継承したクラスmotorcycleの定義。""" def start(self): print("moto start.") def stop(self): print("moto stop.") my_car = car() my_car.start() my_car.stop() my_motorcycle = motorcycle() my_motorcycle.start() my_motorcycle.stop() # car start. # car stop. # moto start. # moto stop.
抽象メソッドを表すデコレータについては、@abstractmethod
以外にも存在します。ここでは詳細を省きますが、抽象メソッドを表すデコレータを以下に列挙しておきます。
@abstractmethod
@abstractclassmethod
@abstractstaticmethod
@abstractproperty
ちなみに、誤って抽象クラスのインスタンスを生成してしまっているコードをflake8
にかけてみましたが、こちらではなにも表示されませんでした。抽象クラスのインスタンス生成エラーは実行時にしかわからないようです。
参考
-
公式ドキュメントです。
-
こちらはWikipediaの抽象クラスに関する説明です。
【Android】コンストラクタに引数があるViewModelを使う
AndroidでViewModelを使用するとき、以下のようなコンストラクタに独自の引数があるViewModelを使用したいときがあります。今回は、その方法を解説します。
class MainViewModel(id: Int) : ViewModel()
独自Factoryの定義
ViewModelインスタンスの生成はViewModelのコンストラクタを直接呼び出すのではなくて、以下のようにFactoryクラスのcreateメソッドを呼び出して生成します。
if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { viewModel = mFactory.create(modelClass); }
よって、まずはコンストラクタに引数があるViewModelに対応したFactoryクラスを定義します。
class MainViewModel(id: Int) : ViewModel() { class Factory( private val id: Int ) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return MainViewModel(id) as T } } }
独自Factoryの使用
独自に定義したFactoryクラスは、そのインスタンスをViewModelProviderに渡す必要があります。ViewModelProviderのコンストラクタの中に、Factoryインスタンスを引数に持つコンストラクタがオーバーロードされているので、以下のようにそのコンストラクタを使用します。
class MainActivity : AppCompatActivity(R.layout.main_activity) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProvider( this, MainViewModel.Factory(0) ).get(MainViewModel::class.java) } }
以上のようにして、コンストラクタに引数があるViewModelを使用できます。
キー入力をスクリーンキャストする
はじめに
前回の記事「git worktree と peco」の実演(gif アニメ)にて、キー入力を表示するために [screenkey] というソフトウェアを使いました。使用するにあたり、少しコツ(バグフィックス?)が必要だったので、備忘のためここに記します。
[screenkey] のサイトより:
なお、今現在 [screenkey] は Wayland 環境では動作しないので、使用する場合は Xorg 環境で使用します。
続きを読む【Android】WorkManagerでバックグラウンド処理を行う
今回は、AndroidのWorkManager
でバックグラウンド処理を行う方法を解説します。
概要
WorkManager
を使用すると、処理をバックグラウンドで実行できます。WorkManager
を使用すると、以下のメリットがあります。
- 完了に時間がかかる処理でもアプリが止まらない。
- アプリを終了しても
WorkManager
の処理は継続する。
よって、WorkManager
は以下のような場合に使われます。
- ファイルのダウンロード/アップロード
- ログのアップロード
ライブラリを追加する
WorkManager
を使用するため、以下のようにライブラリを追加します。
dependencies { val work_version = "2.7.1" // Kotlin + coroutines implementation("androidx.work:work-runtime-ktx:$work_version") }
作業を定義する
まず、WorkManager
に処理させたい作業を定義します。Worker
を継承して、doWork
をオーバーライドします。そして、オーバーライドした doWork
の中で、WorkManager
に処理させたい作業を実行します。
class HeavyWorker( appContext: Context, workerParams: WorkerParameters ) : Worker(appContext, workerParams) { override fun doWork(): Result { // 重い処理 doHeavyWork() // 処理結果の返却 return Result.success() } }
処理の後に処理結果を返します。なお、返せる処理結果を以下の通りです。
Result.success()
: 作業成功。Result.failure()
: 作業失敗。Result.retry()
: 作業をリトライする。
WorkRequestを作成する
次に、さきほど定義した作業から WorkRequest を作成します。WorkRequest の作成方法は、以下の 2 通りあります。
val heavyWorkRequest: WorkRequest =
OneTimeWorkRequestBuilder<HeavyWorker>().build()
val heavyWorkRequest = OneTimeWorkRequest.from(HeavyWorker::class.java)
WorkRequestを登録する
最後に、WorkManager
に WorkRequest を登録します。登録されると、WorkRequest の作業がバックグラウンドで実行されます。
WorkManager .getInstance(context) .enqueue(heavyWorkRequest)