code

다중 생성자 : 파이썬 방식?

codestyles 2020. 12. 15. 19:20
반응형

다중 생성자 : 파이썬 방식?


데이터를 보유하는 컨테이너 클래스가 있습니다. 컨테이너가 생성 될 때 데이터를 전달하는 다양한 방법이 있습니다.

  1. 데이터가 포함 된 파일 전달
  2. 인수를 통해 데이터를 직접 전달
  3. 데이터를 전달하지 마십시오. 그냥 빈 컨테이너를 만듭니다.

Java에서는 세 개의 생성자를 만듭니다. Python에서 가능하다면 다음과 같이 표시됩니다.

class Container:

    def __init__(self):
        self.timestamp = 0
        self.data = []
        self.metadata = {}

    def __init__(self, file):
        f = file.open()
        self.timestamp = f.get_timestamp()
        self.data = f.get_data()
        self.metadata = f.get_metadata()

    def __init__(self, timestamp, data, metadata):
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

Python에서는 세 가지 분명한 솔루션을 볼 수 있지만 그중 어느 것도 예쁘지 않습니다.

A : 키워드 인수 사용 :

def __init__(self, **kwargs):
    if 'file' in kwargs:
        ...
    elif 'timestamp' in kwargs and 'data' in kwargs and 'metadata' in kwargs:
        ...
    else:
        ... create empty container

B : 기본 인수 사용 :

def __init__(self, file=None, timestamp=None, data=None, metadata=None):
    if file:
        ...
    elif timestamp and data and metadata:
        ...
    else:
        ... create empty container

C : 빈 컨테이너를 만들기위한 생성자 만 제공합니다. 다양한 소스의 데이터로 컨테이너를 채우는 방법을 제공합니다.

def __init__(self):
    self.timestamp = 0
    self.data = []
    self.metadata = {}

def add_data_from_file(file):
    ...

def add_data(timestamp, data, metadata):
    ...

솔루션 A와 B는 기본적으로 동일합니다. 특히이 메서드에 필요한 모든 인수가 제공되었는지 확인해야하기 때문에 if / else를 수행하는 것을 좋아하지 않습니다. 데이터를 추가하는 네 번째 방법으로 코드를 확장해야하는 경우 A는 B보다 약간 더 유연합니다.

솔루션 C가 가장 좋은 것 같지만 사용자는 자신에게 필요한 방법을 알아야합니다. 예를 들어, 그가 c = Container(args)무엇인지 모르면 할 수 없습니다 args.

가장 Pythonic 솔루션은 무엇입니까?


.NET에서 동일한 이름을 가진 여러 메서드를 가질 수 없습니다 Python. in과 달리 함수 오버로딩 Java은 지원되지 않습니다.

기본 매개 변수 또는 **kwargs*args인수를 사용하십시오 .

@staticmethod또는 @classmethod데코레이터를 사용하여 정적 메서드 또는 클래스 메서드를 만들어 클래스 의 인스턴스를 반환하거나 다른 생성자를 추가 할 수 있습니다.

다음과 같이 조언합니다.

class F:

    def __init__(self, timestamp=0, data=None, metadata=None):
        self.timestamp = timestamp
        self.data = list() if data is None else data
        self.metadata = dict() if metadata is None else metadata

    @classmethod
    def from_file(cls, path):
       _file = cls.get_file(path)
       timestamp = _file.get_timestamp()
       data = _file.get_data()
       metadata = _file.get_metadata()       
       return cls(timestamp, data, metadata)

    @classmethod
    def from_metadata(cls, timestamp, data, metadata):
        return cls(timestamp, data, metadata)

    @staticmethod
    def get_file(path):
        # ...
        pass

⚠ 파이썬에서 기본값으로 변경 가능한 유형을 갖지 마십시오. 여기를 참조 하십시오 .


여러 생성자를 가질 수는 없지만 적절하게 이름이 지정된 여러 팩토리 메서드를 가질 수 있습니다.

class Document(object):

    def __init__(self, whatever args you need):
        """Do not invoke directly. Use from_NNN methods."""
        # Implementation is likely a mix of A and B approaches. 

    @classmethod
    def from_string(cls, string):
        # Do any necessary preparations, use the `string`
        return cls(...)

    @classmethod
    def from_json_file(cls, file_object):
        # Read and interpret the file as you want
        return cls(...)

    @classmethod
    def from_docx_file(cls, file_object):
        # Read and interpret the file as you want, differently.
        return cls(...)

    # etc.

하지만 사용자가 생성자를 직접 사용하는 것을 쉽게 막을 수는 없습니다. (중요한 경우 개발 중 안전 예방책으로 생성자에서 호출 스택을 분석하고 예상 메서드 중 하나에서 호출이 이루어 졌는지 확인할 수 있습니다.)


Most Pythonic would be what the Python standard library already does. Core developer Raymond Hettinger (the collections guy) gave a talk on this, plus general guidelines for how to write classes.

Use separate, class-level functions to initialize instances, like how dict.fromkeys() isn't the class initializer but still returns an instance of dict. This allows you to be flexible toward the arguments you need without changing method signatures as requirements change.


What are the system goals for this code? From my standpoint, your critical phrase is but the user has to know which method he requires. What experience do you want your users to have with your code? That should drive the interface design.

Now, move to maintainability: which solution is easiest to read and maintain? Again, I feel that solution C is inferior. For most of the teams with whom I've worked, solution B is preferable to A: it's a little easier to read and understand, although both readily break into small code blocks for treatment.


I'm not sure if I understood right but wouldn't this work?

def __init__(self, file=None, timestamp=0, data=[], metadata={}):
    if file:
        ...
    else:
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

Or you could even do:

def __init__(self, file=None, timestamp=0, data=[], metadata={}):
    if file:
        # Implement get_data to return all the stuff as a tuple
        timestamp, data, metadata = f.get_data()

    self.timestamp = timestamp
    self.data = data
    self.metadata = metadata

Thank to Jon Kiparsky advice theres a better way to avoid global declarations on data and metadata so this is the new way:

def __init__(self, file=None, timestamp=None, data=None, metadata=None):
    if file:
        # Implement get_data to return all the stuff as a tuple
        with open(file) as f:
            timestamp, data, metadata = f.get_data()

    self.timestamp = timestamp or 0
    self.data = data or []
    self.metadata = metadata or {}

If you are on Python 3.4+ you can use the functools.singledispatch decorator to do this (with a little extra help from the methoddispatch decorator that @ZeroPiraeus wrote for his answer):

class Container:

    @methoddispatch
    def __init__(self):
        self.timestamp = 0
        self.data = []
        self.metadata = {}

    @__init__.register(File)
    def __init__(self, file):
        f = file.open()
        self.timestamp = f.get_timestamp()
        self.data = f.get_data()
        self.metadata = f.get_metadata()

    @__init__.register(Timestamp)
    def __init__(self, timestamp, data, metadata):
        self.timestamp = timestamp
        self.data = data
        self.metadata = metadata

The most pythonic way is to make sure any optional arguments have default values. So include all arguments that you know you need and assign them appropriate defaults.

def __init__(self, timestamp=None, data=[], metadata={}):
    timestamp = time.now()

An important thing to remember is that any required arguments should not have defaults since you want an error to be raised if they're not included.

You can accept even more optional arguments using *args and **kwargs at the end of your arguments list.

def __init__(self, timestamp=None, data=[], metadata={}, *args, **kwards):
    if 'something' in kwargs:
        # do something

ReferenceURL : https://stackoverflow.com/questions/44765482/multiple-constructors-the-pythonic-way

반응형