PythonBrasil

sexta-feira, junho 27, 2008

Cria Prova - Pequenas modificações e melhorias

Fiz algumas melhorias e modificações no script criaprova.
O programa agora cria provas com número de questões iguais entre os arquivos; isto permite uma prova mais balanceada.
Algumas informações sobre as provas são apresentadas, como o nome dos arquivos, o número de questões em cada arquivo, total de questões.
O programa calcula o número de questões que serão selecionadas de cada arquivo; em caso de divisão não exata, a sobra é distribuída aleatoriamente entre os arquivos - uma questão por arquivo.
Também é informada distribuição de questões por arquivo [num.questões, num.arquivo] e o número real de questões que cada prova tem; pode ser que uma prova fique com número de questões menor do que o solicitado se algum arquivo não contém questões suficientes (maior do que o número de questões por arquivo). Para executar o script usei a seguinte linha de comando:


python criaprova_v3.py 5 22 ../q1-3-20080623.txt ../q4-20080623.txt \
../q5.txt ../q6-20080623.txt

Na linha acima, o número 5 após o nome do script indica o número de provas a serem geradas; o número 22 indica o número de questões por prova e os demais são os nomes dos arquivos com as questões.

Abaixo um exemplo das informações que são mostradas:

arquivos ['../q1-3-20080623.txt', '../q4-20080623.txt', '../q5.txt', \
'../q6-20080623.txt']
arquivo 0: ../q1-3-20080623.txt questoes: 33
arquivo 1: ../q4-20080623.txt questoes: 15
arquivo 2: ../q5.txt questoes: 10
arquivo 3: ../q6-20080623.txt questoes: 14
num arquivos: 4 total: 72

Questoes por arquivo 5
Sobra: 2 >>> Arquivo(s) que ficara(o) com a sobra: [3, 1]
Prova: 0 dist. de questoes [[5, 0], [6, 1], [5, 2], [6, 3]] total 22
Prova: 1 dist. de questoes [[5, 0], [6, 1], [5, 2], [6, 3]] total 22
Prova: 2 dist. de questoes [[5, 0], [6, 1], [5, 2], [6, 3]] total 22
Prova: 3 dist. de questoes [[5, 0], [6, 1], [5, 2], [6, 3]] total 22
Prova: 4 dist. de questoes [[5, 0], [6, 1], [5, 2], [6, 3]] total 22


Este é um exemplo de prova gerada pelo script

quinta-feira, junho 19, 2008

Explorando Sistemas Operacionais com Python - Processos

Um processo é a entidade básica ativa na maioria dos sistemas operacionais modernos.

Quando um usuário e criado no Unix/Linux, ele recebe um ID de usuário e um ID de grupo. O sistema utiliza os IDs de usuário e de grupo para buscar as permissões atribuídas a este usuário. O usuário mais privilegiado é o administrador de sistema, que tem o ID 0.

Um processo tem vários IDs de usuário e grupo que resultam em privilégios ao processo. Estes incluem o ID real de usuário, o ID real de grupo, o ID efetivo de usuário e o ID efetivo de grupo. Usualmente, os IDs real e efetivo são os mesmos, mas sob certas circunstâncias o processo pode mudá-los. O processo utiliza os IDs efetivos para determinar permissões de acesso a arquivos. Por exemplo, um programa que roda com privilégios de root pode querer criar um arquivo em nome de um usuário comum. Definindo o ID efetivo de usuário do processo como o do usuário, o processo pode criar arquivos "como se" o usuário os tivesse criado. Na maior parte, assumimos que os IDs real e efetivo de usuário e grupo são os mesmos.

As seguintes funções retornam os IDs de usuário e grupo para um processo. Os gid_t e uid_t são tipos inteiros representando os IDs de grupo e usuário, respectivamente. As funções getgid e getuid retornam os IDs reais, e as funções getegid e geteuid retornam os IDs efetivos.


import
os
uid_t = os.getuid()
euid_t = os.geteuid()
gid_t = os.getgid()
egid_t = os.getegid()



Um processo pode criar outro processo chamando fork. O processo chamador se torna o pai e o processo criado é chamado filho.

A função fork copia a imagem da memória do pai de modo que o novo processo recebe uma cópia do espaço de endereçamento do pai. Ambos os processos continuam sua execução na instrução seguinte à declaração fork (executando em suas imagens de memória respectivas).


import
os
pid_t = os.fork()


A criação de dois processos completamente idênticos não seria muito útil. O valor de retorno da função fork é a característica crítica que permite que pai e filho distinguam-se para executar código diferente. A função fork retorna 0 para o filho e retorna o ID do processo filho para o pai. Quando fork falha, ela retorna -1 e define errno. Se o sistema não tem os recursos necessários para criar o filho ou se os limites sobre o número de processos forem excedidos, fork define errno como EAGAIN. No caso de uma falha, fork não cria o filho.

No exemplo a seguir, após o fork, pai e filho imprimem seus respectivos ID's de processo.


import os,sys

childpid = os.fork()

if childpid == -1:
print 'erro no fork'
sys.exit()
if childpid == 0:
print 'Eu sou o processo filho %ld' % (os.getpid())
else:
print 'Eu sou o processo pai %ld' % (os.getpid())



O programa a seguir cria uma cadeia de n processos chamando fork em um loop. Em cada iteração do loop, o processo pai tem um childpid não zero e interrompe o loop. O processo filho tem um valor zero de childpid e se torna um pai na próxima iteração do loop. No caso de um erro, fork retorna -1 e o processo chamador sai do loop.


import os,sys

if len(sys.argv) != 2:
print >>sys.stderr,"Uso: %s processos\n" % (sys.argv[0])
sys.exit(1)

n = int(sys.argv[1])
i = 1
while i<n:
childpid = os.fork()
if childpid > 0:
break
i+=1

print >>sys.stderr,"i:%d ID do processo: %ld, ID do pai: %ld, \
ID do filho %ld\n" % (i, os.getpid(), os.getppid(), childpid)
sys.exit(0)


Por padrão, o sistema buferiza a saída escrita em stdout, de modo que uma mensagem particular pode não aparecer imediatamente após o retorno de print. Mensagens para stderr não são buferizadas, mas, ao contrário, são imediatamente escritas. Por esta razão, você deve sempre utilizar stderr para as mensagens de depuração.

O programa a seguir cria um ventilador de processos chamando fork em um loop. Em cada iteração, o processo recém criado sai do loop enquanto o processo original continua. Isto é o inverso do que acontece no programa acima.


import os,sys

if len(sys.argv) != 2:
print >>sys.stderr,"Uso: %s processos\n" % (sys.argv[0])
sys.exit(1)

n = int(sys.argv[1])
i = 1
while i<n:
childpid = os.fork()
if childpid <= 0:
break
i += 1

print >>sys.stderr,'i:%d ID do processo %ld, ID do pai %ld, \
ID do filho %ld\n' % (i, os.getpid(), os.getppid(), childpid)
sys.exit(0)


Estes scripts são baseados no livro Unix Systems Programming, de Robbins e Robbins, da Prentice Hall. Por serem scripts pequenos, os códigos são facilmente digitados. Quando houver algum script longo, o código fonte será disponibilizado também.

Continuarei com mais programas para explorar o Sistema Operacional.

segunda-feira, junho 16, 2008

Cria Prova - Um python script para gerar provas

Este é um programa para a criação automática de provas diferentes, com questões aleatórias, a partir de um banco de questões.

A idéia deste pequeno programa veio de uma conversa com um colega (Prof. Anibal, que já utiliza uma versão DOS para as suas provas).

O programa gera, a partir do banco de questões, tantas provas diferentes e com a quantidade de questões indicadas pelo usuário.

As questões devem estar em um arquivo texto com o seguinte layout:
Q: pergunta
A: resposta 1
A: resposta 2 [opcional]

Uma pergunta pode ter várias respostas (aspectos). Neste caso, a pergunta, para ter sentido, deve ser do tipo: "Dentre as características de XXXX, está" O programa selecionará aleatoriamente, para cada prova, uma das respostas da pergunta; poder-se-á ter então, provas que tenham uma pergunta com o mesmo enunciado mas com resposta diferente. Isto é bom! Dificulta a "cola".

As perguntas podem ser separadas por linhas em branco no arquivo de questões. Mas isto é opcional.

Atualmente, o programa gera um arquivo .tex com a prova (prova_NN.tex) e um arquivo gabarito com as respostas das provas (prova_gb.tex). Futuramente, outros formatos de saída deverão ser adicionados, bem como uma interface gráfica para facilitar o uso.

Cada prova é identificada por um código de barras que contém o gabarito. Veja no código fonte informações importantes sobre os identificadores.

No arquivo gabarito, as respostas são identificadas pelo mesmo código de barras. Veja no código fonte mais informações sobre os identificadores no gabarito.

Após gerados, os arquivos das provas podem ser editados para a inserção de outras questões ou ajuste fino na formatação.

O código está abaixo, e pode ser baixado daqui



#!/usr/bin/env python
# -*- coding: ISO-8859-1 -*-

import random, sys, os

# define a quantidade maxima de questoes/respostas: 702 por enquanto.

letras_1 = ['A','B','C','D','E','F','G','H','I','J', 'K','L', 'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']

letras_2 = [x+y for x in letras_1 for y in letras_1]

letras = letras_1 + letras_2

identificadores = ['.-' , ',+' , ':-' , '=+', ';.', '...', ';;;', '+++', '~~~', '***']

def lequestoes(arquivos):
""" funcao que le os arquivos e cria uma lista com todas as perguntas, que sao dicionarios"""
print 'crialista', arquivos
listao = []
respostas = []
d = dict()

# estados possiveis: fora de alguma questao
# dentro de uma questao - Q - pergunta
# dentro de uma questao - A - respostas
# quando esta fora de uma questao e encontra um Q, deve entrar em modo questao, adicionar a pergunta ao
# dicionario
# quando esta em modo questao e encontra um A, deve entrar em modo respostas, adicionar a resposta ao
# dicionario
# quando esta em modo respostas e encontra um A, adiciona a resposta ao dicionario
# quando esta em modo respostas e encontra um Q, deve entrar em modo questao, adicionar o dicionario
# ao listao de perguntas, zerar o dicionario e as respostas
# quando esta em qualquer modo e encontra uma linha em branco, passa a proxima linha
# quando esta em modo questao e encontra um Q, significa erro no arquivo: retorna ERRO.

for a in arquivos:
arq = open(a)
lista = []
todaslinhas = arq.readlines()
respostas = []
estado = 'OUT'
for linha in todaslinhas:
if linha.startswith('Q:') and estado == 'OUT': # nova questao
d = dict()
estado = 'Q' # entra em modo questao
linha = linha.lstrip(' ')
d["q"] = linha[3:]
d["t"] = linha[0]
elif linha.startswith('Q') and estado == 'Q': # outra linha com Q -> erro
print "Arquivo %s de questoes com erro linha %d " % (a, todaslinhas.index(linha))
sys.exit(-1)
if linha.startswith('A') and (estado == 'Q' or estado == 'A'):
estado = 'A' # entra em modo resposta
linha = linha.lstrip(' ')
if len(linha) > 4:
respostas.append(linha[3:])
d["a"] = respostas
else:
print "linha len < 4"
if linha.startswith('Q') and estado == 'A': # nova questao
listao.append(d)
respostas = []
d = dict()
estado = 'Q'
linha = linha.lstrip(' ')
d["q"] = linha[3:]
d["t"] = linha[0]
if linha.startswith('\n'): # linha em branco separando questao
if estado == 'A' and len(d):
listao.append(d)
respostas = []
d = dict()
estado = 'OUT'
pass
# fim do loop
if estado == 'A' and len(d): # terminaram as linhas e o dicionario contem dados
listao.append(d)
estado = 'OUT'
d = {}
respostas = []
arq.close()

return listao

def criasequencia(listao):
""" cria uma sequencia (lista) de tuplas (q,a) a partir do listao geral de questoes """
sequencia = [] # todas as possibilidades de perguntas (tuplas do tipo (Q,A))
for q in listao:
perg = q['q']
resp = random.choice(q['a'])
sequencia.append((perg,resp))
if q['t'] != 'QNI':
pass
return sequencia

def criaprovas(sequencia, numprovas,questaoporprova):
""" cada prova eh gerada aleatoriamente a partir da lista de tuplas com todas as questoes.
recebe como argumentos: uma lista de tuplas (q,a), o num de provas a serem geradas e
quantas questoes cada prova deve ter.
retorna as provas geradas e os gabaritos; as provas sao listas de tuplas (q,a) e os gabaritos sao listas de tuplas (num questao, letra resposta)
"""
provas = []
gabaritos = []
for np in range(numprovas):
prova = random.sample(sequencia,questaoporprova)
provas.append(prova)
perguntas = []
respostas = []
embaralha = range(len(prova))
random.shuffle(embaralha)
gabarito = []
gaba_str = ''
for i in range(len(prova)):
gabarito.append((i+1,letras[embaralha[i]]))
gabaritos.append(gabarito)
return provas, gabaritos


def crialatex(sequencia, numprovas,questaoporprova):
""" cria um arquivo .tex para cada prova e tb o arquivo gabarito para as provas
recebe como argumentos: uma lista de tuplas (q,a), o num de provas a serem geradas e
quantas questoes cada prova tera.
cada prova eh gerada aleatoriamente a partir da lista de tuplas com todas as questoes.
retorna as provas geradas.
"""
preambulo1 = """
\documentclass[10pt,brazil,a4paper]{article}
\usepackage[latin1]{inputenc}
\usepackage[portuguese]{babel}
%\usepackage{graphicx}
%\usepackage{multicol}
%\usepackage{shadow}
%\usepackage{pifont}
%\usepackage{listings}
%\usepackage{fancyvrb}

%\usepackage{boxedminipage}
%\usepackage{theorem}
\usepackage{verbatim}
\usepackage{tabularx}
%\usepackage{moreverb}
\usepackage{times}
%\usepackage{relsize}
\usepackage{pst-barcode}

\setlength{\\textwidth}{180mm}
\setlength{\\oddsidemargin}{-0.5in}
\setlength{\\evensidemargin}{0in}
\setlength{\\columnsep}{8mm}
\setlength{\\topmargin}{-28mm}
\setlength{\\textheight}{265mm}
\setlength{\\itemsep}{0in}
\\begin{document}
\\pagestyle{empty}
%\lstset{language=python}

"""
preambulo2= """

\\textbf{Curso:}\\rule{11cm}{0.1pt}\hspace{0.5cm}\\textbf{Turma:}\\rule{3cm}{0.1pt}

\\textbf{Nome:}\\rule{11cm}{0.1pt} \hspace{0.5cm}\\textbf{nota:} \\rule{3cm}{0.1pt}

{\scriptsize

\\textbf{Instruções:}
\\begin{verbatim}
1. Proibida a consulta de livros ou anotações 2. Permitido o uso de calculadoras eletrônicas
3. Somente serão consideradas as respostas da Parte 1 na região "Gabarito" desta página
\end{verbatim}
}


\\begin{tabularx}{\linewidth}{X|X}
\\textbf{Parte 1 -- Questões} """

preambulo3 = """

& \\textbf{Respostas} \\\\ \hline \hline
%& \\\\

"""

finaltabela = """
\end{tabularx}

%\\vspace{0.3cm}

\\textbf{Parte 1 -- Gabarito:}
"""
fimdocumento = """
\end{document}
"""

gabatabela = """

\\begin{tabular}[t]{|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|b{10mm}|} \\\\ \hline
"""

titulo = "\\textbf{\\centering \\large %s}" % tituloprova
provas = []
arqgaba = open('prova_'+'gb.tex','w')
arqgaba.write(preambulo1.decode('utf-8').encode("latin1"))
titulogaba = "%s \\textbf{\\large GABARITO}" % titulo
arqgaba.write(titulogaba.decode('utf-8').encode('latin1'))

for np in range(numprovas):
prova = random.sample(sequencia,questaoporprova)
arqprova = open('prova_'+str(np)+'.tex','w')
arqprova.write(preambulo1.decode('utf-8').encode("latin1"))
arqprova.write(titulo.decode('utf-8').encode("latin1"))
arqprova.write(preambulo2.decode('utf-8').encode("latin1"))
arqprova.write("%s" % (identificadores[np]))
arqprova.write(preambulo3.decode('utf-8').encode("latin1"))
#arqprova.write(initabela)


provas.append(prova)
perguntas = []
respostas = []
embaralha = range(len(prova))
random.shuffle(embaralha)
gabarito = []
gaba_str = ''
for i in range(len(prova)):
arqprova.write("%d) %s & \n".encode('latin1') % (i+1, prova[embaralha[i]][0]))
gabarito.append((i+1,letras[embaralha[i]]))
gaba_str += letras[embaralha[i]]
arqprova.write("%s) %s \\\\ \n".encode("latin1") % (letras[i],prova[i][1]))

arqprova.write(finaltabela)
#arqprova.write("%s" % (identificadores[np]))
arqprova.write("\\hspace{1cm}\\begin{pspicture}(1,1in)\n")
# a linha abaixo gera o codigo de barras do identificador (duplicado) da prova,
# inclui o texto no codigo de barras
#arqprova.write("\\psbarcode[scalex=0.9,scaley=0.3]{%s%s}{includetext}{code39}\n" % (np,np))
# gera codigo de barras usando a string do gabarito, nao inclui o texto no codigo de barras.
arqprova.write("\\psbarcode[scalex=0.9,scaley=0.3]{%s}{}{code39}\n" % (gaba_str))
arqprova.write("\\end{pspicture}\n")
arqprova.write(gabatabela.encode('latin1'))
for l in range(len(prova)):
arqprova.write("{\\raggedright \\raisebox{0.8ex}[2.5ex][0.75ex]{%d}} ".encode("latin1") % (l+1))
if l > 0 and (l+1) % 10 == 0:
arqprova.write("\\\\ \hline \n")
else:
arqprova.write("&")
arqprova.write("\\end{tabular}")

arqprova.write(fimdocumento)

arqgaba.write("\n\n\n PROVA")
arqgaba.write("%s" % (identificadores[np]))
arqgaba.write("\\hspace{1cm}\\begin{pspicture}(1,1in)\n")
# a linha abaixo gera o codigo de barras do identificador (duplicado) da prova,
# inclui o texto no codigo de barras
#arqprova.write("\\psbarcode[scalex=0.9,scaley=0.3]{%s%s}{includetext}{code39}\n" % (np,np))
# gera codigo de barras usando a string do gabarito, nao inclui o texto no codigo de barras.
arqgaba.write("\\psbarcode[scalex=0.9,scaley=0.3]{%s}{}{code39}\n" % (gaba_str))
arqgaba.write("\\end{pspicture}\n")
arqgaba.write(gabatabela.encode('latin1'))
for l in range(len(prova)):
arqgaba.write("{\\raggedright \\raisebox{0.8ex}[2.5ex][0.75ex]{%d}} %s ".encode("latin1") % (gabarito[l]))
if l > 0 and (l+1) % 10 == 0:
arqgaba.write("\\\\ \hline \n")
else:
arqgaba.write("&")
arqgaba.write("\\end{tabular}")

arqprova.close()

arqgaba.write(fimdocumento)
arqgaba.close()
return provas


tituloprova = "Avaliação Bimestral"

def main():
if len(sys.argv) > 1:
arquivos = sys.argv[3:]
numprovas = int(sys.argv[1])
questaoporprova = int(sys.argv[2])
print arquivos

else:
numprovas = int(raw_input("entre o numero de provas: "))
questaoporprova = int(raw_input("entre o numero de questoes por prova: "))
entrada = raw_input("entre os nomes dos arquivos, separados por espaco: ")
entrada.rstrip('\n')
arquivos = entrada.split(' ')
global tituloprova
tituloprova= raw_input("Digite o titulo da prova: ")
lista = lequestoes(arquivos)
sequencia = criasequencia(lista)
provas = crialatex(sequencia, numprovas, questaoporprova)

if __name__ == '__main__':
main()

terça-feira, junho 10, 2008

Ensinando Sistemas Operacionais com Python

Python é uma excelente linguagem de programação: alto nível, de fácil aprendizagem, excelente produtividade, orientada a objeto, tipagem dinâmica, etc.

Normalmente, ela é utilizada como primeira linguagem de programação por causa destes atributos. Entretanto, ela também pode ser utilizada para coisas, digamos, mais baixo nível, como por exemplo:

  • Ensino de Sistemas Operacionais - exploração de chamadas de sistema para criação de processos, navegação em sistemas de arquivos, acessos a primitivas de Entrada e Saída do sistema operacional, e outras coisas a nível de SO.
  • Ensino de Programação de Rede - exploração de sockets, implementação de servidores variados,
  • Ensino de Sistemas Distribuídos, como continuação a Programação de Rede, explorando paradigmas de comunicação entre processos, sincronismo, etc.
  • Ensino de Inteligência Artificial - exploração de conceitos de IA, desde buscas até redes neurais, passando por processamento de linguagem natural, etc.
Enfim, Python é uma linguagem excelente para quem quer aprender bem programação e muito mais, para quem quer ensinar bem os conceitos de ciência da computação/sistemas de informação, sem se perder em merchandising e modismos mercadológicos.