Help us understand the problem. What is going on with this article?

複数のモジュールそれぞれがKvLanguageで同じ名前のWidgetを定義した時に起こる事

More than 3 years have passed since last update.

Python自体がプログラムをモジュール化して名前空間の汚染を防ぐ仕組みを持っていても、KvLanguageにおいてはその恩恵にあずかれないようです。以下のプログラムで検証してみました。

KvLanguageで書いた部分は合成される

以下はimportした複数のモジュールが、同じ名前のWidgetをKvLanguageで定義していた時にどうなるか確かめるプログラムです。

mywidget_a.py

mywidget_a.py
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout


Builder.load_string(r"""
<MyWidget>:
    Button:
        text: r'mywidget_a'  #
        size_hint: 0.3, 0.1
        pos_hint: {r'center_x': 0.25, r'center_y': 0.5}  #
""")


class MyWidget(FloatLayout):

    def __init__(self, **kwargs):
        super(MyWidget, self).__init__(**kwargs)
        print(r'mywidget_a')  #


if __name__ == r'__main__':
    runTouchApp(MyWidget())

一旦、単独で実行

標準出力
mywidget_a

Screenshot at 2016-11-12 12:07:45.png

mywidget_b.py

mywidget_b.py
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout


Builder.load_string(r"""
<MyWidget>:
    Button:
        text: r'mywidget_b'  #
        size_hint: 0.3, 0.1
        pos_hint: {r'center_x': 0.75, r'center_y': 0.5}  #
""")


class MyWidget(FloatLayout):

    def __init__(self, **kwargs):
        super(MyWidget, self).__init__(**kwargs)
        print(r'mywidget_b')  #


if __name__ == r'__main__':
    runTouchApp(MyWidget())

同じく、単独で実行

標準出力
mywidget_b

Screenshot at 2016-11-12 12:13:39.png

同時にimportすると

both.py
from kivy.base import runTouchApp
import mywidget_a
import mywidget_b

runTouchApp(mywidget_b.MyWidget())
標準出力
mywidget_b

Screenshot at 2016-11-12 12:20:05.png
__init__はmywidget_bのものだけが呼ばれている(期待通り)のに対し、子Buttonはmywidget_aのものまで作られてしまいました(期待通りではない)。念のためにConsoleというツールを使ってみると
Screenshot at 2016-11-12 12:36:17.png
間違いなく合成されています。ということは

このままだと

合成を防ぐためにプログラム全体に渡ってWidgetのクラス名がかぶらぬ様に気を付けなければならないということになります。
最悪の場合はモジュール名を頭に付けてMyWidgetA_MyWidget,MyWidgetB_MyWidgetのような感じになるかもしれません。
なんとかそうしなくて済む方法はないでしょうか。

kivy.factory.Factory

どうやらこいつがKvLanguageにおけるWidgetの生成に関わってるようです。実際に使ってみます。
まず上のboth.pyは以下のように書く事ができます。

both.py
from kivy.base import runTouchApp
from kivy.factory import Factory
import mywidget_a
import mywidget_b

runTouchApp(Factory.MyWidget())
標準出力
mywidget_a

又、mywidget_a.pyは以下のように書くことができます。

mywidget_a.py
from kivy.base import runTouchApp
from kivy.factory import Factory


class MyWidget(Factory.FloatLayout):

    def __init__(self, **kwargs):
        super(MyWidget, self).__init__(**kwargs)
        button = Factory.Button(
            text=r'mywidget_a',
            size_hint=[0.3, 0.1],
            pos_hint={r'center_x': 0.25, r'center_y': 0.5})
        self.add_widget(button)
        print(r'mywidget_a')

if __name__ == r'__main__':
    runTouchApp(Factory.MyWidget())

見るとButtonやFloatLayoutがそれぞれのimport文無しで利用できているのが分かります。普通は

from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout

の様に利用するものだからです。他にもKvLanguage内で利用できる物はほとんどこのFactoryから利用できるようになっています。この[Package名やModule名が必要無いという利便性]の代償として、[別ModuleのWidgetとの名前衝突に気を付けなければならないという労]を払うと考えればいいのでしょうか?とにかく名前がかぶっていても何も言わないのでやっかいです。

どうやって名前の衝突を避けるか

FactoryにはunregisterというMethodがあり、最初はこれで合成を避けれると思ったのですが違いました。

both.py
from kivy.base import runTouchApp
from kivy.factory import Factory
import mywidget_a
Factory.unregister(r'MyWidget')  # mywidget_aのMyWidgetを登録解除?
import mywidget_b

runTouchApp(Factory.MyWidget())
標準出力
mywidget_b

Screenshot at 2016-11-12 12:20:05.png

というわけで解決できませんでした。

gotta_dive_into_python
programmingに關する文献を讀むのが好きなだけで實際に作った數は少ないエセprogrammer
https://github.com/gottadiveintopython/backup/tree/master/profile
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away