2015-10-23 9 views
5

एक आत्म परियोजना के लिए के लिए अजगर की स्ट्रिंग होना शामिल कार्यक्षमता को दोहराने के लिए प्रयास कर रहा है, मैं की तरह कुछ करना चाहता था:गैर तार

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() 

आप देख सकते हैं, मैं वास्तव में एक से अधिक उदाहरण की जरूरत नहीं है प्रति आईडी प्रजातियां (आईडी), लेकिन जब भी मैं उस आईडी के साथ एक एनिमल ऑब्जेक्ट बना रहा हूं, तो मैं एक बार बनाउंगा, और मुझे शायद Animal(somename, 3) कहने के लिए कई कॉल की आवश्यकता होगी।

कि हल करने के लिए, मैं क्या करने की कोशिश कर रहा हूँ एक वर्ग बनाने के लिए इतना है कि यह के 2 उदाहरण के लिए, के ए और बी मान लीजिए है, तो निम्न हमेशा सत्य है:

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

यह कुछ है कि पायथन स्ट्रिंग अक्षर के साथ करता है और इसे इंटर्नशिप कहा जाता है। उदाहरण:

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

कि प्रिंट सच (जब तक स्ट्रिंग काफी कम है अगर हम अजगर खोल सीधे उपयोग कर रहे हैं) निकलेगा।

मैं केवल अनुमान लगा सकता हूं कि कैसे सीपीथन यह करता है (इसमें शायद कुछ सी जादू शामिल है) इसलिए मैं इसका अपना संस्करण कर रहा हूं। अब तक मुझे मिल गया है:

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 

मेरे प्रश्न हैं:

क) मेरी समस्या भी हल करने के लायक है? चूंकि मेरी इच्छित प्रजाति वस्तु काफी हल्का वजन होनी चाहिए और अधिकतम मात्रा में पशु को बुलाया जा सकता है, बल्कि सीमित (एक पॉकेटम गेम की कल्पना करें: 1000 से अधिक उदाहरण, शीर्ष)

बी) यदि यह है, तो यह है मेरी समस्या को हल करने के लिए वैध दृष्टिकोण?

सी) यदि यह मान्य नहीं है, तो क्या आप इसे हल करने के लिए एक सरल/क्लीनर/अधिक पाइथोनिक तरीके से विस्तृत कर सकते हैं?

उत्तर

1

हां, __new__ विधि को लागू करना जो एक कैश किए गए ऑब्जेक्ट को लौटाता है, उदाहरणों की सीमित संख्या बनाने का उचित तरीका है। यदि आप बहुत से उदाहरण बनाने की उम्मीद नहीं करते हैं, तो आप केवल __eq__ लागू कर सकते हैं और पहचान के बजाय मूल्य से तुलना कर सकते हैं, लेकिन इसके बजाय इसे ऐसा करने में कोई दिक्कत नहीं होती है।

ध्यान दें कि बाद वस्तु बना दिया गया है अपरिवर्तनीय बजाय वस्तु आम तौर पर __new__ में अपने सभी प्रारंभ करना चाहिए, __init__, बाद के बाद से कहा जाता है। इसके अलावा, __init__ को कक्षा के किसी भी उदाहरण पर कॉल किया जाएगा जो __new__ से वापस लौटाया गया है, इसलिए जब आप कैशिंग कर रहे हों, तो प्रत्येक बार एक कैश ऑब्जेक्ट वापस लौटाया जाएगा।

इसके अलावा, __new__ को पहला तर्क वर्ग वस्तु नहीं एक उदाहरण है, तो आप शायद यह cls बजाय self नाम रखना चाहिए (आप selfinstance के बजाय बाद में उपयोग कर सकते हैं विधि में आप हालांकि चाहते हैं!)।

0

इसे यथासंभव सामान्य बनाने के लिए, मैं कुछ चीजों की सिफारिश करने जा रहा हूं। एक, 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 

आप सामान्य पुस्तकालयों के लिए होना शामिल करने के लिए (जहाँ उपयोगकर्ताओं को पिरोया जा सकता है चाहते हैं, और आप उन पर भरोसा नहीं कर सकते हैं जो अपरिवर्तनीयता को तोड़ने के लिए नहीं हैं), उपरोक्त की तरह कुछ काम करने के लिए एक बुनियादी संरचना है। यह तेजी से है, भले ही निर्माण हेवीवेट (संभवत: एक ऑब्जेक्ट को एक से अधिक बार पुनर्निर्माण के लिए और सभी को एक फेंकने के बदले में स्टॉल के अवसर को कम करें, यदि कई धागे इसे पहली बार पहली बार बनाने की कोशिश करते हैं),

बेशक, अगर निर्माण सस्ता है और उदाहरणों छोटे हैं, तो बस लिखना एक __eq__ (और संभवतः __hash__ अगर यह तार्किक अपरिवर्तनीय है) और इसके साथ किया जाना,

+0

आप के अजगर के क्रियान्वयन में कोड का एक काफी समान तरह देख सकते हैं 'functools.lru_cache' के लिए विभिन्न [' रैपर '] (https://hg.python.org/cpython/file/3.5/Lib/functools.py#l453)। जैसे ही होता है, जब वे काम परमाणु होते हैं, तो वे "परमाणु" संचालन के लिए लॉक नहीं होते हैं, जो उपर्युक्त मेरी टिप्पणी का समर्थन करता है, लेकिन वे इसका उपयोग करते हैं "कैश से लॉक होने की कोशिश करें, अगर विफल हो, लॉक रिलीज करें, महंगा प्रदर्शन करें कार्य, लॉक पुनः प्राप्त करें और यदि कैश नहीं किया गया तो कैश अपडेट करें "कैश आकार के लिए ऊपर इस्तेमाल किया गया पैटर्न सीमित है (और संचालन के समूह परमाणु रूप से प्रदर्शन किया जाना चाहिए)। – ShadowRanger