IT TIP

Python의 클래스 팩토리

itqueen 2020. 11. 20. 17:33
반응형

Python의 클래스 팩토리


저는 Python을 처음 사용하며 아래 시나리오를 구현하는 몇 가지 조언이 필요합니다.

두 개의 다른 등록 기관에서 도메인을 관리하기위한 두 가지 클래스가 있습니다. 둘 다 동일한 인터페이스를 가지고 있습니다.

class RegistrarA(Object):
    def __init__(self, domain):
        self.domain = domain

    def lookup(self):
        ...

    def register(self, info):
        ...

class RegistrarB(object):
    def __init__(self, domain):
        self.domain = domain

    def lookup(self):
        ...

    def register(self, info):
        ...

도메인 이름이 주어진 경우 확장을 기반으로 올바른 등록자 클래스를로드하는 도메인 클래스를 만들고 싶습니다.

com = Domain('test.com') #load RegistrarA
com.lookup()

biz = Domain('test.biz') #load RegistrarB
biz.lookup()

공장 기능 (아래 참조)을 사용하여이 작업을 수행 할 수 있다는 것을 알고 있지만 이것이 가장 좋은 방법입니까 아니면 OOP 기능을 사용하는 더 좋은 방법이 있습니까?

def factory(domain):
  if ...:
    return RegistrarA(domain)
  else:
    return RegistrarB(domain)

기능을 사용하는 것이 좋다고 생각합니다.

더 흥미로운 질문은로드 할 레지스트라를 어떻게 결정합니까? 한 가지 옵션은 하위 클래스를 구체적으로 구현 한 다음 클래스 메서드 __subclasses__()호출 을 반복하는 추상 기본 Registrar 클래스를 갖는 is_registrar_for()것입니다.

class Registrar(object):
  def __init__(self, domain):
    self.domain = domain

class RegistrarA(Registrar):
  @classmethod
  def is_registrar_for(cls, domain):
    return domain == 'foo.com'

class RegistrarB(Registrar):
  @classmethod
  def is_registrar_for(cls, domain):
    return domain == 'bar.com'


def Domain(domain):
  for cls in Registrar.__subclasses__():
    if cls.is_registrar_for(domain):
      return cls(domain)
  raise ValueError


print Domain('foo.com')
print Domain('bar.com')

이렇게하면 새를 투명하게 추가 Registrar하고 각 도메인이 지원하는 결정을 그들에게 위임 할 수 있습니다.


RegistrarARegistrarB 는 기능을 공유하고 Abstract Base Class 에서 파생 될 수 있지만 다른 등록 기관에 대해 별도의 클래스가 필요하다고 가정하면 (예에서는 명확하지 않지만) 솔루션이 괜찮아 보입니다 .

factory함수 의 대안으로 등록자 클래스에 매핑되는 사전을 지정할 수 있습니다.

Registrar = {'test.com': RegistrarA, 'test.biz': RegistrarB}

그때:

registrar = Registrar['test.com'](domain)

한 가지 문제 : 클래스가 아닌 인스턴스를 반환하므로 여기서 클래스 팩토리를 실제로 수행하는 것이 아닙니다.


Python에서는 실제 클래스를 직접 변경할 수 있습니다.

class Domain(object):
  def __init__(self, domain):
    self.domain = domain
    if ...:
      self.__class__ = RegistrarA
    else:
      self.__class__ = RegistrarB

그리고 다음이 작동합니다.

com = Domain('test.com') #load RegistrarA
com.lookup()

이 접근 방식을 성공적으로 사용하고 있습니다.


'래퍼'클래스를 만들고 __new__()메서드를 오버로드 하여 특수 하위 클래스의 인스턴스를 반환 할 수 있습니다. 예 :

class Registrar(object):
    def __new__(self, domain):
        if ...:
            return RegistrarA(domain)
        elif ...:
            return RegistrarB(domain)
        else:
            raise Exception()

또한 다른 답변에서 제기 된 문제인 상호 배타적이지 않은 조건을 처리하기 위해 자신에게 가장 먼저 물어볼 질문은 디스패처 역할을하는 래퍼 클래스가 조건을 제어 할 것인지, 아니면 전문 클래스에 위임합니다. 전문 클래스가 자신의 조건을 정의하는 공유 메커니즘을 제안 할 수 있지만 래퍼는 이와 같이 유효성 검사를 수행합니다 (각 특수 클래스가 특정 도메인에 대한 등록자인지 여부를 확인하는 클래스 메서드 is_registrar_for (. ..) 다른 답변에서 제안) :

class Registrar(object):
    registrars = [RegistrarA, RegistrarB]
    def __new__(self, domain):
        matched_registrars = [r for r in self.registrars if r.is_registrar_for(domain)]

        if len(matched_registrars) > 1:
            raise Exception('More than one registrar matched!')
        elif len(matched_registrars) < 1:
            raise Exception('No registrar was matched!')
        else:
            return matched_registrars[0](domain)

나는 항상이 문제가 있습니다. 응용 프로그램 (및 해당 모듈)에 포함 된 클래스가있는 경우 함수를 사용할 수 있습니다. 그러나 플러그인을 동적으로로드하는 경우 메타 클래스를 통해 자동으로 클래스를 팩토리에 등록하는보다 동적 인 것이 필요합니다.

다음은 원래 StackOverflow에서 해제했다고 확신하는 패턴이지만 여전히 원래 게시물에 대한 경로가 없습니다.

_registry = {}

class PluginType(type):
    def __init__(cls, name, bases, attrs):
        _registry[name] = cls
        return super(PluginType, cls).__init__(name, bases, attrs)

class Plugin(object):
    __metaclass__  = PluginType # python <3.0 only 
    def __init__(self, *args):
        pass

def load_class(plugin_name, plugin_dir):
    plugin_file = plugin_name + ".py"
    for root, dirs, files in os.walk(plugin_dir) :
        if plugin_file in (s for s in files if s.endswith('.py')) :
            fp, pathname, description = imp.find_module(plugin_name, [root])
            try:
                mod = imp.load_module(plugin_name, fp, pathname, description)
            finally:
                if fp:
                    fp.close()
    return

def get_class(plugin_name) :
    t = None
    if plugin_name in _registry:
        t = _registry[plugin_name]
    return t

def get_instance(plugin_name, *args):
    return get_class(plugin_name)(*args)

어때?

class Domain(object):
  registrars = []

  @classmethod
  def add_registrar( cls, reg ):
    registrars.append( reg )

  def __init__( self, domain ):
    self.domain = domain
    for reg in self.__class__.registrars:
       if reg.is_registrar_for( domain ):
          self.registrar = reg  
  def lookup( self ):
     return self.registrar.lookup()    

Domain.add_registrar( RegistrarA )
Domain.add_registrar( RegistrarB )

com = Domain('test.com')
com.lookup()

여기서 메타 클래스는 ENTITIES dict 에서 Registars 클래스를 암시 적으로 수집합니다.

class DomainMeta(type):
    ENTITIES = {}

    def __new__(cls, name, bases, attrs):
        cls = type.__new__(cls, name, bases, attrs)
        try:
            entity = attrs['domain']
            cls.ENTITIES[entity] = cls
        except KeyError:
            pass
        return cls

class Domain(metaclass=DomainMeta):
    @classmethod
    def factory(cls, domain):
        return DomainMeta.ENTITIES[domain]()

class RegistrarA(Domain):
    domain = 'test.com'
    def lookup(self):
        return 'Custom command for .com TLD'

class RegistrarB(Domain):
    domain = 'test.biz'
    def lookup(self):
        return 'Custom command for .biz TLD'


com = Domain.factory('test.com')
type(com)       # <class '__main__.RegistrarA'>
com.lookup()    # 'Custom command for .com TLD'

com = Domain.factory('test.biz')
type(com)       # <class '__main__.RegistrarB'>
com.lookup()    # 'Custom command for .biz TLD'

참고 URL : https://stackoverflow.com/questions/456672/class-factory-in-python

반응형