2018. február 3., szombat

Pyton programozás óra Emeltszintű Érettségire 12. osztály

A lambdafüggvény nem más, mint egy névtelen, egysoros, „egyszer használatos” függvény. Használata minden esetben elkerülhető bővebb kifejtéssel.Szintaxisa: lambda <argumentumok>: kifejezés

 A gyorsabb fejlesztés érdekében készített “onfly” eljárások, amelyeknél 1-2 sorban kifejthető a kód (eljárás törzs és név írása nélkül).
   g = lambda x: x**2
   g(8)
A printf függvény
formázott adatkivitelt valósít meg. H
ívásának formája:
printf(formátum
string
, paraméterlista)
A formátum
string
, amely rendszerint stringkonstans, kétféle karaktert tartalmazhat:

Az első csoport karakterei normál karaktersorozatot alkotnak a formátumstringen belül, ezek
minden átalakítás nélkül kerülnek kiírásra.

A másik csoport karakterei speciális konverziót írnak elő, amelyek meghatározzák a
paraméterlistában szereplő paraméterek értelmezési módját és a megjelenés formátumát. Ezek
a konverziós előírások százalékjellel kezdődnek és valamilyen konverziós karakterrel zárulnak

 Változó definiálás, alaptípusok
   anint1=int(128)
   anint2=132
   # két egész szám

   print anint2/anint1
   # becsapós - egész osztás!

   print float(aint2)/anint1
   # típusváltás - lebegőpontos osztás

   text='Egy szöveges változó'
   text2=text
   # két szövegváltozó

   text[1]='a'
   # hiba, a stringek nem módosíthatóak!

   text=text.replace('k','a')
   # működik, lecseréli


   text=""
   for i in range(10):
       text=text+chr(65+i)
   # ez nem jó - bár működik

   l=[] # ld. később
   for i in range(10):
       l.append(chr(65+i))
   text="".join(l)
   # gyors string összefűzés, mivel a folyamatos hozzáadás lassú
   # ld. Java StringBuffer

   fmts='%d. elem, név: "%s"'
   text=fmts%(1,'János')
   # string formázás (összefűzés helyett is használatos)

   text="alma mater"
   # egyszerű string

   ubtext=u"alma mater"
   # unicode string

   utext=unicode(text,'iso-8859-2')
   # unicode string konvertálással

   print type(text)==type(ubtext)
   # 0 vagy False - egyik string, másik unicode string

   print type(utext)==type(ubtext)
   # 1 vagy True - mindkettő unikódos

   # mágikus unikóddá alakító eljárás (ha magyar gépen fájlokkal dolgoznánk)
   _deflang='iso-8859-2'
   def UnicodeIt(text):
       if not (type(text) in [type(''),type(u'')]):
          text=str(text)
       if type(text)<>type(u''):
          text=unicode(text,_deflang)
       return text
   # eljárás az azonos típusra hozáshoz
 
   s1='a';s2=u'á'
   print s1+s2
   # exception!
   s3=UnicodeIt(s1)+UnicodeIt(s2)
   print s3

   # a stringekben használhatunk vezérlő karaktereket
   s="\n\t"
   # újsor + tab

   print "%s\n%s"%(1,2)
   # ekvivalens ezzel

   print 1
   print 2
   print chr(65)+chr(32)+chr(66)
   # karakterkódok értékből

   # a stringek másolása, részelemek (ld. később a listáknál is):
   s="abcdef"
   print s[1:2] # "bc"
   s2=s[:]      # ez olyan, mint az s2=s
   s3=s[-1]     # az utolsó karakter ("f")

   ma=[]
   # lista definiálás, ez most egy üres lista

   ma.append(1)
   # a listához adtunk egy értéket

   ma.append('sss')
   # a listához adtunk egy értéket

   ma.append(text)
   # a listához adtunk egy értéket - változóból

   print len(ma)
   # lista hossz

   print ma
   # a lista

   print ma[1:2]
   # lista részlete (másolata!)

   ma1=ma
   # ma REFERENCIA(!) átadása

   ma1.append(1)
   # ekkor ma-ba is kerül egy 1-es

   ma2=ma[:]
   # ma teljes átmásolása, egy új lista, de azonos elemekkel

   ma2.append(2)
   print "%s\n%s"%(ma,ma2)
   # ekkor ma és ma2 már más értékeket ad vissza

   ma.remove(1)
   # 1 (mint egész érték) első előfordulásának eltávolítása

   di={}
   # új szótár - üres

   di2={1:'a',2:'b','s':'sss'}
   # új szótár, előre definiált értékekkel

   di[1]='c'
   # elem értékadás (vagy új elem hozzáadása a szótárhoz). A kulcs 1, az érték "c".

   print di[1]
   # elem elérése, érték kinyerése

   print di
   # a szótár a maga teljességében

   di.update(di2)
   # felülírás a másikkal

   print di
   # a szótár

   print di[1]
   # elem olvasása

   print di['s']
   # elem olvasása

   print di.keys()
   # kulcsok
   # fontos: a sorrendjük a hashelés miatt változhat!

   print di.has_key(1)
   print di.has_key(555)
   # van ilyen kulcs ?

   d1={'a':1,'c':1}
   d2={'a':2,'b':3}
   d1.update(d2)
   # felülírás, és új elemek áthozatala
   # {'a':2,'b':3,'c':1}

   d3=d1.copy()
   # teljes másolás

   tu=()
   # egy üres tuple (vektor, "konstans")
   # ezzel nem tudunk mit kezdeni

   tu=(1,2,3)
   print tu[0]
   # elem olvasás

   print tu[1:]
   # több elem 1. indextől felfelé

   tu.remove(1)
   # ez hibát dob, mert nincs neki ilyen metódusa

   #
   # Speciális (imádnivaló!!!) nyelvi jellemzők:
   #

   z=y=x=1
   # értékadás egy lépésben

   x,y,z=(1,2,3)
   # értékadás egy lépésben, tuple-ből

   alist=[1,2,3]
   x,y,z=alist
   # értékadás egy lépésben, list-ből

   a=1;b=2
   a,b=(b,a)
   # felcserélés egy lépésben

   a=[0]*10
   # 10 elemű tömb, csupa nullával

   b=[[],[]]
   # 2 elemű tömb, tömb elemekkel

   b=[[]]*2
   print b
   b[1].append(2)
   print b
   # 2 elemű tömb, azonos elem referenciával

Összehasonlítás, elágazás
   x=1
   if x==1:
      print "x=1'
   x=2
   if x<>1 or x!=1:
   # az <> jel ekvivalens a!= jellel
   # tehát fenti kifejezésből az egyik redundáns
      print "x<>1"
   if x<2:
      print "<2"

 s1="a">1 or b==2 or c==3:
      pass
   # Nem kell zárójelezni az alelemeket (gyorsabban gépelhető)

   if a&&1 and (a%1)<>0:
      print "Páratlan"
   # Az bitszintű "and" és a logikai "and"

Eljárás definiálás, hívás
   def Proc1():
       return "valami"
   # eljárás bemenő érték nélkül

   print Proc1
   # hiba - ez magát az eljárást adja!
 
   print Proc1()
   # ez adja az eljárás eredményét

   def Proc2(x,y):
       return x*y

   print Proc2(1,2)
   # több paraméter

   def Proc3(x,y):
       return (x*x,y*y,x*y)

   print Proc3(1,2)
   # több paraméter visszaadása

   def Proc4(list):
       list.append('aresult')

   alist=['a','b']
   print Proc4(alist)
   # ez nem ad vissza semmit
 
   print alist
   # de a listában megtaláljuk, mert a lista objektum (referencia)
   # így több elemet is vissza tud adni

   proclist=[Proc3,Proc2]
   for proc in proclist:
       print proc(1,2)
   # összegyűjtés, végrehajtás, procedure típus

   def Proc5(list,value=None,item=None):
       if list.has_key(item): return None
       if value<>None: list.append(value)
       return item

   alist=[]
   Proc5(alist,None,1)
   Proc5(alist,item=128)
   Proc5(alist,value=1,item=119)
   # több paraméter, default értékek, nevesített paraméterátadás

Egyéb elemek, elágazás, ciklus
   # listák generálása
   print range(3)
   # 0,1,2

   print range(1,3)
   # 1,2

   print range(1,6,2)
   # 1,3,5


   # ciklus
   # range-dzsel generált tartományból
   for i in range(3):
       print i
   # 0,1,2 => i

   for i in range(1,6,2):
       print i
   # 1,3,5 => i
 
   # saját listából
   alist=[1,2,6]
   for i in alist:
       print i
   # 1,2,6 => i
 
   # stringből
   alist='abc'
   for i in alist:
       print i
   # a,b,c => i
 
   # nagy memóriafoglalással
   for i in range(1000000):
       print i
 
   # nagy memóriafoglalás elkerülésével (xrange)
   for i in xrange(1000000):
       print i
 
   # ciklus vezérlése
   for i in range(10):
       if i==2: continue
       if i==8: break
       print i
   # 0,1,3,4,5,6,7
 
Az objektumok az OOP szabályai alapján zárt egységek, amik különféle interface-eken (metódus/tulajdonság) keresztül kommunikálnak egymással, és a más programrészekkel. Megőrzik belső állapotukat.

Pythonban az objektumok a PHP-től eltérően kétféle tagot tudnak megkülönböztetni: privátot, és publikusat. Bizonyos trükkökkel a privát tagok is elérhetővé válhatnak, erre ügyelni kell, de alapvetően, "hackelés" nélkül nem lehet belenyúlni egy objektum privát adataiba.

Nézzünk egy mintát:
   class A:
         def __init__(self,param1,param2):
             self.Params={1:param1,2:param2}
         def getParams(self):
             return self.Params
   a=A(1,2)
   # osztály létrehozása
   print a.Params
   print a.getParams()

Ez egy alapvető osztálydefiníció. Az __*__ tagok mindig privát tagfüggvények. Nem használhatóak másra, mint felülírásra (új funkciók). Jelen esetben az __init__ pl. maga a constructor, itt két paramétert várunk, amit eltárolunk. Mivel alapobjektumot hívtunk, nincs szükség rá, hogy meghívjuk az őst (másutt inherited, vagy super).

Fontos: a Python jelenleg átalakulás közben van, ahol bizonyos osztály jellemzők, és tulajdonságok megváltoznak. Lehetséges, hogy később nem fog működni egy-egy példa, illetve nem lesz helytálló, ami itt szerepel.

Mint láthatjuk, a Python alapértelmezetten kiajánlja a Params osztályváltozót (member), ami elég furcsa dolog a Java és a Delphi OOP viselkedéséhez képest. Ez azért van, mert a Python az objektumok belső szótárához (amely a membereket is tárolja) alapértelmezetten hozzáférést enged. Hasznos, ha az ember rekordként (struct, record) tárolni akar valamit egy objektumban, de annak forráskódjával nem rendelkezik. Ld. pl. Delphiben a Tag változó, amit direkt ezért hagytak életben. Ugyanakkor ez OOP szempontból kellemetlen, hiszen írhatóvá válnak belső tartalmak.

A Pythonban a __slots__ szótárban felsorolhatjuk az elemeket, amiket láttatni kívánunk. Ekkor minden más elérhetetlen lesz kívülről.

A másik megoldás a property-k használata. (Ezek setter/getter rutinok) Így privátként lehet definiálni az adott membert.
   class A:
         def __init__(self,param1,param2):
             # a _ előtag priváttá teszi az elemet
             self._Params={1:param1,2:param2}
         def getParams(self):
             return self._Params
         Params=property(getParams);

   a=A(1,2)
   # osztály létrehozása
   print a.getParams()
   print a.Params
Ez már megfelel egy rendes, tisztességes objektum alapvető követelményeinek.

Az öröklés, de főleg a többszörös öröklés (multiple inheritance) kényes téma a Python-ban, annál is inkább, mivel forradalmi változások zajlanak az objektumok táján, és ezek a kódokra óriási hatással lesznek. Mindenesetre annyit el lehet mondani, hogy a Python a többszörös öröklődés útját követi (nem pedig a Delphi-ben és Java-ban használatos interface-re épülő megoldásokat).

A következő példa egy egyszeres öröklést mutat be:
   class A:
         def __init__(self,param1,param2):
             self._Params={1:param1,2:param2}
         def getParams(self):
             return self._Params
         Params=property(getParams);

   class B(A):
         def __init__(self,param1,param2,param3):
             A.__init__(self,param1,param2) # az ős meghívása - nagyon fontos dolog (ld. super/inherited)
             self.Params[3]=param3


   b=B(1,2,3)
   # osztály létrehozása
   print b.getParams()
   print b.Params

   # megjegyzés:
   # pontosan a változások miatt érdemesebb minden objektumot az alapobjektumból
   # származtatni. Máskülönben érdekes hibákat kaphatunk, mivel a property-kezelés
   # alacsonyabb rendű, mint az osztályváltozók elérése.
   # Vagyis egy property írás helyett (Set* method) könnyen egy osztályváltozóba
   # írhatunk!

   class C:
         def __init__(self,param1,param2):
             self._Params={1:param1,2:param2}
         def getParams(self):
             return self._Params
         def setParams(self,value):
             self._Params=value
         Params=property(getParams,setParams);
        
   c=C(1,2)
   d={'a':1,'b':2}
   c.Params=d # hiba, mi egy Params változóba írtunk

   # A megoldás:
   class C(object):
         ...

A Pythonban az osztályok temérdek beépített metódust tartalmaznak, amelyekkel szabályozni lehet a viselkedésüket. Ezekkel meg lehet mondani, hogy mely műveletet hogyan értelmezzünk egy osztályon. Pl. a DateTime osztályban felülírásra került a komparáció, és kivonás/összeadás, így ha ezekben a műveletekben szerepel az objektum, akkor nem kell külön eljárásokat írni/hívni.
   dt=DateTime('2001.01.12')
   dt=dt+1 # 1 nappal előre
   print dt

Íme néhány felülírható metódus:
__repr__     Az objektum általános leírása (állapota, stb.).
__cmp__     Összevetés egy értékkel (kisebb/nagyobb/egyenlő).
__len__     Hossz (pl. elemszám).
__add__     Érték hozzáadás
__getitem__     Al-elem értéke (index).

Természetesen sok más metódus is létezik (fenti lista csak ízelítő), de az alábbi példa megértéséhez szükséges.
   class A:
         def __init__(self,param1,param2):
             self._Params={1:param1,2:param2}
         def __repr__(self):
             return "A object: "+str(self._Params)
         def __add__(self,op1):
             p=self._Params
             if type(op1)==type(''):
                return (op1+str(p[1])+str(p[2]))
             else:
                return (op1+p[1]+p[2])
         def __getitem__(self,idx):
             return self._Params[idx]



   a=A(1,2)
   # osztály létrehozása
   print a
   print a+1
   print a[1]

A Python objektumok rendelkeznek még egy érdekes lehetőséggel, egyfajta áldestruktor definiálásával. Ezek a metódusok tartalmazhatnak olyan elemeket, amelyek segítik a szemétgyűjtő munkáját.

Megjegyzés: Mint tudjuk, a szemétgyűjtés a változók és objektumok automatikus memóriakezelését célozza meg. Ezzel gond lehet, hogyha a változók/objektumok keresztreferenciákat tárolnak, mert így a szemetet nem lehet eltüntetni, ami memóriahiányt okoz. A megoldás, hogyha egyrészt minden olyan helyen, ahol nem tárolunk elsődleges elemeket, gyenge referenciákat tartunk (weakref.ref()), másrészt a objektum "halálakor" segíthetjük a garbage collectort bizonyos erőforrások felszabadításával.
   class A:
         def __init__(self,param1,param2):
             # Egy nagy adattömb foglalása
             self._Data=[0]*1000000
             self._ID=param1
             self._Refs=None

         def CloseResources(self):
             # mindent megszüntetünk
             # memória felszabadítás, ez a kevésbé fontos, mert ez a gc megteszi, ha meghal az objektum
             self._Data=None
             # külső referenciák törlése - ez a lényeges pont
             # más esetben keresztbehivatkozás miatt a gc nem tudja kidobni az objektumot
             # és memóriaszivárgást okoz!
             self._Refs=None

         def SetReferences(self,Refs):
             # Külső refrenciákat tárolunk
             self._Refs=Refs

         def __del__(self):
             print "Now release data of "+str(self._ID)
             self.CloseResources()

   for idx in range(10):
       a=A(idx,idx+1)

   import gc
   gc.collect()

Erre építeni programot azonban nem szabad, mivel nem garantált a lefutása sem (lehet, hogy a program befejeződik még a memória teljes felszabadítása előtt)! Az efféle "destruktorok" végrehajtása kétséges, amellett az lefutási idejüket sem tudjuk kiszámítani. Lásd Java GC működése + finalize metódus.

Ezzel áttekintettük a Python objektumorientált programozás legeslegalapjait.
Példaprogramok
1.) Összevetés kód strukturáltság, és tömörség tekintetében:
Python
    Delphi
def MyFunction(value):
    return value*2+1

ma=myarray=[0]*10
for idx in range(len(ma)):
    ma[idx]=idx

na=newarray
for value in ma:
    na.append(MyFunction(value))
print myarray
print newarray
   
function MyFunction(value:double):double;
begin
 Result:=value*2+1;
end;

var
 ma, myarray,na,newarray:array of double;
 idx:integer;
begin
 SetLength(myarray,10);
 ma=myarray;
 SetLength(newarray,10);
 na=newarray;
 for idx:=0 to High(ma) do begin
  ma[idx]:=idx;
 end;
 for idx:=0 to High(ma) do begin
  na[idx]:=MyFunction(ma[idx]);
 end;
 for idx:=0 to High(ma) do write(',',ma[idx])
 writeln();
 for idx:=0 to High(na) do write(',',na[idx])
 writeln();
end;

2.) Helyfoglalás számítás egy szerver egy mappájában:
   import os,sys

   print "NetDirLen"
   l=len(sys.argv)
   if (l<2):
    print 'Usage:\n    Netdirlen.py [starting dir]\n\n Starting dir can "."'
    sys.exit()
   s=str(sys.argv[1])
   if (s=='..'):
    print 'Error: starting dir cannot be ".."'
    sys.exit()
   if (s=='.'):
    s=os.getcwd()
  
   sdir=s
   print "Working dir:",sdir
   print "Dir","Size"
  
   fstat=open("netdirlen.stat.txt","w")
   fstat.write("Dir"+chr(9)+"Size\n")
   def processdir(basedir,Root):
       Sum=0
       os.chdir(basedir)
       files=os.listdir(basedir)
       files.sort()
       for s in files:
           sfile=basedir+"/"+s
           try:
               stt=os.stat(sfile)
               size=stt[6]
           except:
               size=-1
           isdir=os.path.isdir(sfile)
           if Root==0:
              if isdir:
                 print "- subdir:",s
                 Sum=processdir(sfile,1)
                 stat=s+chr(9)+str(Sum)
                 print "  size  :",Sum
                 fstat.write(stat+"\n")
           elif Root==1:
              if isdir:
                 Sum=Sum+processdir(sfile,1)
              else:
                 if size<>-1: Sum=Sum+size
       return Sum


   processdir(sdir,0)
   fstat.close()
3.) Grafikus felület wxPython-nal:
   import wx

   class MyApp(wx.App):
       def OnInit(self):
           frame = wx.Frame(None, -1, "Test Splitter")
           splitter = wx.SplitterWindow(frame, -1)
           p1 = wx.Panel(splitter, -1)
           p1.SetBackgroundColour('red')
           p2 = wx.Panel(splitter, -1)
           p2.SetBackgroundColour('green')
           splitter.SplitHorizontally(p1, p2)

           sizer = wx.BoxSizer(wx.VERTICAL)
           sizer.Add(splitter, 1, wx.EXPAND)
           frame.SetSizer(sizer)

           frame.Show()
           return True

   MyApp(0).MainLoop()
4.) Külső FTP-vel letöltésre példa:
   #!/usr/bin/env python
   import sys,os,time,ftplib
   from MyLogger import AddLog
   import traceback
   import commands, string

   def IncSlash(adir):
       if adir<>'':
          if adir[-1]<>'/': adir=adir+'/'
       return adir

   def GetTraceBack():
       typ,val,tra=sys.exc_info()
       excmsg="\n".join(traceback.format_exception(typ,val,tra))
       return excmsg

   def GetActTime():
       return time.strftime("%Y.%m.%d. %H:%M:%S:",time.localtime())


   def StartIt(prog,params):
       AddLog(GetActTime()) # az AddLog fgv. a MyLogger modulban van kifejtve
       AddLog('Start program:')
       AddLog(prog+' '+params+'\n')
       return (commands.getstatusoutput(prog+' '+params))

   def GetThisFromFTP(subdir,subfile='*.*'):
       fdir=FTPAddr+'/'+FTPBaseDir+'/'+subdir+'/'+subfile
       r=StartIt('wget','-x --passive-ftp -P '+BaseDir+' '+fdir)
       return r

   BaseDir='/home/suse'
   FTPAddr='ftp.suse.com'
   FTPBaseDir='pub/suse'
   try:
      if GetThisFromFTP('9.0','test1.rpm')==0:
         print "Succeeds."
      else:
         print "An error occured."
   except:
      print GetTraceBack()
5.) Darabolás + Exception Handling (erőforrás lezárás):
   #!/usr/bin/env python
   import sys
   fn=sys.argv[1]
   fs=open(fn,'rb')
   try:
      cnt=0
      while 1:
            data=fs.read(1000000)
            if len(data)>0:
               fd=open(fn+'.split'+str(cnt),'wb')
               try:
                  fd.write(data)
               finally:
                  fd.close()
               cnt+=1
   finally:
      fs.close()

Nincsenek megjegyzések:

Megjegyzés küldése