Pythonの特殊メソッド__new__とは

特殊メソッド __new__について

Pythonには、「特殊メソッド」と呼ばれるものが存在します。
Pythonではこの特殊メソッドをオーバーライドし使用することで、自分が定義したクラスのインスタンスの振る舞いを細かく調整することが可能です。 その特殊メソッドの一つが__new__です。
__new__はインスタンスの生成時に呼ばれる特殊メソッドです。 他にも、インスタンスに関わる特殊メソッドとして、インスタンスの初期化時に呼ばれる私たちに馴染深い__init__メソッド、インスタンスの破壊時に呼ばれる__del__メソッドがあります。
__new__を考える上で 切り離せないのが特殊メソッド__init__です。__init__と__new__の役割の違いを見ていきましょう。

__new__と __init__の違い

__init__はどちらかというと他の言語(Javaなど)のコンストラクタ的なものなので、 __init__が呼び出されるとオブジェクトの生成+初期化が行われていると思われがちですが、Pythonの場合はオブジェクトの生成はnewメソッドで公開/カスタマイズされるもので、 __init__の役割はあくまで初期化です。(Javaなどはコンストラクタが呼び出されるとオブジェクトの生成が自動で行われ、コンストラクタ内の処理で初期化が行われます。)
なので、 __new__はインスタンス生成、__init__はインスタンス初期化という役割に明確な違いがあります。
当たり前ですが、名前通りの働きをしてくれていることになりますね。

__new__

  1. 新しいインスタンス生成時に呼ばれます。
  2. 第一引数にはクラス自身を表すclsをとります。

__init__

  1. initという言葉通りインスタンスが生成された後に初期化時に呼び出されます。
  2. 第一引数には生成されたインスタンス自身を表すselfをとります。

利用例

以下で __new__を使用する場合の利用例を示していきます。

1.シングルトンパターン

シングルトンパターンとはGoFデザインパターンの一種で生成されるインスタンスを一つに制限することです。
ロケールなどの処理で使用されるデザインパターンだそうですが、使われるのはあまり見ないかもしれません。

Singleton パターン - Wikipedia

# main.py
class Singleton():
    singleton = None

    # *argがないとSomeClassの__init__で引数を指定できない
    def __new__(cls, *arg, **kwargs):
        if cls.singleton is None:
            cls.singleton = super().__new__(cls)
        return cls.singleton

class SomeClass(Singleton):
    def __init__(self, name: str):
        self.__name = name

    @property
    def name(self):
        return self.__name

def main():
    a = SomeClass('Jone')
    print(f'a.name is {a.name}')
    print(a)

    b = SomeClass('Ken')
    print(f'a.name is {a.name}')
    print(f'b.name is {b.name}')
    print(a)
    print(b)

if __name__ == '__main__':
    main()
# 実行結果
a.name is Jone
<__main__.SomeClass object at 0x10214a150>
a.name is Ken
b.name is Ken
<__main__.SomeClass object at 0x10214a150>
<__main__.SomeClass object at 0x10214a150>

ちゃんと生成されたインスタンスが一つだけになっています。

2.イミュータブルなクラスの継承した時の初期化

strやtuppleなどイミュータブルなクラスを継承した場合はinitでは初期化できません。
そこで__new__を使用することによって擬似的に初期化(インスタンス生成時のカスタマイズ)を行います。

strの場合の例

修正前

# strの場合
class SomeClass(str):
    def __init__(self, args):
        self = 'Jone'

def main():
    a = SomeClass('Ken')
    print(a)
# 実行結果
Ken

修正後

class SomeClass(str):
    def __new__(cls, args):
        self = str.__new__(cls, 'Jone')
        return self

def main():
    a = SomeClass('Ken')
    print(a)
# 実行結果
Jone

tupleの場合の例

# tupleの場合
class SomeClass(tuple):
    def __init__(self, args):
        print(self[0] + self[1] + self[2])
        self[4] = 'San'

def main():
    SomeClass('Ken')
# 実行結果
TypeError: 'SomeClass' object does not support item assignment
class SomeClass(tuple):
    def __new__(cls, args):
        x = args[0]
        y = args[1]
        z = args[2]
        
        self = tuple.__new__(cls, (x, y, z, 'San'))
        return self

def main():
    a = SomeClass('Ken')
    print(a)
# 実行結果
('K', 'e', 'n', 'San')

こちらも積極的に使うことはないと思いますが、このようなことができます。

終わりに

今回はあまり見かけない特殊メソッド__new__について調べてみましたが、上の利用例の他にもメタプログラミングなどで__new__をよく使用することがあるみたいですが、あまりまだメタプログラミングについては詳しくないので割愛します。
調べた限りでは特殊メソッド__new__はそこまで積極的に使うことはなさそうですね。

Docker-Composeを使って5分でJupyterLabの環境構築

仕事でデータ分析とかもやっていかないといけなくて、勉強のための実行環境欲しいなと思って、自分のPCにJupyterLabをDocker-Composeを使って環境構築したので記事にしました。

実施環境

  • macOS Catalina(15-inch, 2016)
  • Docker for Macインストール済
  • Docker version 19.03.8
  • docker-compose version 1.25.4

JuptyerLabとは

ライブラリを使用したデータの可視化が簡単にでき、機械学習に必須の前処理やデータマイニングをデータを見ながら試行錯誤ができるという、データ分析には必須のIDEです。
JuptyerLabはJupyter notebookの後継でJupyter notebookの開発は一旦終了し,JupyterLabに移行すると公式アナウンスされています。

github.com

使用したDocker image

今回はJupyterLab公式のPythonとJuliaとRが使えて、デフォルトでpandasやscikit-learnなどのPythonライブラリが使えるdatascience-notebookというimageを使用します。Pythonしか使わない場合はscipy-notebookというimageがあるので、そちらを使った方が良いと思います。

他にもいろいろimageがありますので公式ドキュメントをご覧ください。
Selecting an Image — docker-stacks latest documentation

環境構築

  1. ローカルに今回の環境構築用のディレクトリを任意の場所に用意します。
  2. 1 で作ったディレクトリ配下に、workフォルダとdocker-compose.ymlを作成します。
  3. 作成したdocker-compose.ymlに以下のように記述します。
    version: "3"
    services:
      datascience-notebook:
        container_name: datascience-notebook-container
        # scipy-notebookの場合はjupyter/scipy-notebookになります。
        image: jupyter/datascience-notebook
        # コンテナの/home/jobyan/workをホストの./workで永続化
        volumes:
          - ./work:/home/jovyan/work
        ports:
          - 8888:8888
        restart: always
        # 今回はローカル起動用なのでログインは省略します。
        command: start.sh jupyter lab --NotebookApp.token=''
    
  4. terminalで 1 で作ったディレクトリ配下にて、初回は`docker-compose up --build`を実行しコンテナを起動します。(buildは結構時間がかかります。コマンドは次回以降`docker-compose up`でOK)
  5. http://localhost:8888/にアクセスして起動を確認します。 以下のような画面が出てきたら起動完了です。 f:id:okiyasi:20200712140752p:plain

動作確認

起動ができたら動作確認を行います。

  1. 左のworkフォルダをダブルクリックします。
  2. Luncherの中のNotebook Python3を選択すると以下のファイルが作成されます。 f:id:okiyasi:20200712141256p:plain
  3. 青い枠の中に以下のコードを書き込みます。
from sklearn.datasets import load_iris
import pandas as pd
iris = load_iris()
pd.DataFrame(iris.data, columns=iris.feature_names)

枠の中でShift + Enterを押し、以下のようなテーブルがちゃんと出てきたらOKです!

f:id:okiyasi:20200712141810p:plain

終わりに

Jupyter Labを使ったデータ分析の環境ができたので、本を使ったり、Kaggleをやったりしてデータ分析のスキルを身に付けていきたいですね!

O'Reilly Japan - Pythonによるデータ分析入門 第2版

Kaggle: Your Machine Learning and Data Science Community

AngularにおけるDIについて

Spring Frameworkを触っていたりすると、DIコンテナとか@Autowiredとか@Beanとか見慣れないものが登場しすぎて、 コード側でもDIの機構について理解しておかないとちゃんとコードが書けない!ってなるのですが、
Angularの場合は本当に簡単にDIが実現できてしまうので、そう言えばAngularのDIについてちゃんと知らない!と思ったので、公式ドキュメントを読みながら記事を書いてみることにしました。

1.DI(依存性の注入)とは

ここでは例としてインスタンスを生成の場合について考えます。

class ClassA {
    b :ClassB;

    constructor(){
        this.b = new ClassB();
    }

    doFuga() {
        this.b.doSomethingGreat();
    }
}

class ClassB {

    constructor() { }

    doSomethingGreat(){
        // 何かすごいことをやる
    }
}

上のコード(テキトーですが)のように、あるClassAの中で、ClassBのインスタンスが生成され、使用されている場合、
ClassAは既にClassBが実装されていないととクラスAを動かすことはできません。
また、ClassAはClassBの生成方法を知っていないといけません。
このような状態のことをClassAがClassBに依存しているといいます。

class ClassA {
    b :ClassB;

    constructor(b: ClassB){
        this.b = b
    }

    doFuga() {
        this.b.doSomethingGreat();
    }
}

しかしここで、クラスAの中でクラスBのインスタンスを直接生成するのではなく、外部から生成されたクラスBのインスタンスを渡してもらうことにしたらどうでしょうか?
クラスAがクラスBのインスタンスの生成方法を知らなくて良くなり、クラスAは渡されたクラスBのインスタンスを使用することだけを考えるだけで良くなります。
このデザインパターンがDIと呼ばれています。
DIによってまだクラスBが作られていなくても、同じインターフェイスを実装したクラスのインスタンスをクラスAに渡してあげることが可能なため、 テスト時はモックを使うなどが簡単に可能にできるようになります!

2.AngularのDIの基本

# Service側
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class HeroService {
  constructor() { }
}
# Component側
import { Component }   from '@angular/core';
import { Hero }        from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'app-hero-list',
  template: ``
})
export class HeroListComponent {
  heroes: Hero[];
 
  // heroService: HeroServiceと書くだけで勝手にHeroServiceが注入される!
  constructor(heroService: HeroService) {
    this.heroes = heroService.getHeroes();
  }
}

AngularではServiceに@Injectable()デコレーターをつけることによって注入可能なServiceをマークすることができます。
そしてServiceを要求するComponentはコンストラクターの引数にheroService: HeroServiceと書くだけで、起動時にHeroServiceクラスのインスタンスを一つだけ生成し、勝手にComponent側に注入してくれて、heroServiceを使用することが可能になります。 (めちゃめちゃ簡単ですね。。。)

3.Injectorについて

実は2の@Injectable()デコレータをServiceにつけるだけでは注入可能なServiceとして認識されるだけで本当はAngularは何もしてくれません。
2では、{providedIn: 'root'}というprovidedIn メタデータオプションによってServiceをroot Injectorに登録することで、このroot InjectorがServiceインスタンスをComponentに注入してくれています。
root InjectorがDIをしてくれている張本人になります。

root Injectorの他にもDOMごとやModuleごとにInjectorが存在します。それぞれElement InjectorModule Injectorと呼ばれています。
各Injectorはトークンプロバイダーマップというものを持っていて、トークン(マップのキー・DIトークンとも呼ばれている)が保持されています。
2の例ではHeroServiceというトークンがroot Injectorのトークンプロバイダーマップに保持されていて、起動時にHeroServiceクラスのインスタンスが一つだけ生成されます。
また、Injectorは階層構造になっていて、トークンがどのInjectorにあるのか探索を行います。Element Injectorに欲しいトークンが登録されていなかったら、Module Injectorの中を探しに行き、なかったらどんどん上の階層のModule Injectorを探索しにいくことになります。そして最上位の階層のInjectorにもいない場合root Injectorが呼ばれ参照されます。
それでもなかったら最後にNull Injectorというものが呼ばれNullInjectorErrorが吐かれます。(結構登録し忘れててNullInjectorError見たことある人いるのでは?!)

4.injectorの設定方法

@Injectable()も含め、以下3つのInjectorに設定する方法があります。
- @Injectable()のデコレータ内での設定
- @NgModule()のprovidersオプションでの設定
- @Component() デコレーターの中での設定
の3つです。順に説明していきます。

@Injectable()のデコレータ内での設定

root Injectorでなく任意のモジュールのInjectorに設定したい場合は、モジュールクラス名を指定することによって可能です。
下の例ではAuthModuleのModule InjectorにSessionServiceトークンが登録されます。

@Injectable({
  providedIn: AuthModule
})
class SessionService {
     constructor(private http: HttpClient) {}
     login() {
      // ログイン
     }
}

@NgModule()のprovidersオプションでの設定

NgModuleのprovidersに登録することで設定可能です。この場合もModule InjectorにServiceが登録されます。
SessionServiceは省略記法でproviderオブジェクトリテラル{ provide: SessionService, useClass: SessionService }に展開されます。 useClass等のオプションについては5で解説します。

@NgModule({
  providers: [
    // 省略記法で登録
    SessionService
  ],
})
export class AuthModule { }

// 注入
@Component({...})
class LoginComponent {
  constructor(
    service :SessionService,
  ) {}
}

@Component() デコレーターの中での設定

@Component() デコレーターの中でproviderを設定すると、Element Injectorにproviderが登録されます。

@Component({
  ...
  providers: [{ provide: ItemService, useValue: { name: 'lamp' } }]
})
export class TestComponent

Element Injector自体、そこまで多用することは多くないと思いますが、このようなユースケースがあるようです。

providerのオプション設定について

ここでは上で紹介したuseClassのような、providerでのオプションについて解説します。

Angular公式のコードが分かり易かったので拝借して説明したいと思います。

import { Component, Inject } from '@angular/core';

import { DateLoggerService } from './date-logger.service';
import { Hero }              from './hero';
import { HeroService }       from './hero.service';
import { LoggerService }     from './logger.service';
import { MinimalLogger }     from './minimal-logger.service';
import { RUNNERS_UP, runnersUpFactory }  from './runners-up';

const someHero = new Hero(42, 'Magma', 'Had a great month!', '555-555-5555');

@Component({
  selector: 'app-hero-of-the-month',
  templateUrl: './hero-of-the-month.component.html',
  providers: [
    { provide: Hero,          useValue:    someHero },
    { provide: TITLE,         useValue:   'Hero of the Month' },
    { provide: HeroService,   useClass:    HeroService },
    { provide: LoggerService, useClass:    DateLoggerService },
    { provide: MinimalLogger, useExisting: LoggerService },
    { provide: RUNNERS_UP,    useFactory:  runnersUpFactory(2), deps: [Hero, HeroService] }
  ]
})
export class HeroOfTheMonthComponent {
  logs: string[] = [];

  constructor(
      logger: MinimalLogger,
      public heroOfTheMonth: Hero,
      @Inject(RUNNERS_UP) public runnersUp: string,
      @Inject(TITLE) public title: string)
  {
    this.logs = logger.logs;
    logger.logInfo('starting up');
  }
}

Angular 日本語ドキュメンテーション

useClass

useClassプロバイダーキーを使用すると、指定したクラスの新しいインスタンスを1つ作成して注入することができます。
このuseClassを使用することで、クラスを代替クラスのインスタンスで置き換えることができます。 これにより元のクラスと異なる挙動で実装したり、クラスを拡張したり、テストでServiceをmockにできます。 上の実装例では{ provide: LoggerService, useClass: DateLoggerService },で使用されており、LoggerServiceトークンにDateLoggerServiceのインスタンスが紐づけられ、LoggerServiceの代わりにDateLoggerServiceが使用可能になります。

またテスト時には以下のように使用し、ServiceをMockServiceに変えることができます。

// テストの時の使用例 spec.ts
class MockUserService {
  isLoggedIn = true;
  user = { name: 'Test User'};
};

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      WelcomeComponent,
      { provide: UserService, useClass: MockUserService }
    ]
  });
  comp = TestBed.inject(WelcomeComponent);
  userService = TestBed.inject(UserService);
});

useValue

useValue キーを使用すると、固定値をトークンに関連付けることができます。
これによりServiceの代わりに、単体テスト内でモックデータを提供することなどができます。
上の実装例では{ provide: Hero, useValue: someHero }ではHeroトークンにHeroクラスの既存のインスタンスであるsomeHeroを紐づけています。(一見何してるのか分かりませんが、下のuseFactoryで再度登場します。)
{ provide: TITLE, useValue: 'Hero of the Month' },ではuseValueに文字列リテラルを指定します。
provide: TITLEのTITLEはもはやクラスでもなんでもないですが、これは代わりに InjectionTokenオブジェクトと呼ばれるものを使用しています。これによりクラス以外の文字列、関数、またはオブジェクトをDIすることが可能です。詳しくはこちら
@Inject(TITLE) public title: string)でtitleに値を注入しています。

またテストの場合は以下のように使用されます。

let userServiceStub: Partial<UserService>;

beforeEach(() => {
  // 作成したmock
  userServiceStub = {
    isLoggedIn: true,
    user: { name: 'Test User' },
  };

  TestBed.configureTestingModule({
     declarations: [ WelcomeComponent ],
     
     providers: [ { provide: UserService, useValue: userServiceStub } ],
  });

  fixture = TestBed.createComponent(WelcomeComponent);
  comp    = fixture.componentInstance;
  userService = TestBed.inject(UserService);
});

useFactory

useFactoryを使用すると、ファクトリー関数を呼び出してオブジェクトを作成し注入することができます。
上の{ provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] }では runnersUpFactory()がファクトリー関数です。

export function runnersUpFactory(take: number) {
  return (winner: Hero, heroService: HeroService): string => {
    /* ... */
  };
};

このようにファクトリー関数を実装することができ、runnersUpFactory()(winner: Hero, heroService: HeroService): string => { };というプロバイダーファクトリー関数を返しています。 上のuseValueでHeroトークンに注入したインスタンスやHeroServiceをプロバイダーファクトリー関数の引数として使用することが可能でdeps: [Hero, HeroService]で使用するものが 定義されています。

useExisting(エイリアスプロバイダー)

useExistingを使用すると、あるトークンを別のトークンにマッピングできます。つまり、useExisting は他のトークンのエイリアスとして動作します。

{ provide: LoggerService, useClass: DateLoggerService },
{ provide: MinimalLogger, useExisting: LoggerService },

上の{ provide: LoggerService, useClass: DateLoggerService },LoggerServiceトークンに対応するDateLoggerServiceインスタンスは生成されているため、MinimalLoggerにはLoggerServiceが参照しているDateLoggerServiceインスタンスが注入されます。 またこの使用例では

export abstract class MinimalLogger {
  logs: string[];
  logInfo: (msg: string) => void;
}

を定義しておくことによって、LoggerServiceがMinimalLoggerよりも遥かに多いプロパティやメソッド を持ってる場合、MinimalLoggerに定義されているプロパティとメソッドだけを使用できるように機能を制限することができます。

multi(おまけ)

一つ前の記事でInterceptorを扱いましたが、その時、app.moduleのproviders[]に{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }を登録しました。
このmultiというのはtrue を指定すると同じトークンで複数の依存オブジェクトを扱う事ができるようになるもので、HTTP_INTERCEPTORSトークンに対応する、既存のInterceptor処理のインスタンスが入った配列にTokenInterceptorのインスタンスをpushしていることになります。

終わりに

今回はAngularのDIについて、実際にある程度使ったり、理解に役立つ範囲で紹介しました。 この他にもInjector階層の探索のオプション(@Optional, @Skip)、バンドルサイズに関わるツリーシェイキングの話があるのですが、話が長くなりすぎてしまうので、今回は割愛することにします。

参考

Angular 日本語ドキュメンテーション

AngularにおけるInterceptorの使い方

実務でAngularのInterceptorを使ったらhttpClientの事前・事後処理を共通化できてとても便利だったので軽くInterceptorについてまとめてみました。 たまたま見つけるまで自分は知らなかったのですが、そこそこメジャーな機能なようです。

Interceptorとは?

Angular 4.3から登場した機能で httpclientに割り込み処理を入れることで、リクエストを投げる前やレスポンスを受け取った後に共通の処理を行うことができます。
以下で実務で使えそうなInterceptorでできることを紹介します。

リクエスト時の認証用ヘッダー追加処理を共通化

JWTを使った認証を利用する場合、Authorizationヘッダーにトークンをセットする必要がありますが、通信ごとにヘッダーを追加するのはとてもめんどくさいので interceptorを使ってヘッダーを追加する処理を共通化することができます。 認証用ヘッダー以外のヘッダーも同じ要領で追加することができます。

// src/app/common/auth/token-interceptor.ts

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { Observable } from 'rxjs';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

    constructor(private cookieService: CookieService ) { }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // Cookieに保存されているJWT(JSON Web Token)を取り出す
        const token = this.cookieService.get('jwt');
        const newRequest = request.clone({
            // 全てのリクエストのAuthorizationヘッダーにトークンをセット
            headers: request.headers.set(
                'Authorization', `Bearer ${token}`
            )
        });
        return next.handle(newRequest)
    }
}

HttpClientに適用するために、上で作成したInterceptorを既存のHTTP_INTERCEPTORSに追加する必要があります。 app,module.tsのprovidersに以下を追加することでHTTP_INTERCEPTORSに追加完了です。

// src/app/app.module.ts

import { TokenInterceptor } from './common/token-interceptor' ;
import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  providers: [
    // 新たに追加
    { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

レスポンス時のエラー処理を共通化

ここでは例として1で送ったレスポンスがセッションが切れるなどして、Status Code 401(Unauthorized)が帰ってきたときにログイン画面にルーティングするような処理を共通化します。

// src/app/common/auth/unauthorized-interceptor.ts

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, throwError} from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Router } from '@angular/router';

@Injectable()
export class UnauthorizedInterceptor implements HttpInterceptor {
    constructor(private router: Router) {}
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(request).pipe(
            catchError((error: HttpErrorResponse) => {
                if (error.status === 401) {
                    /**
                     * ログイン画面へのリダイレクトや
                     * 「セッションが切れました」というモーダルの表示などを行う
                     */
                    this.router.navigate(['login']);
                }
                return throwError(error);
            }));
    }
  }

今回も1と同じようにapp,module.tsに { provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true } を追加することで全体で使用可能になります。
また今回は1・2を分けるために別々で書きましたが、一連の処理を同じinterceptorに書くことも可能です。

終わりに

他にもInterceptorを使用することでキャッシュを行ったり、ダミーのレスポンスを返すことができるようですが、そこまで使うことはないと思うので今回は遠慮させていただきます! 興味がある方は調べてみてくださいね。

参考

Top 10 ways to use Interceptors in Angular | by Michael Karén | Angular In Depth | Medium