NB02 - Conceitos basicos da linguagem Python [Link]
html
Introdução à Programação em Python
Notebook 02 - Conceitos básicos da linguagem Python
Carlos Caleiro, Jaime Ramos
Dep. Matemática, IST - 2016
(actualizado em 8 de Fevereiro de 2018)
A linguagem Python
A linguagem Python, extremamente versátil e cada vez mais utilizada não apenas na academia mas
também, e fundamentalmente, na indústria, tem três características fundamentais.
(1) Python é uma linguagem interpretada. Isto significa que funciona num ambiente em que um
interpretador interactivo avalia cada expressão no contexto definido, tendo por efeito o cálculo de um
resultado (ou uma mensagem de erro) e eventual actualização do contexto. Esta classificação surge por
oposição às linguagens ditas compiladas (como por exemplo C, C++ ou Fortran) em que todo um
programa é analisado por um compilador que, caso não detecte erros, produz então um executável que
pode ser utilizado.
(2) Python é uma linguagem de programação orientada a objectos (OO). Não nos vamos deter
demasiado nesta questão, de momento, mas é útil que tenhamos presente que a linguagem Python
manipula objectos. Cada objecto é caracterizado por um identificador, um valor corrente, e um menu de
métodos e operações que lhe são aplicáveis (e que são determinados pela classe do objecto).
(3) Python é uma linguagem fortemente tipada, o que significa que a aplicação de uma operação a um
valor inadequado resulta numa mensagem de erro. O sistema de tipos é dinâmico e os tipos são
implícitos, o que facilita bastante o trabalho do programador (que não necessita de declarar o tipo dos
objectos que manipula), mas em cada momento cada expressão tem associado um objecto cujo tipo
pode ser interpretado como sendo a classe a que pertence. Para certos tipos, ditos imutáveis, o valor que
está associado a um objecto é sempre o mesmo. Noutros casos, ditos tipos mutáveis, há métodos
disponíveis para alterar o valor associado a um objecto.
A forma como os valores (objectos) são manipulados é definida por intermédio de comandos, que
incluem expressões que referem os valores (objectos) em causa.
In [1]: 6+7
Out[1]: 13
1 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
Valores e tipos
Em Python, cada valor (objecto) pertence a um determinado tipo (classe). Esse tipo determina quais as
operações que podem ser aplicadas a esse valor (objecto). Trata-se portanto de uma linguagem tipada,
mas onde os tipos não são explícitos, o que facilita bastante o trabalho do utilizador (apesar de poder
trazer outro tipo de problemas, a jusante, na depuração de erros). Os tipos básicos disponíveis mais úteis
são: números inteiros (int), números reais (float) e valores lógicos (bool). Os tipos básicos são
imutáveis.
In [2]: type(1)
Out[2]: int
In [3]: id(1)
Out[3]: 4297514912
In [4]: type(1.0)
Out[4]: float
In [5]: id(1.0)
Out[5]: 4391795088
O tipo bool disponibiliza as constantes True e False, bem como diversos operadores lógicos (and, not,
or). O Python tem ainda diversos operadores de comparação que devolvem valores lógicos: <, <=, >, >=,
==, !=, is, is not, isinstance.
In [5]: (1==1)
Out[5]: True
In [6]: type(1==1)
Out[6]: bool
In [1]: 1!=3 and (3<1 or (not 0==1))
Out[1]: True
In [8]: 2 is 1+1
Out[8]: True
In [9]: 2==2.0
Out[9]: True
2 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
Os operadores lógicos is e is not comparam os identificadores dos objectos em causa. Vale a pena
compreender a relação de is com ==. Obviamente, se duas expressões denotam o mesmo objecto terão
também o mesmo valor. O contrário não é verdade em geral, nomeadamente para tipos mutáveis.
In [10]: 2 is 2.0
Out[10]: False
O operador isinstance testa a pertença de um valor (objecto) a um tipo (classe).
In [11]: isinstance(2.0,int)
Out[11]: False
In [12]: isinstance(2.0,float)
Out[12]: True
Além dos tipos básicos, há também os tipos sequenciais, ou iteráveis: cadeias de caracteres (str), listas
(list), vectores (tuple), registos (dictionary), conjuntos (set), e ficheiros. Cadeias de caracteres,
vectores e ficheiros são tipos imutáveis, enquanto listas, registos e conjuntos são tipos mutáveis. É
possível ainda introduzir novos tipos, por definição das respectivas classes.
Atentamos de seguida nas cadeias de caracteres, deixando os restantes tipos referidos para mais tarde.
Dedicaremos o próximo notebook ao tipo das listas, pela sua importância, o que nos permitirá por um
lado compreender melhor a natureza OO da linguagem Python (já que o tipo das listas é mutável), e por
outro lado introduzir os conceitos essenciais sobre outros tipos iteráveis. A definição de novas classes
enquadra-se na exploração mais a fundo da orientação a objectos, um tópico mais avançado que
abordaremos mais tarde.
As cadeias de caracteres, ou $\textit{strings}$, são representadas em Python usando aspas (") ou plicas
('). Estão disponíveis diversas operações para manipular $\textit{strings}$.
In [13]: type("bom")
Out[13]: str
In [6]: "boma"/2
------------------------------------------------------------------
---------
TypeError Traceback (most recent c
all last)
<ipython-input-6-c4ff888eb18b> in <module>()
----> 1 "boma"/2
TypeError: unsupported operand type(s) for /: 'str' and 'int'
In [15]: "bom"+" "+"dia"
Out[15]: 'bom dia'
3 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
In [16]: "bom dia"[0]
Out[16]: 'b'
In [17]: "bom dia"[1]
Out[17]: 'o'
In [18]: "bom dia"[-1]
Out[18]: 'a'
In [19]: "bom dia"[2:5]
Out[19]: 'm d'
In [20]: "bom dia"[4:]
Out[20]: 'dia'
O Python disponibiliza também várias funcionalidades que permitem converter valores entre diferentes
tipos.
In [21]: int("523")+1
Out[21]: 524
In [22]: int(2.9)
Out[22]: 2
In [23]: float(2)
Out[23]: 2.0
In [24]: "depois de "+str(123)+" vem "+str(124)
Out[24]: 'depois de 123 vem 124'
In [25]: chr(123)
Out[25]: '{'
In [26]: bool("")
Out[26]: False
In [27]: bool("abc")
Out[27]: True
As cadeias de caracteres podem ainda ser manipuladas recorrendo a métodos específicos, como
veremos abaixo.
4 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
Expressões
Tal como nos vários exemplos que já vimos, às expressões em Python está associado um valor. As
expressões constroem-se a partir de expressões atómicas (literais), e de nomes, por aplicação de
operadores e funções. Já vimos uma panóplia de operadores básicos, aritméticos, lógicos e sobre
strings. Veremos adiante como podemos programar os nossos próprios operadores. Nas expressões
usam-se parênteses para associação, como é usual. A extensão Sympy permite analisar a estrutura de
uma expressão.
In [28]: from sympy import *
In [29]: x,y,z=symbols("x"),symbols("y"),symbols("z")
In [30]: srepr(x+y*z)
Out[30]: "Add(Symbol('x'), Mul(Symbol('y'), Symbol('z')))"
In [31]: srepr((x+y)*z)
Out[31]: "Mul(Symbol('z'), Add(Symbol('x'), Symbol('y')))"
In [32]: ?sympify
Expressões alternativas
Uma forma particularmente útil de construir expressões, é a composição alternativa.
In [33]: 2 if 1==1 else 3
Out[33]: 2
In [34]: 2 if 1==2 else 3 if 1==1 else 4
Out[34]: 3
Nomes e atribuição
5 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
É possível, frequente e desejável, associar nomes a valores (objectos), por forma a memorizá-los e ter
um mecanismo simples para os referir.
Na terminologia das linguagens de programação é usual chamar variáveis aos nomes. No entanto,
usaremos preferencialmente nomes (e não variáveis) pois a linguagem Python dá um uso particular ao
mecanismo de associação de valores a nomes que a distingue da maioria das linguagens de
programação.
Em Python, um nome é uma qualquer sequência de caracteres (sem aspas, não se trata de uma string),
cujo primeiro caracter é tipicamente uma letra, maiúscula ou minúscula. Nomes começados por _
também são possíveis, mas convenciona-se que têm um significado especial, de que falaremos mais
tarde. Convenciona-se ainda que nomes cujo primeiro caracter é uma letra maiúscula são usados para
definir classes de objectos, algo a que retornaremos daqui a algumas aulas. Para já, portanto, usaremos
apenas nomes iniciados com uma letra minúscula.
Note-se que há vários nomes reservados, predefinidos na linguagem Python, que não podemos utilizar,
parte dos quais até já conhecemos.
In [35]: import keyword
print([Link])
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class',
'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'fo
r', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlo
cal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'wit
h', 'yield']
In [36]: and=3
File "<ipython-input-36-8a3b1ae9e2b5>", line 1
and=3
^
SyntaxError: invalid syntax
O mecanismo de associação de um nome a um valor é conhecido por atribuição.
In [37]: a=5
In [38]: a+a**2
Out[38]: 30
In [39]: a
Out[39]: 5
In [40]: b=a
In [41]: b
Out[41]: 5
In [42]: a=a+1
6 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
In [43]: a
Out[43]: 6
In [44]: b
Out[44]: 5
Isto acontece porque o tipo dos objectos que estamos a manipular é imutável. O objecto que representa o
valor 5 vai sempre representar o valor 5. A atribuição a=a+1 associa então ao nome a o objecto que
corresponde a avaliar 5+1, que representa o valor 6. No entanto, o objecto associado a b não é alterado.
Veremos adiante que o mecanismo de atribuição tem um comportamento menos óbvio quando os valores
(objectos) manipulados são de tipos mutáveis, como as listas.
Vale a pena reforçar que os operadores de atribuição (=) e de comparação (==) são distintos, e têm
papéis muito diferentes, pelo que não devem ser confundidos.
Instruções
As instruções são a forma como podemos prescrever as acções que queremos efectuar. Instruções
básicas podem ser compostas para dar origem a instruções mais complexas. Entre as instruções básicas
mais comuns temos a avaliação de expressões e o mecanismo de atribuição, que vimos acima, a
invocação de métodos, e instruções de leitura e escrita (input/output).
Outras instruções básicas
Métodos
Cada tipo (classe) de valores (objectos) tem associada uma série de métodos. A invocação desses
métodos pode dar origem a um valor, bem como resultar em alterações nos objectos em causa, no caso
de serem mutáveis. Os métodos associados a cada tipo (classe) podem ser usados de duas formas.
In [45]: bit_length?
Object `bit_length` not found.
7 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
In [46]: help(int.bit_length)
Help on method_descriptor:
bit_length(...)
int.bit_length() -> int
Number of bits necessary to represent self in binary.
>>> bin(37)
'0b100101'
>>> (37).bit_length()
6
In [47]: int.bit_length(7)
Out[47]: 3
In [48]: (7).
Out[48]: 3
In [7]: x=8
In [50]: int.bit_length(x)
Out[50]: 4
In [51]: x.
Out[51]: 4
Os métodos associados a cada valor (objecto) podem ser facilmente consultados usando o mecanismo
de introspecção do IPython (completação por TAB a partir do objecto, ou do seu tipo).
In [1]: from [Link] import Image
Image("[Link]")
Out[1]:
8 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
Neste caso, trabalhando sobre inteiros, que correspondem a um tipo imutável, a invocação de métodos
tem um efeito em tudo semelhante ao do uso de operadores em expressões. Veremos adiante, no
contexto de tipos mutáveis, que a situação pode ser um pouco mais complexa.
Relativamente às cadeias de caracteres, ou strings, valerá a pena explorar os métodos disponíveis.
In [53]: Image("[Link]")
Out[53]:
Outra forma expedita de aceder aos métodos disponíveis de um tipo, ou de um objecto desse tipo,
consiste em usar dir.
9 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
In [2]: dir(str)
10 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
Out[2]: ['__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'capitalize',
'casefold',
'center',
'count',
'encode',
'endswith',
'expandtabs',
'find',
'format',
'format_map',
'index',
'isalnum',
'isalpha',
'isdecimal',
'isdigit',
'isidentifier',
'islower',
'isnumeric',
'isprintable',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'maketrans',
11 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
'partition',
'replace',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill']
Leitura e escrita
Os mecanismos de leitura e escrita de dados (input/output) são fundamentais para a interacção desejável
entre os nossos programas e os seus utilizadores.
In [8]: input("valor? ")
valor? 67
Out[8]: '67'
In [9]: int(input("valor? "))+1
valor? 67
Out[9]: 68
In [56]: v=input("valor? ")
valor? 8
In [57]: print("valor =",v,", quadrado =",int(v)**2)
valor = 8 , quadrado = 64
Poderá ser útil saber que a mudança de linha corresponde ao caracter invisível representado por "\n".
In [58]: print("valor =",v,"\nquadrado =",int(v)**2)
valor = 8
quadrado = 64
É possível formatar o resultado de um comando print de formas mais sofisticadas, usando o método
format.
12 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
In [59]: "conseguir fazer {} dos {} exercícios do teste".format(3,5)
Out[59]: 'conseguir fazer 3 dos 5 exercícios do teste'
Leitura e escrita em ficheiros
É particularmente útil, em muitas situações, criar, ler e/ou escrever em ficheiros. O Python disponibiliza
formas expeditas para tal.
In [60]: %pwd
Out[60]: '/Users/ccal/Desktop/Dropbox/python'
Para criar um ficheiro de texto nesta localização (a que acedemos e podemos alterar usando magias do
IPython), basta fazer o seguinte (daria um erro se o ficheiro já existisse).
In [62]: f=open("[Link]","x")
In [63]: %ls
[Link]/
My First Jupyter [Link]
NB01 - IPython como ambiente de computação, cálculo e visualizaçã
[Link]
NB02 - Conceitos básicos da linguagem [Link]
NB03 - Listas e outros tipos iterá[Link]
NB04 - (limpar)Programação [Link]
NB05 - (limpar)Programação [Link]
NB06 - (limpar)Algoritmos de ordenaçã[Link]
NB07 - (limpar)Programação funcional e comparação entre paradigmas
de programaçã[Link]
NB08 - (limpar)Programação em grande [Link]
NB09 - (halfway)Simulação digital estocá[Link]
NB10 - (halfway)Programacao orientada a [Link]
NB11 - (missing)Verificação de [Link]
[Link]
[Link]
basic_animation.mp4
[Link]
[Link]
[Link]
[Link]
new
[Link]
old/
[Link]
[Link]
test.mp4
In [64]: type(f)
Out[64]: _io.TextIOWrapper
13 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
Uma vez criado e aberto o ficheiro podemos escrever nele o que entendermos, e de seguida fechá-lo.
In [65]: [Link]("bom dia\nboa tarde\nboa noite")
Out[65]: 27
In [66]: [Link]()
O número 27 que recebemos como resultado da escrita corresponde ao número de caracteres escritos.
Podemos agora ler o conteúdo do ficheiro.
In [67]: f=open("[Link]","r")
In [68]: [Link]()
Out[68]: 'bom dia\nboa tarde\nboa noite'
In [69]: [Link]()
Out[69]: ''
In [70]: [Link]()
Podemos também ler o ficheiro linha por linha.
In [71]: f=open("[Link]","r")
In [72]: [Link]()
Out[72]: 'bom dia\n'
In [73]: [Link]()
Out[73]: 'boa tarde\n'
In [74]: [Link]()
Out[74]: 'boa noite'
In [75]: [Link]()
Se abrirmos o ficheiro para escrita (usando "w") iremos reescrevê-lo, perdendo o conteúdo anterior. Caso
queiramos escrever no final do ficheiro devemos abri-lo usando "a".
In [76]: f=open("[Link]","a")
14 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
In [77]: [Link]("\nfim")
Out[77]: 4
In [78]: [Link]()
In [79]: f=open("[Link]","r")
In [80]: [Link]()
Out[80]: 'bom dia\nboa tarde\nboa noite\nfim'
In [81]: [Link]()
Instruções compostas
Composição sequencial
O mais simples dos mecanismos de composição de instruções é a denominada composição sequencial.
Consiste, essencialmente, na execução consecutiva de várias instruções, na ordem especificada.
In [82]: x=7;x=8;x=x+1;x
Out[82]: 9
O valor de x obtido não é surpreendente, se considerarmos que (;) prescreve a execução sequencial das
várias atribuições, da esquerda para a direita. Apesar de suportada, e muito comum noutros ambientes,
esta sintaxe é altamente desaconselhada em Python, por razões que promovem a legibilidade do código,
bem como os princípios essenciais da programação estruturada. A forma quase universalmente usada
em Python para expressar a composição sequencial é, pura e simplesmente, a mudança de linha.
In [83]: x=5
x=x+1
x
Out[83]: 6
Composição alternativa
De forma semelhante à composição alternativa de expressões, que vimos acima, é também possível, e
extremamente útil, a composição alternativa de instruções.
15 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
In [84]: x=2
if x<3:
x=x*2
elif x<5:
x=x+1
x=x**2
elif x<10:
x=x+1
else: x=0
Out[84]: 4
In [85]: x=4
if x<3:
x=x*2
elif x<5:
x=x+1
x=x**2
elif x<10:
x=x+1
else: x=0
Out[85]: 25
In [86]: x=8
if x<3:
x=x*2
elif x<5:
x=x+1
x=x**2
elif x<10:
x=x+1
else: x=0
Out[86]: 9
In [87]: x=100
if x<3:
x=x*2
elif x<5:
x=x+1
x=x**2
elif x<10:
x=x+1
else: x=0
Out[87]: 0
16 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
Note-se que a indentação (alinhamento dos comandos) é fundamental. Caso as condições não sejam
exaustivas e seja omitida a hipótese final (else) não é executada nenhuma instrução (ou é executada a
instrução que nada faz, que é designada em Python por pass).
In [88]: x=100
if x<50:
x=x+1
Out[88]: 100
Veremos mais tarde os mecanismos de composição iterativa de instruções.
Definição de funções
É frequente querermos definir um procedimento para ser utilizado para diferentes valores, no momento,
ou mais tarde.
In [12]: def escrevepercentagem(tot,val):
global w
w=int(100*val/tot)
print(w,"%")
In [2]: ?escrevepercentagem
A definição acima escreve, como percentagem, a razão entre os parâmetros val e tot. Por si só o
procedimento assim definido não devolve nenhum valor. Mas a definição age também por efeito colateral,
já que o resultado é guardado no nome w que se considera global.
In [13]: escrevepercentagem(25,11)
44 %
In [11]: w
Out[11]: 44
Vale a pena retirar a declaração de w é global, para compreender a diferença.
Por vezes, além do mais, queremos devolver explicitamente um valor calculado, caso em que o
procedimento se denomina uma função.
In [10]: def percentagem(tot,val):
return int(100*val/tot)
17 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
A definição, por si só, apenas acrescenta uma definição de percentagem ao sistema. A função assim
definida pode ser utilizada várias vezes, a partir do momento em que é definida.
In [11]: percentagem(20,14)
Out[11]: 70
In [12]: x=percentagem(88,20)
x
Out[12]: 22
Naturalmente, uma função já definida pode ser usada na definição de novas funções.
In [17]: def percentagens(val1,val2,val3):
return [percentagem(val1,val3),percentagem(val2,val3),percentage
m(val1,val2)]
In [18]: alunos=102
In [19]: avaliados=78
In [20]: aprovados=64
In [21]: percentagens(alunos,avaliados,aprovados)
Out[21]: [62, 82, 76]
In [100]: def estatistica(w):
avals=[x for x in w if x!=0]
return sum(w)/len(avals),percentagens(len(w),len(avals),len([x
for x in avals if x>9.4]))
In [101]: estatistica([10,20,0,0,9,6,14])
Out[101]: (11.8, [42, 60, 71])
Comentários
Uma das boas práticas de programação consiste em comentar o que escrevemos por forma a explicitar,
para nós e principalmente para os outros, o significado de cada pedaço de código (rapidamente
perceberemos que o código que escrevemos se torna facilmente incompreensível). Linhas começadas
por # são consideradas comentários, e portanto não são avaliadas pelo interpretador.
In [102]: # estas linhas são irrelevantes para o interpretador
# mas poderão ser fundamentais para quem lê o código
18 de 19 02/01/2021, 00:30
NB02 - Conceitos basicos da linguagem Python [Link]
Sumário
Python é uma linguagem de programação interpretada e orientada a objectos (OO)
Interagimos com o interpretador por execução de instruções, que avaliam expressões e
actualizam o contexto da computação
Em Python cada expressão tem um tipo que determina as operações que lhe são aplicáveis;
números, inteiros ou reais, e valores lógicos correspondem a tipos básicos; os tipos não-básicos
são usualmente iteráveis (ou então correspondem a classes de objectos definidas pelo
programador)
As instruções básicas mais comuns, além da avaliação de expressões, são atribuições de
valores a nomes
As instruções podem ser compostas sequencialmente, alternativamente ou iterativamente,
dando origem a programas mais complexos
O objectivo final do programador é definir as funções (ou, mais tarde, métodos em classes) que
permitam resolver de forma expedita os problemas que lhe são colocados
Bibliografia
Introdução à Programação em Mathematica (3a edição): J. Carmo, A. Sernadas, C. Sernadas, F. M.
Dionísio, C. Caleiro, IST Press, 2014.
Think Python: How to think like a computer scientist: A. Downey, Green Tea Press, 2012.
Introduction to Computation and Programming Using Python (revised and expanded edition): J. V. Guttag,
MIT Press, 2013.
The Art of Computer Programming: D. E. Knuth, Addison-Wesley (volumes 1--3, 4A), 1998.
Learning Python (fifth edition): M. Lutz, O'Reilly Media, 2013.
Programação em Python: Introdução à programação utilizando múltiplos paradigmas: J. P. Martins, IST
Press, 2015.
Introdução à Programação em MatLab: J. Ramos, A. Sernadas e P. Mateus, DMIST, 2005.
Learning IPython for Interactive Computing and Data Visualization: C. Rossant, Packt Publishing, 2013.
Programação em Mathematica: A. Sernadas, C. Sernadas e J. Ramos, DMIST, 2003.
19 de 19 02/01/2021, 00:30