2015-10-23 6 views
5

のためのPythonの文字列のインターンの機能を複製しようとすると、私のような何かやってみたかった:自己のプロジェクトのために非文字列

class Species(object): # immutable. 
    def __init__(self, id): 
     # ... (using id to obtain height and other data from file) 
    def height(self): 
     # ... 

class Animal(object): # mutable. 

    def __init__(self, nickname, species_id): 
     self.nickname = nickname 
     self.species = Species(id) 
    def height(self): 
     return self.species.height() 

をあなたが見ることができるように、私は本当にの複数のインスタンスを必要としません。 IDごとの種(id)が、そのIDを持つAnimalオブジェクトを作成するたびに作成していますが、おそらくAnimal(somename, 3)という複数の呼び出しが必要です。私は何をしようとしている、それを解決するために

は、それの2つのインスタンスのために、のは、aとbを言わせようにクラスを作ることです、次は常に真である:

(a == b) == (a is b) 

これは何かでありますPythonは文字列リテラルで行い、インターンシップと呼ばれます。例:

a = "hello" 
b = "hello" 
print(a is b) 

この印刷は真(私たちがPythonシェルを直接使用している場合は文字列が十分短い場合)になります。

私はCPythonがどのようにこれを行うのかを推測することができます。これまでのところ私が持っている:

class MyClass(object): 

    myHash = {} # This replicates the intern pool. 

    def __new__(cls, n): # The default new method returns a new instance 
     if n in MyClass.myHash: 
      return MyClass.myHash[n] 

     self = super(MyClass, cls).__new__(cls) 
     self.__init(n) 
     MyClass.myHash[n] = self 

     return self 

    # as pointed out on an answer, it's better to avoid initializating the instance 
    # with __init__, as that one's called even when returning an old instance. 
    def __init(self, n): 
     self.n = n 

a = MyClass(2) 
b = MyClass(2) 

print a is b # <<< True 

は、私の質問は以下のとおりです。

a)の解決にも価値が私の問題ですか?私の意図するSpeciesオブジェクトは非常に軽量で、最大の時間でなければならないので、動物はかなり制限されています(ポケモンのゲームを想像してください:1000以上のインスタンス、トップス)

b)私の問題を解決する有効なアプローチ?

c)有効でない場合は、この問題を解決するために、もっとシンプルで洗練された方法を詳しく教えてください。

答えて

1

はい、キャッシュされたオブジェクトを返すメソッド__new__を実装することは、限られた数のインスタンスを作成する適切な方法です。多くのインスタンスを作成することを期待していない場合は、__eq__を実装してIDではなく値で比較することができますが、代わりにこのようにすることは害ではありません。

不変オブジェクトは、__init__でなく、__new__で初期化するのが一般的であることに注意してください。後者は、オブジェクトの作成後に呼び出されるためです。さらに、__init__は、__new__から返されるクラスのインスタンスで呼び出されるため、キャッシュするときは、キャッシュされたオブジェクトが返されるたびに呼び出されます。

はまた、__new__の最初の引数は、クラスのオブジェクトインスタンスでないので、あなたは、おそらくそれがclsいうよりself名前を付ける必要があります(あなたががしたい場合は、この方法では、後self代わりinstanceのを使用することができます!)。

0

できるだけ一般的にするために、私はいくつかのことをお勧めします。 1つは、 "真の"不変性を望む場合はnamedtupleから継承します(通常、人々はこれについて手を離していますが、インターンをしているときは、不変の不変条件を破ると大きな問題を引き起こす可能性があります)。次に、ロックを使用してスレッドセーフな動作を許可します。

これはかなり複雑なので、私はそれを説明するコメントをSpeciesコードの修正されたコピーを提供するつもりです:

import collections 
import operator 
import threading 

# Inheriting from a namedtuple is a convenient way to get immutability 
class Species(collections.namedtuple('SpeciesBase', 'species_id height ...')): 
    __slots__ =() # Prevent creation of arbitrary values on instances; true immutability of declared values from namedtuple makes true immutable instances 

    # Lock and cache, with underscore prefixes to indicate they're internal details 
    _cache_lock = threading.Lock() 
    _cache = {} 

    def __new__(cls, species_id): # Switching to canonical name cls for class type 
     # Do quick fail fast check that ID is in fact an int/long 
     # If it's int-like, this will force conversion to true int/long 
     # and minimize risk of incompatible hash/equality checks in dict 
     # lookup 
     # I suspect that in CPython, this would actually remove the need 
     # for the _cache_lock due to the GIL protecting you at the 
     # critical stages (because no byte code is executing comparing 
     # or hashing built-in int/long types), but the lock is a good idea 
     # for correctness (avoiding reliance on implementation details) 
     # and should cost little 
     species_id = operator.index(species_id) 

     # Lock when checking/mutating cache to make it thread safe 
     try: 
      with cls._cache_lock: 
       return cls._cache[species_id] 
     except KeyError: 
      pass 

     # Read in data here; not done under lock on assumption this might 
     # be expensive and other Species (that already exist) might be 
     # created/retrieved from cache during this time 
     species_id = ... 
     height = ... 
     # Pass all the values read to the superclass (the namedtuple base) 
     # constructor (which will set them and leave them immutable thereafter) 
     self = super(Species, cls).__new__(cls, species_id, height, ...) 

     with cls._cache_lock: 
      # If someone tried to create the same species and raced 
      # ahead of us, use their version, not ours to ensure uniqueness 
      # If no one raced us, this will put our new object in the cache 
      self = cls._cache.setdefault(species_id, self) 
     return self 

あなたは、一般的なライブラリのためのインターン行いたい場合は(ここで、ユーザーが通されるかもしれません、あなたが不変性不変を破らないようにそれらを信じることはできません)、上記のようなものは、一緒に働く基本的な構造です。それは速く、工事が重い(たとえ多くのスレッドが一度に初めてそれを構築しようとすると、オブジェクトを2回以上再構築し、1つのコピーを除くすべてを破棄することと引き換えに)屋台の機会を最小限に抑えます。

建設が安いとインスタンスが小さい場合はもちろん

、そしてちょうどあなたがのPythonの実装では、コードの非常に似て並べ替えを見ることができる__eq__を書く(そしておそらく__hash__それは論理的に不変の場合)と、それを行うことが、

+0

さまざまな '' functools.lru_cache'のための ''ラッパー ''(https://hg.python.org/cpython/file/3.5/Lib/functools.py#l453)です。それが起こると、上記の私のコメントをサポートしている原子的な作業では "原子的"操作のためにロックされませんが、 "ロックでキャッシュから取得しようとすると、失敗した場合はロックを解除し、キャッシュされたサイズに対して上記で使用されたパターンは制限されています(また、グループはアトミックに実行する必要があります)。 – ShadowRanger

関連する問題