3

すべてが 1 つの基本クラスから継承される一連の関連クラスがあります。ファクトリ メソッドを使用して、これらのクラスのオブジェクトをインスタンス化したいと考えています。オブジェクトを呼び出し元に返す前に、クラス名をキーとする辞書にオブジェクトを格納できるため、これを行いたいと考えています。次に、特定のクラスのオブジェクトに対する要求がある場合、そのオブジェクトが辞書に既に存在するかどうかを確認できます。そうでない場合は、インスタンス化して辞書に追加します。もしそうなら、辞書から既存のオブジェクトを返します。これにより、基本的にモジュール内のすべてのクラスがシングルトンに変わります。

私がこれをしたいのは、それらすべてが継承する基本クラスがサブクラスの関数の自動ラッピングを行うためであり、関数が複数回ラップされることを望まないためです。同じクラスが作成されます。

これを行う唯一の__init__()方法は、常に呼び出される基本クラスのメソッドでスタック トレースをチェックし、スタック トレースがオブジェクトを作成する要求が工場機能。

これは良い考えですか?

編集:これが私の基本クラスのソースコードです。これをよりエレガントに実現するには、メタクラスを理解する必要があると言われましたが、今のところはこれだけです。すべての Page オブジェクトは同じ Selenium Webdriver インスタンスを使用します。これは、上部にインポートされたドライバー モジュールにあります。このドライバは、初期化に非常にコストがかかります。LoginPage が初めて作成されるときに初期化されます。初期化後、initialize()メソッドは新しいドライバーを作成する代わりに、既存のドライバーを返します。これは、ユーザーが LoginPage を作成することから始めなければならないという考え方です。最終的には何十もの Page クラスが定義され、それらは単体テスト コードによって使用され、Web サイトの動作が正しいことを検証します。

from driver import get_driver, urlpath, initialize
from settings import urlpaths

class DriverPageMismatchException(Exception):
    pass

class URLVerifyingPage(object):
    # we add logic in __init__() to check the expected urlpath for the page
    # against the urlpath that the driver is showing - we only want the page's
    # methods to be invokable if the driver is actualy at the appropriate page.
    # If the driver shows a different urlpath than the page is supposed to
    # have, the method should throw a DriverPageMismatchException

    def __init__(self):
        self.driver = get_driver()
        self._adjust_methods(self.__class__)

    def _adjust_methods(self, cls):
        for attr, val in cls.__dict__.iteritems():
            if callable(val) and not attr.startswith("_"):
                print "adjusting:"+str(attr)+" - "+str(val)
                setattr(
                    cls,
                    attr,
                    self._add_wrapper_to_confirm_page_matches_driver(val)
                )
        for base in cls.__bases__:
            if base.__name__ == 'URLVerifyingPage': break
            self._adjust_methods(base)

    def _add_wrapper_to_confirm_page_matches_driver(self, page_method):
        def _wrapper(self, *args, **kwargs):
            if urlpath() != urlpaths[self.__class__.__name__]:
                raise DriverPageMismatchException(
                    "path is '"+urlpath()+
                    "' but '"+urlpaths[self.__class.__name__]+"' expected "+
                    "for "+self.__class.__name__
                )
            return page_method(self, *args, **kwargs)
        return _wrapper


class LoginPage(URLVerifyingPage):
    def __init__(self, username=username, password=password, baseurl="http://example.com/"):
        self.username = username
        self.password = password
        self.driver = initialize(baseurl)
        super(LoginPage, self).__init__()

    def login(self):
        driver.find_element_by_id("username").clear()
        driver.find_element_by_id("username").send_keys(self.username)
        driver.find_element_by_id("password").clear()
        driver.find_element_by_id("password").send_keys(self.password)
        driver.find_element_by_id("login_button").click()
        return HomePage()

class HomePage(URLVerifyingPage):
    def some_method(self):
        ...
        return SomePage()

    def many_more_methods(self):
        ...
        return ManyMorePages()

ページが数回インスタンス化されても大したことではありません。メソッドは数回ラップされ、不要なチェックがいくつか行われますが、すべてが機能します。しかし、ページが何十回、何百回、何万回もインスタンス化されるのは良くありません。各ページのクラス定義にフラグを立てて、メソッドが既にラップされているかどうかを確認することもできますが、クラス定義を純粋かつクリーンに保ち、すべてのまやかしをページの奥深くに押し込むというアイデアが気に入っています。誰もそれを見ることができず、ただ機能する私のシステム。

4

4 に答える 4

3

Python では、何かを「強制」しようとする価値はほとんどありません。あなたが思いついたものは何でも、誰かがあなたのクラスにモンキーパッチを適用したり、ソースをコピーして編集したり、バイトコードをいじったりすることでそれを回避できます.

したがって、ファクトリを記述し、それをクラスのインスタンスを取得する正しい方法として文書化し、クラスを使用してコードを記述するすべての人が TOOWTDI を理解することを期待し、自分が何をしているかを本当に理解し、喜んでそうしない限り、TOOWTDI に違反しないことを期待します。結果を把握して対処します。

意図的な「誤用」ではなく、事故を防ごうとしているだけなら話は別です。実際、これは単なる標準的な契約による設計です。不変条件を確認してください。もちろん、この時点で、SillyBaseClassはすでに台無しになっており、修復するには遅assertすぎraiseますlog。しかし、それはあなたが望んでいることです。それはアプリケーションの論理エラーであり、プログラマーに修正してもらうしかないので、assertおそらくまさにあなたが望んでいることです.

そう:

class SillyBaseClass:
    singletons = {}

class Foo(SillyBaseClass):
    def __init__(self):
        assert self.__class__ not in SillyBaseClass.singletons

def get_foo():
    if Foo not in SillyBaseClass.singletons:
        SillyBaseClass.singletons[Foo] = Foo()
    return SillyBaseClass.singletons[Foo]

物事がここまで進むのを本当に止めたい場合は、不変条件をメソッドの早い段階で確認できますが、「めちゃくちゃになった」が「核兵器を発射する」と同等でない限り、わざわざする必要はありません。__new__SillyBaseClass

于 2013-01-01T06:42:14.990 に答える
2

複雑なコードを追加して実行時にエラーを検出するのではなく、まず規則を使用して、モジュールのユーザーが自分で正しいことを実行できるようにします。

クラスに「プライベート」な名前 (アンダースコアを先頭に付ける) を付け、インスタンス化してはならないことを示唆する名前 (_Internal... など) を付けて、ファクトリ関数を「パブリック」にします。

つまり、次のようなものです。

class _InternalSubClassOne(_BaseClass):
    ...

class _InternalSubClassTwo(_BaseClass):
    ...

# An example factory function.
def new_object(arg):
    return _InternalSubClassOne() if arg == 'one' else _InternalSubClassTwo()

new_objectまた、「このクラスを手動でインスタンス化しないでください。ファクトリ メソッドを使用してください」のように、各クラスにドキュメント文字列またはコメントを追加します。

于 2013-01-01T09:00:28.563 に答える
2

実装を提供したいようです__new__: 次のようなもの:

class MySingledtonBase(object):
    instance_cache = {}
    def __new__(cls, arg1, arg2):
        if cls in MySingletonBase.instance_cache:
            return MySingletonBase.instance_cache[cls]
        self = super(MySingletonBase, cls).__new__(arg1, arg2)
        MySingletonBase.instance_cache[cls] = self
        return self
于 2013-01-01T06:32:46.320 に答える