Подробнее Pythonic способ добавления атрибутов в класс?

Я работаю с наборами данных с двух разных веб-страниц, но для одного и того же человека - наборы данных - это юридическая информация. Некоторые из данных доступны на первой странице, поэтому я инициализирую объект Defendant с соответствующей информацией и устанавливаю атрибуты, на которые у меня в настоящее время нет данных для null. Это класс:

class Defendant(object):
    """holds data for each individual defendant"""
    def __init__(self,full_name,first_name,last_name,type_of_appeal,county,case_number,date_of_filing,
                 race,sex,dc_number,hair_color,eye_color,height,weight,birth_date,initial_receipt_date,current_facility,current_custody,current_release_date,link_to_page):
        self.full_name = full_name
        self.first_name = first_name
        self.last_name = last_name
        self.type_of_appeal = type_of_appeal
        self.county = county
        self.case_number = case_number
        self.date_of_filing = date_of_filing
        self.race = 'null'
        self.sex = 'null'
        self.dc_number = 'null'
        self.hair_color = 'null'
        self.eye_color = 'null'
        self.height = 'null'
        self.weight = 'null'
        self.birth_date = 'null'
        self.initial_receipt_date = 'null'
        self.current_facility = 'null'
        self.current_custody = 'null'
        self.current_release_date = 'null'
        self.link_to_page = link_to_page

И это похоже на то, когда я добавляю наполовину заполненный объект Defendant в список подсудимых:

list_of_defendants.append(Defendant(name_final,'null','null',type_of_appeal_final,county_parsed_final,case_number,date_of_filing,'null','null','null','null','null','null','null','null','null','null','null','null',link_to_page))

тогда, когда я получаю остальную часть данных с другой страницы, я обновляю эти атрибуты так, чтобы она была нулевой:

        for defendant in list_of_defendants:
            defendant.sex = location_of_sex_on_page
            defendant.first_name = location_of_first_name_on_page
            ## Etc.

Мой вопрос: есть ли более питонический способ либо добавлять атрибуты к классу, либо менее уродливый способ инициализации объекта класса, когда у меня есть только половина информации, которую я хочу сохранить в нем?

Ответ 1

Сначала используйте значения по умолчанию для любых аргументов, которые вы устанавливаете в значение null. Таким образом, вам не нужно указывать эти аргументы при создании экземпляра объекта (и вы можете указать все, что вам нужно в любом порядке, используя имя аргумента). Вы должны использовать значение Python None, а не строку "null" для них, если только не существует определенной причины для использования этой строки. В Python 2.x аргументы со значениями по умолчанию должны быть последними, поэтому перед этим нужно перемещать link_to_page.

Затем вы можете установить свои атрибуты, обновив атрибут экземпляра __dict__, который хранит атрибуты, прикрепленные к экземпляру. Каждый аргумент будет установлен как атрибут экземпляра с тем же именем.

def __init__(self, full_name, first_name, last_name, type_of_appeal, county, case_number, 
             date_of_filing, link_to_page, race=None, sex=None, dc_number=None,
             hair_color=None, eye_color=None, height=None, weight=None, birth_date=None,
             initial_receipt_date=None, current_facility=None, current_custody=None, 
             current_release_date=None):

      # set all arguments as attributes of this instance
      code     = self.__init__.__func__.func_code
      argnames = code.co_varnames[1:code.co_argcount]
      locs     = locals()
      self.__dict__.update((name, locs[name]) for name in argnames)

Вы также можете рассмотреть возможность синтеза full_name из двух других аргументов имени. Тогда вам не нужно передавать избыточную информацию, и она никогда не может совпадать. Вы можете сделать это на лету через свойство:

@property
def full_name(self):
    return self.first_name + " " + self.last_name

Для обновления, я бы добавил метод для этого, но принимаю только аргументы только для ключевого слова, используя **. Чтобы защитить целостность данных, мы изменим только те атрибуты, которые уже существуют, и установлены на None.

def update(self, **kwargs):
    self.__dict__.update((k, kwargs[k]) for k in kwargs
                          if self.__dict__.get(k, False) is None)

Затем вы можете легко обновить все те, которые вы хотите, с помощью одного вызова:

defendant.update(eye_color="Brown", hair_color="Black", sex="Male")

Чтобы убедиться, что экземпляр полностью заполнен, вы можете добавить метод или свойство, которое проверяет, чтобы все атрибуты не были None:

@property
def valid(self):
    return all(self.__dict__[k] is not None for k in self.__dict__)

Ответ 2

Если вы согласны с передачей каждого атрибута в качестве пары имя-значение, вы можете использовать что-то вроде:

class Defendant(object):
    fields = ['full_name', 'first_name', 'last_name', 'type_of_appeal', 
              'county', 'case_number', 'date_of_filing', 'race', 'sex',
              'dc_number', 'hair_color', 'eye_color', 'height', 'weight', 
              'birth_date', 'initial_receipt_date', 'current_facility', 
              'current_custody', 'current_release_date', 'link_to_page']

    def __init__(self, **kwargs):
        self.update(**kwargs)

    def update(self, **kwargs):
        self.__dict__.update(kwargs)

    def blank_fields(self):
        return [field for field in self.fields if field not in self.__dict__]

    def verify(self):
        blanks = self.blank_fields()
        if blanks:
            print 'The fields {} have not been set.'.format(', '.join(blanks))
            return False
        return True

Использование будет выглядеть примерно так:

defendant = Defendant(full_name='John Doe', first_name='John', last_name='Doe')
defendant.update(county='Here', height='5-11', birth_date='1000 BC')
defendant.verify()
# The fields type_of_appeal, case_number, date_of_filing, race... have not been set.

Расширить это, чтобы использовать обязательные поля и необязательные поля, было бы легко. Или вы можете добавить необходимые аргументы в инициализацию. Или вы можете проверить, чтобы каждая пара имен и значений имела действительное имя. И так далее...

Ответ 3

Итак, более простой пример, иллюстрирующий, как вы могли:

class Foo:
  def __init__(self, a, b, e, c=None, d=None):
    self.a = a
    self.b = b
    self.c = c
    self.d = d
    self.e = e

Но если у вас никогда есть c и d, когда вам нужно создать инстанцирование, я бы рекомендовал это вместо:

class Foo:
  def __init__(self, a, b, e):
    self.a = a
    self.b = b
    self.c = None
    self.d = None
    self.e = e

EDIT: Другой способ:

class Defendant(object):
    __attrs = (
        'full_name',
        'first_name',
        'last_name',
        'type_of_appeal',
        'county',
        'case_number',
        'date_of_filing',
        'race',
        'sex',
        'dc_number',
        'hair_color',
        'eye_color',
        'height',
        'weight',
        'birth_date',
        'initial_receipt_date',
        'current_facility',
        'current_custody',
        'current_release_date',
        'link_to_page'
    )

    def __update(self, *args, **kwargs):
        self.__dict__.update(dict(zip(self.__attrs, args)))
        self.__dict__.update(kwargs)

    def __init__(self, *args, **kwargs):
        self.__dict__ = dict.fromkeys(Defendant.__attrs, None)
        self.__update(*args, **kwargs)

    update_from_data = __update


if __name__ == '__main__':
    test = Defendant('foo bar', 'foo', 'bar', height=180, weight=85)
    test.update_from_data('Superman', 'Clark', 'Kent', hair_color='red', county='SmallVille')

Ответ 4

Я бы сказал, что самый пифонический способ - это то, что выглядит так:

class Defendant(Model):
    full_name = None  # Some default value
    first_name = None
    last_name = None
    type_of_appeal = None
    county = None
    case_number = None
    date_of_filing = None
    race = None
    sex = None
    dc_number = None
    hair_color = None
    eye_color = None
    height = None
    weight = None
    birth_date = None
    initial_receipt_date = None
    current_facility = None
    current_custody = None
    current_release_date = None
    link_to_page = None

Очистить, все определяется только один раз и работает автоматически.

Об этом супер-классе Model... Если вы используете какую-либо веб-инфраструктуру, такую ​​как Django, обязательно наследуйте ее модель, и все готово. Он имеет всю необходимую проводку.

В противном случае, простой способ реализовать что-то короткое и сладкое, наследует ваш класс Defendant:

class Model(object):
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

И создайте экземпляр на основе доступных полей:

d1 = Defendant(height=1.75)
print d1.height

d2 = Defendant(full_name='Peter')
print d2.full_name

Вы можете достичь гораздо более прохладных вещей с небольшим количеством метапрограмм, таких как проверка типа поля, проверка ценности, дублированные объявления и т.д. Если вы используете python 3, вы можете легко разрешить передачу значений в __init__ метод либо с помощью args (на основе порядка объявления), либо kwargs.