Метод на клас за създаване на нов екземпляр

Навлизам в сериозно ООП с Python и се натъквам на някои проблеми. Имам клас с инициализатор, три частни променливи и set и get методи за всяка променлива.

Освен това има метод за въвеждане, който позволява на потребителя да въведе стойност за променливите, когато те бъдат извикани, вместо да изисква извикване input в главната функция, която предава стойност на метода на набора от класове. По този начин мога незабавно да валидирам потребителския запис в рамките на метода, специфичен за изискванията за данни:

def input_first_name(self):
    done = 0

    while done == 0:
        print("Input First Name:",end='')
        first_name = input()
        if first_name.isalpha():
            self.__first_name = first_name
            done = 1
        else:
            print("Invalid Entry. Please use alphabetic characters only.")

Използвайки тези методи за въвеждане, имам друг метод, който ги използва за създаване на нов екземпляр на клас (само изчакайте, вече знам какво си мислите):

@classmethod
def new(cls):
    cls.input_first_name(cls)
    cls.input_last_name(cls)
    cls.input_birthday(cls)

    return cls 

Така че знам, че използвайки това, всъщност няма да създам нов екземпляр. Използването на методите обаче не води веднага до никакви грешки. Но когато се позовавам на моето новосъздадено копие с метода get, това е мястото, където изпадам в проблеми, които знам, че са свързани с липсата на инстанциране.

new_relative = Relative.new()

print(new_relative.get_first_name())

води до:

TypeError: get_first_name() missing 1 required positional argument: 'self'       

Има ли начин да имам метод на клас, използваем в моята основна функция за инстанциране на нов екземпляр, който използва моята идея за потребителско въвеждане и валидиране по прост и елегантен начин?

За справка, минимална извадка от класа:

class Relative:
    __first_name = ""
    __last_name = ""
    __birthday = 0

    def __init__(self, first_name,last_name,birthday):
        self.__first_name = first_name
        self.__last_name = last_name
        self.__birthday = birthday

    def get_first_name(self):
        return self.__first_name

person Andres Mino    schedule 21.04.2015    source източник


Отговори (1)


Бих направил това със свойства (замяната на Python за безкрайни gets и sets, вижте напр. Python @property срещу getters и сетери) и оставете само input на метод на клас. Ето минимален пример:

class Relative:

    def __init__(self, first_name=None):
        self.first_name = first_name

    @staticmethod
    def _valid_name(name):
        return name is None or name.isalpha()
        # this will choke if name isn't None or a string - maybe add more rules?

    @property
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self, new_name):
        if not self._valid_name(new_name):
            raise ValueError('Invalid first name: {!r}'.format(new_name))
        self._first_name = new_name

    @classmethod
    def from_input(cls):
        relative = cls()
        while True:
            # you could alternatively use cls._valid_name directly here
            try:
                relative.first_name = input('Enter first name: ')
            except ValueError:
                print('Please try again.')
            else:
                break
        return relative

В употреба:

>>> uncle_bob = Relative.from_input()
Enter first name: 123
Please try again.
Enter first name: *
Please try again.
Enter first name: Robert
>>> uncle_bob.first_name
'Robert'
>>> uncle_bob.first_name = '123'
Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    uncle_bob.first_name = '123'
  File "<pyshell#11>", line 17, in first_name
    raise ValueError('Invalid first name: {!r}'.format(new_name))
ValueError: Invalid first name: '123'

Забележи, че:

  1. Абстрахирането на валидирането на името до Relative._valid_name улеснява валидирането на собственото и фамилното име с едни и същи правила (можете също да добавите бащино име и лесно да го направите същото).
  2. Използването на @property означава, че потребителят не може да зададе неправилно име в нито един момент, без да го принуждава да извика конкретен метод за получаване/задаване на стойности.
  3. Използвал съм private-by-convention име с единична долна черта за вътрешната променлива, вместо name-mangled двойно долна черта - вижте напр. ръководството за стил.
person jonrsharpe    schedule 21.04.2015