Python personaliza una clase de fecha

        A través de nuestra comprensión previa de los métodos mágicos en Python, ahora usamos estos métodos mágicos para personalizar una clase de fecha para profundizar nuestra comprensión y aplicación de los métodos mágicos.

clase de fecha

        Primero, escribamos un método __init__ para inicializar la clase de fecha. En __init__ podemos usar parámetros predeterminados para evitar la situación en la que nos olvidamos de pasar parámetros durante la creación de instancias y no podemos crear instancias. Los atributos de año, mes y día en la clase de fecha se pueden ocultar mediante guiones bajos dobles para evitar que otros modifiquen los valores de los atributos a voluntad.

class MyDate:
    """日期类"""

    def __init__(self, year=2023, month=1, day=1):
        self.__year = year
        self.__month = month
        self.__day = day

 Ahora podemos obtener el objeto de fecha creando una instancia de MyDate y simplemente pasando algunos parámetros para obtener el objeto de fecha.

date1 = MyDate()
print(date1)
date2 = MyDate(1999, 6, 6)
print(date2)

 Los resultados de la ejecución son los siguientes:

Descubrimos que la información del objeto impresa mediante impresión no es atractiva. Sería bueno si pudiéramos imprimir <año-mes-día>. Entonces pensamos en el método __str__, Print llama al método __str__ para imprimir la información del objeto. Podemos resolver este problema personalizando el método __str__, por lo que escribimos el método __str__ en la clase MyDate. En el método __str__, necesitamos devolver una cadena que contenga información sobre el año, mes y día. Podemos hacer algún procesamiento para el mes y el día, para que sigan siendo dos dígitos, y si tienen menos de dos dígitos, agregar 0 delante de ellos (por ejemplo: 06 de julio).

    def __str__(self):
        return f'{self.__year}-{self.__month:0>2}-{self.__day:0>2}'

Después de agregar el método __str__, ejecutamos el programa nuevamente e imprimimos la información del objeto.

¿No parece más cómodo ahora?

fecha del juicio

        Aunque hemos implementado la creación de objetos de fecha, hay un ERROR aquí. Podemos crear en cualquier momento, como el 60 de enero de 2023.

date1 = MyDate(2023, 1, 60)
print(date1)

 Los resultados de la ejecución son los siguientes:

Una fecha como el 60 de enero de 2023 simplemente no existe y no cumple con las reglas de las fechas. Por lo tanto, debemos juzgar la fecha para determinar si cumple con el estándar del calendario gregoriano. Crearemos un objeto de fecha solo si cumple con el estándar del calendario gregoriano. Si no cumple con el estándar del calendario gregoriano, se generará un error.

        El año de la fecha de nuestro calendario gregoriano debe ser mayor o igual a 0, el mes es 1 ~ 12 y el número de días de cada mes se determina en función del año y el mes. Podemos encontrar que el año y el mes son fáciles de juzgar, pero el número de días de cada mes es difícil de juzgar, pero no temas mientras lo analizamos lentamente. Si se divide el año en años ordinarios y años bisiestos, febrero tiene 28 días en los años ordinarios y 29 de febrero en los años bisiestos. Los demás meses son iguales, 31 días de enero, 31 días de marzo, 30 días de abril, 31 días de mayo, 30 días de junio, 31 días de julio, 31 días de agosto, 30 días de septiembre, 31 días de octubre, 30 días de noviembre y 30 días. en diciembre. Según la cantidad de días del mes, podemos diseñar una lista de meses y colocar la cantidad de días de cada mes en la posición correspondiente del índice de la lista. Podemos obtener una lista de meses en años ordinarios y una lista de meses. en años bisiestos.

    __non_leap = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]  # 平年月份列表
    __leap = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]  # 闰年月份列表

Debido a que el índice de la lista comienza desde 0 y no tenemos el mes 0, el primer elemento está ocupado por 0 y no podemos usar el primer elemento. La lista de meses también se oculta mediante subrayados dobles para evitar que se modifique a voluntad.

        Luego tenemos que distinguir entre años ordinarios y años bisiestos: para años ordinarios, use la lista de años ordinarios y para años bisiestos, use la lista de años bisiestos. Entonces, ¿cómo distinguir entre años ordinarios y años bisiestos? Se llaman años bisiestos los años que son divisibles por 4 pero no por 100, o divisibles por 400. Exceptuando los años bisiestos, el resto son años ordinarios. Por lo tanto, solo podemos juzgar los años bisiestos, si es un año bisiesto, use la lista de meses de años bisiestos, de lo contrario use la lista de meses de años ordinarios. Entonces escribimos un método en la clase MyDate para devolver el número de días del mes, de la siguiente manera:

    __non_leap = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]  # 平年月份列表
    __leap = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]  # 闰年月份列表

    def _month_day(self):
        """
        返回这个月的天数\n
        :return: 月份的天数
        """
        if (self.__year % 4 == 0 and self.__year % 100 != 0) or (self.__year % 400 == 0):
            return self.__leap[self.__month]  # 返回闰年月份的天数
        return self.__non_leap[self.__month]  # 返回平年月份的天数

Después de escribir este método, descubrimos que en realidad es un poco redundante. Solo hay un día de diferencia entre la lista de meses en años ordinarios y la lista de meses en años bisiestos, solo podemos usar una lista. Agregamos otra condición a la condición de juicio if, si el mes actual es febrero. Si el mes es febrero y el año es bisiesto se devolverán 29 días, en caso contrario se devolverá el número de días del mes correspondiente en la lista de meses de años ordinarios. Entonces modificamos el método _month_day() y lo cambiamos a lo siguiente:

    __non_leap = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]  # 平年月份列表

    def _month_day(self):
        """
        返回这个月的天数\n
        :return: 月份的天数
        """
        if self.__month == 2 and ((self.__year % 4 == 0 and self.__year % 100 != 0) or (self.__year % 400 == 0)):
            return 29
        return self.__non_leap[self.__month]  # 返回平年月份的天数

Con esta función para determinar el número de días del mes, podemos determinar si la fecha cumple con el estándar del calendario gregoriano durante la inicialización. Entonces agregamos una declaración lógica para determinar la fecha en el método __init__:

    def __init__(self, year=2023, month=1, day=1):
        self.__year = year
        self.__month = month
        self.__day = day
        if not (self.__year >= 0 and (0 < self.__month < 13) and (0 < self.__day <= self._month_day())):
            raise ValueError

Ahora podemos usar la clase MyDate para crear una instancia de la fecha correcta. Si la fecha es incorrecta, la instanciación fallará.

date1 = MyDate()
print(date1)
date2 = MyDate(2023, 1, 60)
print(date2)

Los resultados de la ejecución son los siguientes:

Aunque ahora se pueden generar errores, el valueError arrojado no coincide muy bien con nuestro tipo MyDate. Además, simplemente arroja ValueError sin ninguna descripción de texto, lo que también confunde a la gente sobre por qué se informa el error. Entonces podemos personalizar el tipo de error y generar un error que coincida con el tipo MyDate.

Tipo de error de fecha personalizado

        Los tipos de error en Python son básicamente excepciones heredadas, y nuestros errores personalizados también heredan excepciones. Podemos pasar la fecha del error a la instancia del tipo de error. En este caso, necesitamos personalizar el método __init__ para recibir la fecha del error y asignar la fecha del error al atributo de la instancia; luego personalizar el método __str__ para describir el contenido del error. Y añade la fecha del error a la descripción. Entonces podemos escribir los siguientes tipos de error:

class MyDateError(Exception):
    """非法日期错误"""

    def __init__(self, error_date):
        self.date = error_date

    def __str__(self):
        return f"The data should conform to the gregorian standards\nno exists date {self.date}"

        Ahora cambiamos el método __init__ de lanzar ValueError a lanzar MyDateError.

    def __init__(self, year=2023, month=1, day=1):
        self.__year = int(year)
        self.__month = int(month)
        self.__day = int(day)
        if not (self.__year >= 0 and (0 < self.__month < 13) and (0 < self.__day <= self._month_day())):
            raise MyDateError(f'{self.__year}-{self.__month:0>2}-{self.__day:0>2}')

Instalemos nuevamente la fecha del error y verifiquemos el informe de errores.

¿No es mucho mejor esta forma de informar errores?

Comparación del tamaño de la fecha

        Ha salido el prototipo de MyDate, y ahora podemos darle la posibilidad de comparar tallas entre otras similares. Entonces se nos ocurrieron métodos mágicos para operaciones de comparación: __eq__, __ne__, __lt__, __le__, __gt__, __ge__. Siempre que implementemos estos métodos mágicos en MyDate, podemos permitir que las instancias de MyDate realicen operaciones de comparación.

__eq__ (igual a)

        Primero debemos pensar si las dos fechas son iguales y qué es necesario comparar. Debería ser necesario comprobar si los tres atributos de año, mes y día son iguales, si el año, mes y día son iguales, entonces podemos decir que las dos fechas son iguales. Entonces definimos el método __eq__ en la clase.

    def __eq__(self, other):
        return self.__year == other.__year and self.__month == other.__month and self.__day == other.__day

Intentemos ver si la instancia MyDate puede realizar operaciones iguales.

date1 = MyDate()
date2 = MyDate(2023, 1, 30)
date3 = MyDate(2023, 1, 30)
print(date2 == date1)
print(date2 == date3)

Los resultados de la ejecución son los siguientes:

Podemos ver que podemos determinar correctamente si dos fechas son iguales, pero ¿qué pasa si usamos otros tipos para comparar con MyDate?

date1 = MyDate()
print(date1 == 1)

Los resultados de la ejecución son los siguientes:

Podemos ver que se informó un error. No solo se informó el error, sino que también quedó expuesto nuestro atributo oculto _MyDate__year. Entonces, para evitar este tipo de error, debemos determinar si el tipo de parámetro otro es MyDate antes de comparar, si es así, compararemos, si no, arrojaremos directamente un error. Por lo tanto, deberíamos agregar lógica para determinar si other es del tipo MyDate en el método __eq__, pero que no cunda el pánico todavía. Piénselo detenidamente, dado que el tipo debe juzgarse por igual, no significa que el tipo deba juzgarse: menor que, mayor que, menor o igual que y mayor o igual que también deben juzgar el tipo. Dado que todos los métodos de comparación necesitan determinar el tipo, debemos escribir un código para determinar el tipo en cada método de comparación. Hacer esto hará que nuestro código sea redundante. ¿Hay alguna forma de reducir la redundancia del código? Entonces pensamos en decoradores, que pueden usarse para determinar tipos y reducir la duplicación de código. Escribimos la siguiente función de decoración:

from functools import wraps


class NoMyDateError(Exception):
    """不是MyDate类型错误"""

    def __str__(self):
        return "MyDate can only be compared to MyDate"


def check_type(func):
    """
    检查比较的类型是否为MyDate的装饰器\n
    如果不是抛出 NoMyDateError\n
    :param func: 被装饰的函数
    :return:
    """
    @wraps(func)
    def check(*args, **kwargs):
        for _type in args:
            if type(_type) != MyDate:
                raise NoMyDateError
        result = func(*args, **kwargs)
        return result
    return check

Cuando se produce un error en la función decorada, también se define un error. Ahora que la función de decoración está escrita, decorémosla en el método de comparación.

    @check_type
    def __eq__(self, other):
        return self.__year == other.__year and self.__month == other.__month and self.__day == other.__day

Ahora comparémoslo con otros tipos para ver cómo se muestra el error.

Ahora este tipo de informe de errores es mucho mejor y nuestros atributos ocultos ya no estarán expuestos.

__ne__ (no igual a)

        Ahora que hemos logrado la igualdad, no ser igual es simple. Debido a que lo opuesto a igual no es igual, se puede hacer fácilmente usando el operador lógico not.

    @check_type
    def __ne__(self, other):
        return not self == other

Realizamos la operación == en __ne__ y luego invertimos el resultado de la operación para obtener el resultado de la operación de! =.

__lt__ (menos que)

        Primero, pensemos en cómo juzgar el tamaño de dos dátiles. Debe ser que el año sea menor que la fecha menor; cuando el año es igual, el mes es menor, y el mes es menor que la fecha menor; cuando el año y el mes son iguales, se comparan los días, y el la fecha con menos días es más pequeña. Entonces definimos el método __lt__ en MyDate:

    @check_type
    def __lt__(self, other):
        if self.__year < other.__year:
            return True
        elif self.__year == other.__year and self.__month < other.__month:
            return True
        elif self.__year == other.__year and self.__month == other.__month and self.__day < other.__day:
            return True
        return False

__le__ (menor o igual a)

        Ya hemos implementado los métodos igual a y menor que, por lo que menor o igual a es simple. Debido a que menor o igual a es menor o igual a, simplemente use el operador lógico o para procesar los resultados de menor que e igual a.

    @check_type
    def __le__(self, other):
        return self < other or self == other

__gt__ (mayor que)

        Se realiza menor o igual que, y mayor que es simple, es lo opuesto a menor o igual que, y se puede hacer usando el operador lógico not.

    @check_type
    def __gt__(self, other):
        return not self <= other

__ge__ (mayor o igual a)

        Mayor o igual a es lo opuesto a menor que. Ya hemos implementado menor que, por lo que mayor o igual a también es muy simple. Utilice el operador lógico para no obtenerlo.

    @check_type
    def __ge__(self, other):
        return not self < other

Al reutilizar otros métodos mágicos, la definición de métodos mágicos se puede completar rápidamente y al mismo tiempo reducir la cantidad de código.

Suma y resta de fechas

        Las operaciones de suma y resta de fechas aquí no se refieren a la suma y resta entre fechas. Porque la suma y resta entre fechas no tiene significado práctico. Por ejemplo, 2023-8-1 menos 2023-1-1, decimos que hay una diferencia de 7 meses, pero el número de días de cada mes es diferente y también se divide en años ordinarios y años bisiestos. Por lo tanto, no puedes usar años o meses como unidades. Debes usar días como unidades para obtener resultados precisos. Cuente desde la fecha menor día a día hasta contar hasta la fecha mayor, la diferencia entre las dos fechas será tantos días como cuente. Entonces necesitamos implementar la suma y resta de fechas y números enteros para calcular el número de días entre dos fechas.

__iadd__ (sumar iguales)

        ¿Por qué se utiliza aquí el método __iadd__ en lugar del método __add__? Debido a que el método __add__ implica una copia profunda, cuando se agregan dos objetos, el objeto en sí no cambiará y solo se devolverá el resultado agregado. Sin embargo, la adición de la clase de fecha cambiará los valores de los atributos __año, __mes y __día. Si no queremos cambiar el objeto en sí, debemos copiar profundamente el objeto actual y luego usar el objeto copiado para el cálculo. . El método __iadd__ está destinado originalmente a cambiar el valor del objeto, por lo que primero implementamos __iadd__ y luego implementamos __add__ hasta __iadd__ para una mejor comprensión.

        Ahora pensemos en cómo implementar el método __iadd__. Cuando se agregan algunos días al atributo __day de la fecha, debemos determinar si el __day en este momento excede el número máximo de días del mes. Si excede, debe agregar uno al mes. Primero, reste el número de días del mes actual de __day y luego agregue uno al mes. Después de agregar uno al mes, debe determinar si el mes es mayor que 12. Si es mayor que 12, debe agregar uno al año. Primero, agregue uno al año y luego reasigne el mes a 1. . Después de una ronda de juicio, juzgue si __day es mayor que la cantidad de días del mes actual, hasta que __day sea menor o igual a la cantidad de días del mes actual. Entonces definimos el método __iadd__:

    def __iadd__(self, other):
        self.__day += other  # __day加上数天
        while self.__day > self._month_day():  # 以__day大于当月天数作为循环条件
            self.__day -= self._month_day()  # __day减去当月天数
            self.__month += 1  # 月份加一
            if self.__month == 13:  # 判断月份是否等于13
                self.__month = 1  # 把月份重置为1
                self.__year += 1  # 把年份加一
        return self  # 返回自己(只要other不为0,这时属性__day、__month、__year或许已经改变)

Después de implementar el método __iadd__, podemos realizar operaciones += en instancias de MyDate.

date1 = MyDate()
print(date1)
date1 += 200
print(date1)

Los resultados de la ejecución son los siguientes:

El resultado de la ejecución muestra que 200 días desde el 1-1-2023 llegarán al 20-7-2023. Podemos verificar manualmente si el resultado es correcto.

        Las instancias de MyDate pueden realizar += operaciones con números enteros, pero si otros usan instancias de MyDate para realizar += operaciones con tipos de punto flotante, no se ajustará a nuestras ideas de diseño. Entonces también podemos usar un decorador para determinar si el tipo de otro es un número entero. Si es un número entero, se realizará la operación +=. Si no es un número entero, se generará un error.

from functools import wraps


class UpDateError(Exception):
    """修改日期错误"""

    def __str__(self):
        return "The right operand can only be an integer"


def check_int(func):
    """
    检查右操作数是否为整形的装饰器\n
    如果不是抛出 UpDateError\n
    :param func: 被装饰的函数
    :return:
    """
    @wraps(func)
    def check(*args, **kwargs):
        if type(args[1]) != int:
            raise UpDateError
        result = func(*args, **kwargs)
        return result
    return check

Podemos utilizar la función decorativa check_int para todas las operaciones de suma y resta.

__add__ (adición)

        Ahora que hemos implementado el método __iadd__, __add__ es fácil de escribir. La diferencia entre += y + es que += reasignará el resultado de la suma al objeto, mientras que + solo devolverá el resultado de la suma y no reasignará el objeto. Entonces, solo tenemos una copia profunda del objeto, luego realizamos la operación += en el objeto de copia profunda y finalmente devolvemos el objeto de copia profunda, evitando así que se cambie el objeto original. Entonces usamos la función de copia profunda en copia para implementar el método __add__:

    @check_int
    def __add__(self, other):
        new = deepcopy(self)  # 深拷贝自己
        new += other  # 用深拷贝的对象来做+=运算
        return new  # 返回深拷贝对象

Verifiquemos si la operación de suma cambiará el valor del objeto original.

date1 = MyDate()
date2 = date1 + 200
print(date1)
print(date2)

Los resultados de la ejecución son los siguientes:

Podemos encontrar que la operación de suma no cambiará el valor del objeto original.

__isub__ (menos igual a)

        El mismo principio que la suma y la resta también requiere una copia profunda, por lo que primero implementamos el método __isub__. ¿Cómo implementar el método __isub__?Cuando se restan varios días del atributo de fecha __day, debemos determinar si el __day en este momento es un número negativo. Si es un número negativo, debe tomar prestado el número del mes, primero restar uno del mes y luego sumar el número de días del mes actual a __día. Después de restar uno del mes, debe determinar si el mes es 0. Si el mes es 0, debe tomar prestado un número del año. Primero, reste uno del año y luego reasigne el mes a 12. Año menos uno. Es necesario determinar si el año es menor que 0. Si es menor que 0, se generará un error. Después de una ronda de juicio, juzgue si __day es menor o igual a 0 hasta que __day sea mayor que 0.

    @check_int
    def __isub__(self, other):
        self.__day -= other
        while self.__day <= 0:
            self.__month -= 1
            if self.__month == 0:
                self.__year -= 1
                self.__month = 12
                if self.__year < 0:
                    raise ValueError('year cannot be less than zero')
            self.__day += self._month_day()
        return self

Después de implementar el método __isub__, las instancias de MyDate pueden realizar la operación -=.

date1 = MyDate()
date1 -= 100
print(date1)

Los resultados de la ejecución son los siguientes:

El resultado de la ejecución muestra que 100 días desde el 2023-1-1 hasta el 2022-9-23, podemos verificar manualmente si el resultado es correcto.

__sub__ (resta)

        Después de implementar el método __isub__, es sencillo implementar el método __sub__. La diferencia entre -= y - es que -= reasignará el resultado de la resta al objeto, mientras que - solo devolverá el resultado de la resta y no reasignará el objeto. Entonces, solo tenemos una copia profunda del objeto, luego realizamos la operación -= en el objeto de copia profunda y finalmente devolvemos el objeto de copia profunda, evitando así que se cambie el objeto original. Entonces usamos la función de copia profunda en copia para implementar el método __sub__:

    @check_int
    def __sub__(self, other):
        new = deepcopy(self)
        new -= other
        return new

Verifiquemos si hacer la operación de resta cambiará el valor del objeto original.

date1 = MyDate()
date2 = date1 - 100
print(date1)
print(date2)

Los resultados de la ejecución son los siguientes:

Podemos encontrar que hacer una resta no cambiará el valor del objeto original.

        Aunque hemos implementado el método de sumar y restar días a una fecha, en realidad hay un ERROR en él. Es decir, cuando el número de días es negativo, habrá problemas con los resultados después de la suma y la resta.

date1 = MyDate()
date2 = date1 + -200
date3 = date1 - -100
print(date1)
print(date2)
print(date3)

Los resultados de la ejecución son los siguientes:

Entonces necesitamos agregar lógica para procesar la suma y resta de números negativos en __iadd__ y __isub__. Sumar un número negativo es equivalente a restar un número positivo, por lo que la resta es la única forma de sumar un número negativo. Necesitamos agregar un juicio en __iadd__ y usar la resta cuando otro es menor que 0. Restar un número negativo es equivalente a sumar un número positivo, por lo que la suma es la única forma de restar un número negativo. Necesitamos agregar un juicio en __isub__ y usar la suma cuando otro es menor que 0. Entonces volvemos a modificar __iadd__ y __isub__:

    @check_int
    def __iadd__(self, other):
        if other < 0:  # 增加判断
            return self - -other  # 返回减法结果
        self.__day += other
        while self.__day > self._month_day():
            self.__day -= self._month_day()
            self.__month += 1
            if self.__month == 13:
                self.__month = 1
                self.__year += 1
        return self

    @check_int
    def __isub__(self, other):
        if other < 0:  # 增加判断
            return self + -other  # 返回加法结果
        self.__day -= other
        while self.__day <= 0:
            self.__month -= 1
            if self.__month == 0:
                self.__year -= 1
                self.__month = 12
                if self.__year < 0:
                    raise ValueError('year cannot be less than zero')
            self.__day += self._month_day()
        return self

Ahora podemos sumar y restar correctamente números negativos a las fechas.

date1 = MyDate()
date2 = date1 + -200
date3 = date1 - -100
print(date1)
print(date2)
print(date3)

 Los resultados de la ejecución son los siguientes:

Calcular el intervalo de fechas

        También podemos diseñar un método de instancia para calcular el intervalo entre dos fechas. Primero, use una operación de comparación para determinar el tamaño de las dos fechas y luego continúe agregando la fecha más pequeña para ver cuántas unidades se pueden agregar para que sean iguales. la fecha más grande Podemos obtener el número de días entre dos fechas. Para evitar cambiar el tamaño de la fecha, aún necesita usar copia profunda para hacer una copia profunda.

    @check_type
    def interval(self, other):
        day = 0
        if self < other:
            little, big = deepcopy(self), other  # self小时就深拷贝self
        elif self > other:
            little, big = deepcopy(other), self  # other小时就深拷贝other
        else:
            return day  # 相等时直接返回0
        while little < big:  # 以小的日期小于大的日期为循环条件
            little += 1  # 小的日期加一
            day += 1  # 天数加一
        return day  # 返回间隔天数

Ahora podemos obtener fácilmente el número de días entre dos fechas.

date1 = MyDate()
date2 = MyDate(2023, 8, 1)
print(date1.interval(date2))

Los resultados de la ejecución son los siguientes:

obtener fecha actual

        También podemos diseñar un método de clase para obtener la fecha actual. ¿En cuanto a por qué es un método de clase en lugar de un método de instancia? Debido a que la fecha actual es una instancia, no es necesario pasar la instancia a través de la instancia. Obtener instancias directamente a través de clases puede reducir el consumo de memoria y hacer que la lógica sea más clara, y las instancias también pueden usar métodos de clase.

        Aquí necesitamos usar la biblioteca de tiempo en Python para obtener el año, mes y día de la fecha actual, y luego usar la clase para crear una instancia de una fecha y devolverla.

    @classmethod
    def now(cls):
        time_list = time.strftime('%Y-%m-%d').split('-')
        return cls(int(time_list[0]), int(time_list[1]), int(time_list[2]))

Podemos obtener la instancia de la fecha actual mediante el método now.

date1 = MyDate.now()
print(date1)

Los resultados de la ejecución son los siguientes:

Formatear fecha de salida

        Cuando estaba usando la función strftime en el tiempo hace un momento, sentí que la función strftime era bastante divertida. Solo necesitamos darle una cadena de formato de fecha y nos devolverá una fecha formateada, y este formato lo configuramos nosotros mismos. Entonces podemos implementar un método similar en MyDate para devolver cadenas de fecha en varios formatos.

    def strfdate(self, format: str):
        """
        %Y  Year with century as a decimal number.\n
        %m  Month as a decimal number [01,12].\n
        %d  Day of the month as a decimal number [01,31].\n
        :param format: format string
        :return: format date
        """
        _month = f'{self.__month:0>2}'
        _day = f'{self.__day:0>2}'
        return format.replace('%Y', str(self.__year)).replace('%m', _month).replace('%d', _day)

Podemos usar el método strfdate para obtener la fecha formateada.

date = MyDate(2023, 2, 28)
print(date.strfdate('公元 %Y年 %m月 %d日'))
print(date.strfdate('年: %Y 月: %m 日: %d'))
print(date.strfdate('%Y-%m-%d'))

Los resultados de la ejecución son los siguientes:

Calcular el día de la semana.

        También podemos diseñar un método de instancia para obtener el día de la semana en que se encuentra la fecha actual. Sucede que 2023-1-1 es domingo, por lo que usamos 2023-1-1 como marca. Primero, use el método de intervalo para calcular el número de días entre la fecha actual y 2023-1-1, y luego use el número de intervalo para calcular el resto de 7. El resto resultante es el día de la semana.

    def week(self):
        _mark = MyDate(2023, 1, 1)
        _week = ['日', '一', '二', '三', '四', '五', '六']
        _day = self.interval(_mark)
        _day = _day % 7
        return '星期' + _week[_day]

Probemos el método de la semana:

date1 = MyDate(2023, 2, 28)
print(date1.week())  # 星期二
date2 = MyDate(2022, 12, 31)
print(date2.week())  # 星期六

Los resultados de la ejecución son los siguientes:

Podemos encontrar que hay un ERROR en la semana, no hay problema cuando la fecha actual es mayor que 2023-1-1, pero sí hay problema cuando la fecha actual es menor que 2023-1-1. Debido a que contamos hacia adelante de domingo a sábado, cuando la fecha actual es menor que 2023-1-1, debemos contar hacia atrás de sábado a domingo. Entonces necesitamos agregar un estado. Cuando la fecha actual es mayor que 2023-1-1, el estado es 1. Cuando la fecha actual es menor que 2023-1-1, el estado es -1. El estado se utiliza para determinar si el valor final de la lista _week se adelanta o retrocede.

    def week(self):
        _mark = MyDate(2023, 1, 1)
        _week = ['日', '一', '二', '三', '四', '五', '六']
        _day = self.interval(_mark)
        state = 1 if self >= _mark else -1
        _day = _day % 7
        return '星期' + _week[_day * state]

Probemos nuevamente el método de la semana:

date1 = MyDate(2023, 2, 28)
print(date1.week())  # 星期二
date2 = MyDate(2022, 12, 31)
print(date2.week())  # 星期六

 Los resultados de la ejecución son los siguientes:

fecha de iteración

        Podemos definir __iter__ y __next__ en MyDate para hacer de la instancia MyDate un iterador para iterar fechas. Diseñemos un método de iteración para que la fecha actual pueda iterarse hasta el último día del año. Por esta razón, necesitamos agregar un atributo de instancia __next en __init__, que se usa como condición para finalizar la iteración. Siempre que nuestro año llegue al siguiente, la iteración terminará.

    def __init__(self, year=2023, month=1, day=1):
        self.__year = year
        self.__month = month
        self.__day = day
        if not (self.__year >= 0 and (0 < self.__month < 13) and (0 < self.__day <= self._month_day())):
            raise MyDateError(f'{self.__year}-{self.__month:0>2}-{self.__day:0>2}')
        self.__next = self.__year + 1

 __iter__ (devuelve el iterador)

        En __iter__ necesitamos devolver un iterador. Generalmente devolvemos self (self), pero si desea que el valor del objeto no cambie después de la iteración, devuelva una copia profunda del objeto. Antes de devolver el iterador, asigne el atributo __next al año siguiente.

    def __iter__(self):
        new = deepcopy(self)
        return new

 __siguiente__ (iteración)

        En __next__ necesitamos establecer las condiciones de iteración para iterar el objeto de instancia y devolver el valor de iteración. Se genera un error StopIteration cuando finaliza la iteración.

    def __next__(self):
        self += 1  # 每次迭代日期递增1天
        if self.__year < self.__next:  # 判断年份是否到了下一年
            return self  # 返回迭代日期
        raise StopIteration  # 抛出StopIteration错误

 Ahora intentemos iterar sobre fechas:

date = MyDate.now()
for i in date:
    print(i)

Podemos dejar que la instancia MyDate itere desde la fecha actual hasta el último día del año. 

        De hecho, hay un ERROR aquí, es decir, cuando sumamos y restamos fechas, si el año de la fecha cambia, ocurrirá un ERROR durante la iteración. Debido a que el atributo __next no cambia con el cambio del atributo __year, hay dos formas de solucionarlo. Una es agregar cambios al atributo __next en el método __iadd__ y el método __isub__; la otra es personalizar el método __setattr__ para cambiar __next al mismo tiempo cuando cambia __year. Aquí elegí la primera forma::

    @check_int
    def __iadd__(self, other):
        """ Return self+=other. """
        if other < 0:
            return self - -other
        self.__day += other
        while self.__day > self._month_day():
            self.__day -= self._month_day()
            self.__month += 1
            if self.__month == 13:
                self.__month = 1
                self.__year += 1
                self.__next += 1  # 更改__next
        return self

    @check_int
    def __isub__(self, other):
        """ Return self-=other. """
        if other < 0:
            return self + -other
        self.__day -= other
        while self.__day <= 0:
            self.__month -= 1
            if self.__month == 0:
                self.__year -= 1
                self.__month = 12
                if self.__year < 0:
                    raise ValueError('year cannot be less than zero')
                self.__next -= 1  # 更改__next
            self.__day += self._month_day()
        return self

descriptor de propiedad

        Los descriptores se utilizan para describir los atributos de una clase. Siempre que una clase implemente los métodos __get__, __set__ y __delete__, su instancia puede convertirse en un descriptor. Cuando un atributo de clase es un descriptor, ejecutaremos __get__ para acceder a él, __set__ para establecer su valor y __delete__ para eliminarlo. Por lo tanto, podemos establecer varias lógicas condicionales en __get__, __set__ y __delete__ para lograr el propósito de controlar los atributos de clase.

        La propiedad es un descriptor integrado en Python, está directamente integrado en el intérprete de Python y la lógica también se implementa en lenguaje C. Podemos crear tres atributos de clase de año, mes y día en MyDate y usar propiedades para describirlos. ¿Cuál es el propósito de hacer esto? Es pretender decirles a los demás que tengo tres atributos de clase: año, mes y día. Puede utilizar estos tres atributos para obtener los valores de año, mes y día, establecer los valores de año, mes y día y eliminar los valores de año, mes y día. Pero el éxito de la operación que desea realizar depende de si le permito hacerlo. Pero, de hecho, los valores de año, mes y día están en los atributos de instancia __año, __mes y __día, y los atributos de clase año, mes y día son solo una tapadera para engañar a la gente. Si queremos acceder, modificar y establecer los valores de __año, __mes y __día, podemos usar completamente _MyDate__year, _MyDate__month y _MyDate__day para lograr nuestros objetivos. Parece una tontería usar propiedades, pero hay muchas clases que usan propiedades para engañar a la gente. Pero la propiedad puede considerarse como un recordatorio amigable, que le recuerda que no debe cambiar las propiedades al azar para evitar errores, pero si realmente desea cambiar las propiedades, la propiedad no puede detenerlo a menos que pueda describir las propiedades de la instancia. Por lo tanto, también podemos usar la propiedad en MyDate para recordar a otros que no cambien las propiedades al azar y, por cierto, engañen a los novatos.

    def get_year(self):
        return self.__year

    def get_month(self):
        return self.__month

    def get_day(self):
        return self.__day

    year = property(lambda self: self.get_year(), lambda self, v: None, lambda self: None)  # default

    month = property(lambda self: self.get_month(), lambda self, v: None, lambda self: None)  # default

    day = property(lambda self: self.get_day(), lambda self, v: None, lambda self: None)  # default

La propiedad puede recibir 4 parámetros. El primer parámetro es el método que se ejecutará al acceder a la propiedad. Este método solo recibe un parámetro, que es la instancia; el segundo parámetro es el método que se ejecutará al configurar la propiedad. Este método necesita recibe dos parámetros. La instancia y el valor a establecer, el tercer parámetro es el método a ejecutar al eliminar el atributo. Este método solo recibe un parámetro, que es la instancia, el cuarto parámetro es la cadena de descripción, que es inútil. y generalmente no aprobado. Por supuesto, lo que se muestra aquí es solo un uso de la propiedad. Hay otro uso de la propiedad, que es usarlo como una función de decoración para decorar métodos en una clase, pero tampoco le impide modificar por la fuerza las propiedades de la instancia. . La ventaja es que puede usar métodos como atributos, es decir, puede ejecutar métodos sin agregar paréntesis después del nombre del método.

date = MyDate(2023, 2, 28)
date.year = 2020
del date.year
print(date.year)
print(date)

 Los resultados de la ejecución son los siguientes:

A partir de los resultados de la ejecución, podemos encontrar que establecer el atributo año no es válido, eliminar el atributo año no es válido y acceder al atributo año es válido. Esto se debe a que al usar propiedades para describir el efecto del año, podemos controlar el acceso, la configuración y la eliminación de propiedades, pero solo se pueden describir propiedades de clase, no propiedades de instancia.

código completo

import time
from copy import deepcopy
from functools import wraps
from typing import overload


class MyDateError(Exception):
    """非法日期错误"""

    def __init__(self, error_date):
        self.date = error_date

    def __str__(self):
        return f"The data should conform to the gregorian standards\nno exists date {self.date}"


class NoMyDateError(Exception):
    """不是MyDate类型错误"""

    def __str__(self):
        return "MyDate can only be compared to MyDate"


class UpDateError(Exception):
    """修改日期错误"""

    def __str__(self):
        return "The right operand can only be an integer"


def check_int(func):
    """
    检查右操作数是否为整形的装饰器\n
    如果不是抛出 UpDateError\n
    :param func: 被装饰的函数
    :return:
    """
    @wraps(func)
    def check(*args, **kwargs):
        if type(args[1]) != int:
            raise UpDateError
        result = func(*args, **kwargs)
        return result
    return check


def check_type(func):
    """
    检查比较的类型是否为MyDate的装饰器\n
    如果不是抛出 NoMyDateError\n
    :param func: 被装饰的函数
    :return:
    """
    @wraps(func)
    def check(*args, **kwargs):
        for _type in args:
            if type(_type) != MyDate:
                raise NoMyDateError
        result = func(*args, **kwargs)
        return result
    return check


class MyDate:
    """
    日期类
        def __init__(self, year=2023, month=1, day=1):
            self.__year = int(year)\n
            self.__month = int(month)\n
            self.__day = int(day)\n
    提供日期直接加减天数的操作方式
        MyDate += int or MyDate -= int\n
        MyDate + int or MyDate - int
    日期之间的比较
        MyDate == MyDate\n
        MyDate != MyDate\n
        MyDate < MyDate\n
        MyDate <= MyDate\n
        MyDate > MyDate\n
        MyDate >= MyDate\n
    now()方法得到当前日期\n
    interval(MyDate)方法计算当前日期和传入日期之间的间隔天数\n
    strfdate('%Y-%m-%d')方法可以格式化输出当前日期\n
    data()返回日期字符串: 年-月-日\n
    week()返回当前日期是星期几\n
    当前日期可迭代至当年第一天
    """
    __non_leap = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

    @overload
    def __init__(self, year=2023, month=1, day=1):
        ...

    @overload
    def __init__(self, year='2023', month='1', day='1'):
        ...

    def __init__(self, year=2023, month=1, day=1):
        self.__year = int(year)
        self.__month = int(month)
        self.__day = int(day)
        if not (self.__year >= 0 and (0 < self.__month < 13) and (0 < self.__day <= self._month_day())):
            raise MyDateError(f'{self.__year}-{self.__month:0>2}-{self.__day:0>2}')
        self.__next = self.__year + 1

    def get_year(self):
        return self.__year

    def get_month(self):
        return self.__month

    def get_day(self):
        return self.__day

    @classmethod
    def now(cls):
        """返回当前日期实例"""
        time_list = time.strftime('%Y-%m-%d').split('-')
        return cls(int(time_list[0]), int(time_list[1]), int(time_list[2]))

    def strfdate(self, format: str):
        """
        %Y  Year with century as a decimal number.\n
        %m  Month as a decimal number [01,12].\n
        %d  Day of the month as a decimal number [01,31].\n
        :param format: format string
        :return: format date
        """
        _month = f'{self.__month:0>2}'
        _day = f'{self.__day:0>2}'
        return format.replace('%Y', str(self.__year)).replace('%m', _month).replace('%d', _day)

    @check_type
    def interval(self, other):
        """
        返回当前日期和另一个日期的间隔天数\n
        :param other: 另一个日期
        :return: 间隔天数
        """
        day = 0
        if self < other:
            little, big = deepcopy(self), other
        elif self > other:
            little, big = deepcopy(other), self
        else:
            return day
        while little < big:
            little += 1
            day += 1
        return day

    def date(self):
        """返回日期字符串"""
        return f'{self.__year}-{self.__month:0>2}-{self.__day:0>2}'

    def week(self):
        """返回当前日期是星期几"""
        _mark = MyDate(2023, 1, 1)
        _week = ['日', '一', '二', '三', '四', '五', '六']
        _day = self.interval(_mark)
        state = 1 if self >= _mark else -1
        _day = _day % 7
        return '星期' + _week[_day * state]

    def _month_day(self):
        """ Return how many days are there in the same month. """
        if self.__month == 2 and ((self.__year % 4 == 0 and self.__year % 100 != 0) or (self.__year % 400 == 0)):
            return 29
        return self.__non_leap[self.__month]

    @check_int
    def __iadd__(self, other):
        """ Return self+=other. """
        if other < 0:
            return self - -other
        self.__day += other
        while self.__day > self._month_day():
            self.__day -= self._month_day()
            self.__month += 1
            if self.__month == 13:
                self.__month = 1
                self.__year += 1
                self.__next += 1
        return self

    @check_int
    def __add__(self, other):
        """ Return self+other. """
        new = deepcopy(self)
        new += other
        return new

    @check_int
    def __isub__(self, other):
        """ Return self-=other. """
        if other < 0:
            return self + -other
        self.__day -= other
        while self.__day <= 0:
            self.__month -= 1
            if self.__month == 0:
                self.__year -= 1
                self.__month = 12
                if self.__year < 0:
                    raise ValueError('year cannot be less than zero')
                self.__next -= 1
            self.__day += self._month_day()
        return self

    @check_int
    def __sub__(self, other):
        """ Return self-other. """
        new = deepcopy(self)
        new -= other
        return new

    @check_type
    def __eq__(self, other):
        """ Return self==other. """
        return self.__year == other.year and self.__month == other.month and self.__day == other.day

    @check_type
    def __ne__(self, other):
        """ Return self!=other. """
        return not self == other

    @check_type
    def __lt__(self, other):
        """ Return self<other. """
        if self.__year < other.year:
            return True
        elif self.__year == other.year and self.__month < other.month:
            return True
        elif self.__year == other.year and self.__month == other.month and self.__day < other.day:
            return True
        return False

    @check_type
    def __gt__(self, other):
        """ Return self>other. """
        return not self <= other

    @check_type
    def __le__(self, other):
        """ Return self<=other. """
        return self < other or self == other

    @check_type
    def __ge__(self, other):
        """ Return self>=other. """
        return not self < other

    def __iter__(self):
        """ Return new -> deepcopy(self). """
        new = deepcopy(self)
        return new

    def __next__(self):
        """ Iterator self. """
        self += 1
        if self.__year < self.__next:
            return self
        raise StopIteration

    def __str__(self):
        """ Return self string. """
        return f'{self.__year}-{self.__month:0>2}-{self.__day:0>2}'

    year = property(lambda self: self.get_year(), lambda self, v: None, lambda self: None)  # default

    month = property(lambda self: self.get_month(), lambda self, v: None, lambda self: None)  # default

    day = property(lambda self: self.get_day(), lambda self, v: None, lambda self: None)  # default

Si tiene buenas ideas, puede implementarlas usted mismo en MyDate.

Supongo que te gusta

Origin blog.csdn.net/qq_40148262/article/details/132191795
Recomendado
Clasificación