programing

여러 컨스트럭터를 구현하기 위한 깨끗한 "피토닉" 방법은 무엇입니까?

sourcejob 2022. 9. 25. 00:19
반응형

여러 컨스트럭터를 구현하기 위한 깨끗한 "피토닉" 방법은 무엇입니까?

을 사용하다로 할 수 걸로 알고 있어요.__init__파이썬럼럼 이이 제떻 떻떻 떻? ???

를 들어, 「 」라고 하는 .Cheesenumber_of_holes소유물.어떻게 두 가지 방법으로 치즈 오브젝트를 만들 수 있을까?

  1. 것:parmesan = Cheese(num_holes = 15).
  2. 를 받지 하는 것number_of_holes ★★★★gouda = Cheese().

한 가지 방법밖에 생각할 수 없지만, 이 방법은 어설프게 보입니다.

class Cheese():
    def __init__(self, num_holes = 0):
        if (num_holes == 0):
            # Randomize number_of_holes
        else:
            number_of_holes = num_holes

어때?다른 방법이 없나요?

★★★★★★★★★★★★★★★★★.None훨씬 . "예"값은 "예"가 좋습니다.

class Cheese():
    def __init__(self, num_holes = None):
        if num_holes is None:
            ...

이제 파라미터를 추가할 수 있는 완전한 자유를 원하는 경우:

class Cheese():
    def __init__(self, *args, **kwargs):
        #args -- tuple of anonymous arguments
        #kwargs -- dictionary of named arguments
        self.num_holes = kwargs.get('num_holes',random_holes())

의 을 더 잘 하기 위해서*args ★★★★★★★★★★★★★★★★★」**kwargs(실제로 이러한 이름을 변경할 수 있습니다).

def f(*args, **kwargs):
   print 'args: ', args, ' kwargs: ', kwargs

>>> f('a')
args:  ('a',)  kwargs:  {}
>>> f(ar='a')
args:  ()  kwargs:  {'ar': 'a'}
>>> f(1,2,param=3)
args:  (1, 2)  kwargs:  {'param': 3}

http://docs.python.org/reference/expressions.html#calls

「」를 사용합니다.num_holes=None에, 「」만 있으면 .__init__.

여러 개의 독립된 "구성자"를 원하는 경우 클래스 메서드로 제공할 수 있습니다.이러한 방법을 보통 공장 방식이라고 합니다.이 될 수 .num_holes0.

class Cheese(object):
    def __init__(self, num_holes=0):
        "defaults to a solid cheese"
        self.number_of_holes = num_holes

    @classmethod
    def random(cls):
        return cls(randint(0, 100))

    @classmethod
    def slightly_holey(cls):
        return cls(randint(0, 33))

    @classmethod
    def very_holey(cls):
        return cls(randint(66, 100))

이제 다음과 같은 개체를 만듭니다.

gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()

확실히 이미 게재된 솔루션을 선호해야 하지만, 아직 아무도 이 솔루션에 대해 언급하지 않았기 때문에, 완전성을 위해 언급할 가치가 있다고 생각합니다.

@classmethod를 변경하여 디폴트컨스트럭터)를 할 수 .__init__인스턴스(instance)를 사용하여 __new__

이것은 컨스트럭터 인수 유형을 기반으로 초기화 유형을 선택할 수 없고 컨스트럭터가 코드를 공유하지 않는 경우에 사용할 수 있습니다.

예:

class MyClass(set):

    def __init__(self, filename):
        self._value = load_from_file(filename)

    @classmethod
    def from_somewhere(cls, somename):
        obj = cls.__new__(cls)  # Does not call __init__
        super(MyClass, obj).__init__()  # Don't forget to call any polymorphic base class initializers
        obj._value = load_from_somewhere(somename)
        return obj

옵션 파라미터를 사용하는 경우 이들 답변은 모두 훌륭하지만, 다른 방법으로는 클래스 메서드를 사용하여 공장 스타일의 의사 컨스트럭터를 생성할 수 있습니다.

def __init__(self, num_holes):

  # do stuff with the number

@classmethod
def fromRandom(cls):

  return cls( # some-random-number )

솔루션이 "투박"하다고 생각하는 이유는 무엇입니까?개인적으로 다음과 같은 상황에서는 여러 개의 오버로드된 컨스트럭터보다 기본값을 가진 컨스트럭터를 선호합니다(Python은 메서드 오버로드를 지원하지 않습니다).

def __init__(self, num_holes=None):
    if num_holes is None:
        # Construct a gouda
    else:
        # custom cheese
    # common initialization

다양한 컨스트럭터가 있는 매우 복잡한 경우 대신 다른 공장 기능을 사용하는 것이 더 깔끔할 수 있습니다.

@classmethod
def create_gouda(cls):
    c = Cheese()
    # ...
    return c

@classmethod
def create_cheddar(cls):
    # ...

치즈 예에서는 Gouda의 치즈 서브클래스를 사용하는 것이 좋을지도 모릅니다만...

구현에는 좋은 아이디어이지만 사용자에게 치즈 제조 인터페이스를 제공하는 경우입니다.그들은 치즈가 얼마나 많은 구멍이 뚫렸는지, 치즈를 만드는 데 내부가 어떻게 들어가는지 신경 쓰지 않는다.당신의 코드 사용자는 단지 "고다"나 "미얀마"를 원하죠?

그럼 이렇게 하는 게 어때요?

# cheese_user.py
from cheeses import make_gouda, make_parmesean

gouda = make_gouda()
paremesean = make_parmesean()

그런 다음 위의 방법 중 하나를 사용하여 실제로 기능을 구현할 수 있습니다.

# cheeses.py
class Cheese(object):
    def __init__(self, *args, **kwargs):
        #args -- tuple of anonymous arguments
        #kwargs -- dictionary of named arguments
        self.num_holes = kwargs.get('num_holes',random_holes())

def make_gouda():
    return Cheese()

def make_paremesean():
    return Cheese(num_holes=15)

이것은 좋은 캡슐화 기술이고, 나는 이것이 더 피토닉하다고 생각한다.이런 방식은 오리타자와 더 잘 어울린다.당신은 단지 고다 물체를 요구하고 있고 그것이 어떤 클래스인지 별로 신경 쓰지 않는다.

개요

특정 치즈 예제의 경우 랜덤 초기화에 기본값을 사용하거나 정적 공장 방법을 사용하는 것에 대한 다른 많은 답변에 동의합니다.그러나 파라미터 이름 또는 유형 정보의 품질을 해치지 않고 컨스트럭터를 호출하는 대체적이고 간결한 방법이 있는 경우 염두에 둔 관련 시나리오도 있을 수 있습니다.

Python 3.8과 많은 경우 이를 달성할 수 있기 때문에(그리고 더 유연한 것은 더 많은 시나리오에 적용될 수 있습니다).( 게시물은 라이브러리 없이 Python 3.4에서도 동일한 작업을 수행할 수 있는 방법에 대해 설명합니다.)이들 중 어느 것에 대해서도, 특히 과부하를 나타내는 예는 문서에서는 볼 수 없었습니다.__init__문의하신 대로입니다만, 멤버 메서드를 오버로드 하는 경우에도 같은 원칙이 적용되는 것 같습니다(아래 참조).

"단일 디스패치" (표준 라이브러리에서 사용 가능)는 적어도1개의 위치 파라미터가 존재해야 하며 첫 번째 인수 유형은 과부하 가능한 옵션을 식별하기에 충분해야 합니다. Cheese의 않을 때을 원했기 되지 않습니다.단, Cheese의 경우는 이 필요하기 때문에 유지되지 않습니다.multidispatch는 동일한 구문을 지원하며 모든 인수의 수와 유형에 따라 각 메서드버전을 구별할 수 있는 한 사용할 수 있습니다.

다음은 두 가지 방법 중 하나를 사용하는 방법의 예입니다(처음 목표였던 mypy를 만족시키기 위한 세부 사항도 있습니다).

from functools import singledispatchmethod as overload
# or the following more flexible method after `pip install multimethod`
# from multimethod import multidispatch as overload


class MyClass:

    @overload  # type: ignore[misc]
    def __init__(self, a: int = 0, b: str = 'default'):
        self.a = a
        self.b = b

    @__init__.register
    def _from_str(self, b: str, a: int = 0):
        self.__init__(a, b)  # type: ignore[misc]

    def __repr__(self) -> str:
        return f"({self.a}, {self.b})"


print([
    MyClass(1, "test"),
    MyClass("test", 1),
    MyClass("test"),
    MyClass(1, b="test"),
    MyClass("test", a=1),
    MyClass("test"),
    MyClass(1),
    # MyClass(),  # `multidispatch` version handles these 3, too.
    # MyClass(a=1, b="test"),
    # MyClass(b="test", a=1),
])

출력:

[(1, test), (1, test), (0, test), (1, test), (1, test), (0, test), (1, default)]

주의:

  • 는 보통 통통 called called라는 않는다.overload그러나 두 가지 방법을 사용하는 차이는 Import를 사용하는 문제에만 해당됩니다.
  • # type: ignore[misc]꼭 , 코멘트를 은, 「코멘트」를 만족시키기 .mypy 것을.__init__도 않다__init__직접적으로.
  • 구문을 접하는 , '데코레이터'를 붙이는 것이 .@overload__init__ 일 뿐이다__init__ = overload(the original definition of __init__)이경은overload이기 때문에, 그 「」라고 하는 것이 됩니다.__init__를 가진 객체입니다.__call__으로 보이도록 , 즉, 기능적으로 보이도록 하는 이 있습니다..register된 버전의 다른 을 __init__조금 지저분하지만 메서드명이 두 번 정의되어 있지 않기 때문에 매우 기쁘게 생각합니다.이라면 mypy를 사용할 수 .multimethod에는 오버로드된 버전을 지정하는 간단한 대체 방법도 있습니다.
  • 의 정의__repr__인쇄된 출력을 의미 있게 하기 위한 것입니다(일반적으로 필요하지 않습니다).
  • 「 」라는 점에 해 주세요.multidispatch는 위치 파라미터가 없는3개의 추가 입력 조합을 처리할 수 있습니다.

num_holes=None대신 디폴트로 사용합니다. 다음 '있다'가 있는지 .num_holes is None을 사용하다이치노

보다 근본적으로 다른 구성 방법은 다음 항목을 반환하는 클래스 방법을 보증할 수 있습니다.cls.

가장 좋은 답은 디폴트 인수에 대한 위의 답변이지만, 저는 이것을 쓰는 것이 재미있었고, 이것은 확실히 "복수의 컨스트럭터"에 맞는 것입니다.본인 부담으로 사용하세요.

새로운 방법은 어때?

"일반적인 구현에서는 super(current class, cls)를 사용하여 superclass의 new() 메서드를 호출하여 클래스의 새 인스턴스를 만듭니다.new(cls[, ...])와 적절한 인수를 지정한 후 필요에 따라 새로 생성된 인스턴스를 수정한 후 반환합니다."

따라서 적절한 생성자 메서드를 연결하여 새 메서드가 클래스 정의를 수정하도록 할 수 있습니다.

class Cheese(object):
    def __new__(cls, *args, **kwargs):

        obj = super(Cheese, cls).__new__(cls)
        num_holes = kwargs.get('num_holes', random_holes())

        if num_holes == 0:
            cls.__init__ = cls.foomethod
        else:
            cls.__init__ = cls.barmethod

        return obj

    def foomethod(self, *args, **kwargs):
        print "foomethod called as __init__ for Cheese"

    def barmethod(self, *args, **kwargs):
        print "barmethod called as __init__ for Cheese"

if __name__ == "__main__":
    parm = Cheese(num_holes=5)

난 상속을 이용하겠어특히 구멍의 수보다 차이가 더 많이 나는 경우에는 더욱 그렇습니다.특히 고다가 다른 멤버들을 가질 필요가 있다면 파르메산이다.

class Gouda(Cheese):
    def __init__(self):
        super(Gouda).__init__(num_holes=10)


class Parmesan(Cheese):
    def __init__(self):
        super(Parmesan).__init__(num_holes=15) 

저의 최초 답변은 특수목적 컨스트럭터가 (고유한) 디폴트 컨스트럭터라고 부르지 않았다는 이유로 비판을 받았기 때문에, 저는 모든 컨스트럭터가 디폴트 컨스트럭터라고 부르기를 바라는 바램을 존중하는 수정 버전을 여기에 올립니다.

class Cheese:
    def __init__(self, *args, _initialiser="_default_init", **kwargs):
        """A multi-initialiser.
        """
        getattr(self, _initialiser)(*args, **kwargs)

    def _default_init(self, ...):
        """A user-friendly smart or general-purpose initialiser.
        """
        ...

    def _init_parmesan(self, ...):
        """A special initialiser for Parmesan cheese.
        """
        ...

    def _init_gouda(self, ...):
        """A special initialiser for Gouda cheese.
        """
        ...

    @classmethod
    def make_parmesan(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_parmesan")

    @classmethod
    def make_gouda(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_gouda")

했습니다.YearQuarter★★★★★★★★★★★★★★★★★★★★★★★★★★★★★는 성했니 an 를 만들었다.__init__하다

다음과 같이 사용합니다.

>>> from datetime import date
>>> temp1 = YearQuarter(year=2017, month=12)
>>> print temp1
2017-Q4
>>> temp2 = YearQuarter(temp1)
>>> print temp2
2017-Q4
>>> temp3 = YearQuarter((2017, 6))
>>> print temp3
2017-Q2 
>>> temp4 = YearQuarter(date(2017, 1, 18))
>>> print temp4
2017-Q1
>>> temp5 = YearQuarter(year=2017, quarter = 3)
>>> print temp5
2017-Q3

그리고 이렇게 해서__init__그리고 나머지 학생들은 다음과 같이 보입니다.

import datetime


class YearQuarter:

    def __init__(self, *args, **kwargs):
        if len(args) == 1:
            [x]     = args

            if isinstance(x, datetime.date):
                self._year      = int(x.year)
                self._quarter   = (int(x.month) + 2) / 3
            elif isinstance(x, tuple):
                year, month     = x

                self._year      = int(year)

                month           = int(month)

                if 1 <= month <= 12:
                    self._quarter   = (month + 2) / 3
                else:
                    raise ValueError

            elif isinstance(x, YearQuarter):
                self._year      = x._year
                self._quarter   = x._quarter

        elif len(args) == 2:
            year, month     = args

            self._year      = int(year)

            month           = int(month)

            if 1 <= month <= 12:
                self._quarter   = (month + 2) / 3
            else:
                raise ValueError

        elif kwargs:

            self._year      = int(kwargs["year"])

            if "quarter" in kwargs:
                quarter     = int(kwargs["quarter"])

                if 1 <= quarter <= 4:
                    self._quarter     = quarter
                else:
                    raise ValueError
            elif "month" in kwargs:
                month   = int(kwargs["month"])

                if 1 <= month <= 12:
                    self._quarter     = (month + 2) / 3
                else:
                    raise ValueError

    def __str__(self):
        return '{0}-Q{1}'.format(self._year, self._quarter)
class Cheese:
    def __init__(self, *args, **kwargs):
        """A user-friendly initialiser for the general-purpose constructor.
        """
        ...

    def _init_parmesan(self, *args, **kwargs):
        """A special initialiser for Parmesan cheese.
        """
        ...

    def _init_gauda(self, *args, **kwargs):
        """A special initialiser for Gauda cheese.
        """
        ...

    @classmethod
    def make_parmesan(cls, *args, **kwargs):
        new = cls.__new__(cls)
        new._init_parmesan(*args, **kwargs)
        return new

    @classmethod
    def make_gauda(cls, *args, **kwargs):
        new = cls.__new__(cls)
        new._init_gauda(*args, **kwargs)
        return new

나는 아직 예시를 든 솔직한 답이 보이지 않는다.아이디어는 간단합니다.

  • __init__로서 python은 1개의 python만 합니다.__init__
  • @classmethod합니다.

여기 새로운 시도가 있습니다.

 class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def fromBirthYear(cls, name, birthYear):
        return cls(name, date.today().year - birthYear)

사용방법:

p = Person('tim', age=18)
p = Person.fromBirthYear('tim', birthYear=2004)

언급URL : https://stackoverflow.com/questions/682504/what-is-a-clean-pythonic-way-to-implement-multiple-constructors

반응형