Seite 1 von 2

Wenn man mal einen zufälligen String braucht...

Verfasst: Mittwoch 21. Oktober 2009, 14:30
von snafu
...dann nutzt man für temporäre Dateien natürlich [mod]tempfile[/mod], aber manchmal brauch man ja auch nen Zufallsnamen für was anderes.

Der folgende hochkomplexe Codeschnipsel kann das:

Code: Alles auswählen

import random
import string

CHARS = string.ascii_letters + string.digits

def get_random_name(length=10, chars=CHARS):
    sample = random.sample(chars, length)
    return ''.join(sample)
Ich habe mal bewusst keine exotischen Zeichen reingenommen.

Verfasst: Mittwoch 21. Oktober 2009, 18:14
von stuhlbein
wäre "get_random_string()" ein nicht viel passender name für die funktion?
Ich finde nämlich, dass "k4rumaVT5M" (o.ä.) ein relativ exotischer name ist. ;)

Problem (vermutlich eh nur ein eventuelles) ist auch, dass der string nie länger als 62 zeichen sein kann, da die variable CHARS nur 62 character hat (stichwort: ValueError: sample larger than population) - sowas könnte man so beheben (nicht sehr hübsch, funktioniert aber):

Code: Alles auswählen

import os
import re
def get_random_string(length=10, ofsize=2048):
    return re.sub(r'[\s\W]', '', os.urandom(ofsize))[0:length]

Verfasst: Mittwoch 21. Oktober 2009, 18:30
von derdon

Code: Alles auswählen

import random
import string

def get_random_string(length, chars):
    return ''.join(random.choice(chars) for char in xrange(length))

if __name__ == '__main__':
    chars = string.ascii_letters + string.digits
    print get_random_string(70, chars)

Verfasst: Mittwoch 21. Oktober 2009, 19:43
von jbs
Da man ja manchmal bestimme Strings haben möchte, dachte ich mir dass es ganz nett wäre, zu bestimmen, dass man bestimmte Positionen im String bestimmen kann. (Zum Beispiel, dass Namen mit einem Buchstaben beginngen müssen).

Ich hab mal was zusammen gehackt, in welche Richtung das gehen könnte:

Code: Alles auswählen

import random

from string import (ascii_lowercase as LOWERCASE, ascii_uppercase as UPPERCASE,
    ascii_letters as LETTERS, digits as DIGITS)

ALNUM = LETTERS+DIGITS

class RandomString(object):
    def __init__(self, length=10, chars=ALNUM):
        self.length = length
        self.chars = chars
        self.rules = {}
    
    def __setslice__(self, start,stop,value):
        for i in xrange(start,stop):
            self.rules[i] = value
    
    def __setitem__(self, key, value):
        self.rules[key] = value
    
    def __call__(self):
        if type(self.length) == int:
            length = self.length
        else:
            length = random.randint(*self.length)

        chars = []
        for i in xrange(length):
            rev = -length+i
            if rev in self.rules:
                s = self.rules[rev]
            elif i in self.rules:
                s = self.rules[i]
            else:
                s = self.chars
            chars.append(random.choice(s))
        return ''.join(chars)

def get_random_name():
    r = RandomString( (6,10), ALNUM)
    r[0] = LETTERS
    return r()

get_random_digits = RandomString(8, DIGITS)
    
if __name__ == '__main__':
    print get_random_name()
    print get_random_digits()

Verfasst: Mittwoch 21. Oktober 2009, 20:16
von derdon

Code: Alles auswählen

>>> class Foo(object):
...     def __setitem__(self, key, value):
...         print type(key)
... 
>>> foo = Foo()
>>> foo[3:8:-2] = 'oink'
<type 'slice'>
>>> foo[42] = 'sprotz'
<type 'int'>
object.__setslice__ ist imho deprecated.

Edit: Ich hatte es richtig in Erinnerung: http://docs.python.org/reference/datamo ... ence-types

Verfasst: Mittwoch 21. Oktober 2009, 20:30
von jbs
danke, gut zu wissen :)

funktioniert also einfach so :)

Verfasst: Donnerstag 22. Oktober 2009, 03:02
von str1442
Statt type(<Integer>) == int besser isinstance() verwenden. Und wenn in der vorherigen Form dann zumindest mit "is" prüfen, denn die Klasse gibts nur einmal.

Ich hatte sowas auch mal geschrieben, meine Version generiert (mithilfe einer bestimmten Relation) Namen, die man auch aussprechen kann.

http://paste.pocoo.org/show/146325/

Verfasst: Donnerstag 22. Oktober 2009, 07:34
von jbs
Ich denke am besten löst man es mit

Code: Alles auswählen

try:
    length=int(self.length)
except ValueError:
    length = random.randint(*self.length)

Verfasst: Donnerstag 22. Oktober 2009, 09:58
von snafu
@stuhlbein: Ich finde ja, `ofsize` ist mehr ein Implementierungsdetail, daher vielleicht besser etwas in dieser Art:

Code: Alles auswählen

def get_random_string(length=10):
    s = ''
    while len(s) < length:
        s += re.sub(r'[\s\W]', '', os.urandom(length))
    return s[:length]

Verfasst: Donnerstag 22. Oktober 2009, 15:35
von derdon
str1442: Bei Zeile 60 springt mir str.title ins Gesicht. Statt der Konstanten in den Zeilen 26/27 würde ich diese als Parameter der Funktion verwenden.

Verfasst: Freitag 23. Oktober 2009, 08:28
von stuhlbein
@snafu: ziemlich geschickt ^^ so ist's natürlich besser ;)

Verfasst: Freitag 23. Oktober 2009, 08:47
von snafu
Wobei man das bei Bedarf noch etwas optimieren könnte. Bei jeder Anfrage sind anscheinend 20-25% verwertbare Zeichen dabei. Wenn man direkt das Zehnfache anfragt, hätte man es wahrscheinlich in einem Rutsch, allerdings auch mehr Daten im Speicher. Andererseits glaube ich, dass der Faktor Zeit/Volumen bei irgendwelchen zehnstelligen Zufallsstrings eher vernachlässigt werden kann. ;)

Verfasst: Freitag 23. Oktober 2009, 08:55
von veers
oder einfach from uuid import uuid4 ... :wink:

Verfasst: Freitag 23. Oktober 2009, 09:15
von mkesper
veers hat geschrieben:oder einfach from uuid import uuid4 ... :wink:
Ziemlich doofer Name! ;)

16fd2706-8baf-433b-82eb-8c7fada847da attacks and hits for 4 hit points!

Verfasst: Freitag 23. Oktober 2009, 14:10
von snafu
Hier mal für die Kommandozeile mit der zusätzlichen Verfeinerung, dass auf Systemen, die `/dev/urandom` anbieten, das Device nicht unnötig oft auf und zu gemacht wird.

Wenn man viel Wert auf die Vermeidung von Schlüsselworten bei der Namensgebung legt, kann man `bytes` natürlich auch umbenennen... *räusper*

Wer's konsistenter mag, hier mit einem `close()`, das sich konsequenterweise auch unter Windows beschwert.

Verfasst: Freitag 23. Oktober 2009, 14:35
von stuhlbein
Ich versteh nicht inwiefern das manuelle öffnen von /dev/urandom besser als die nutzung von os.urandom() ist?
Letztenendes ist letzteres bereits plattform-unabhängig, und du öffnest das device nicht öfter oder weniger als os.urandom() es tut.
Im grunde erfindest du mit der Klasse das rad neu, statt ein bereits vorhandenes zu nutzen...

Verfasst: Freitag 23. Oktober 2009, 14:44
von snafu
Doch, das Device wird bei jedem Zugriff auf `os.urandom()` geöffnet, also bei jedem Durchlauf meiner while-Schleife. Mit dem Wrapper wird es einmal geöffnet, es wird in der while-Schleife gelesen und am Ende geschlossen. Auf Windows-Systemen wird weiterhin die Python-Implementierung genommen, die die Windows-Funktion CryptGenRandom aufruft, die augenscheinlich kein Datei-Objekt ist.

Verfasst: Freitag 23. Oktober 2009, 15:14
von lunar
@snafu: Und welches tatsächlich existierende Problem wird dadurch nun gelöst? Was ist denn so schlimm daran, dass diese Datei jedes mal erneut geöffnet wird?

Wie dem auch sei, wenn schon, dann wenigstens mit korrekter Ausnahmebehandlung.

Verfasst: Freitag 23. Oktober 2009, 15:50
von snafu
Zeitersparnis, dachte ich. Wie schon gesagt: Eigentlich ist das eher zu vernachlässigen und bläht den Code nur unnötig auf.

Wo mache ich keine korrekte Ausnahmebehandlung?

Verfasst: Freitag 23. Oktober 2009, 16:01
von lunar
In "get_random_string()". ".close()" wird nicht aufgerufen, wenn eine Ausnahme ausgelöst wird.