Dize Python sınıf nesnesine dönüştürmek?


187

Bir Python işlevine kullanıcı girişi olarak bir dize verildiğinde, şu anda tanımlanmış ad alanında bu ada sahip bir sınıf varsa bir sınıf nesnesi almak istiyorum. Aslında, bu tür bir sonuç üretecek bir fonksiyon için uygulama istiyorum:

class Foo:
    pass

str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>

Bu hiç mümkün mü?

python 

2
Burada bazı yararlı cevaplar var, ancak bu sorunun cevabını özellikle yararlı buldum: stackoverflow.com/questions/452969/…
Tom

Yanıtlar:


116

Uyarı : eval()isteğe bağlı Python kodunu yürütmek için kullanılabilir. Sen gerektiğini asla kullanmayın eval()güvenilmeyen dizeleriyle. (Bkz . Python'un güvenilmeyen dizelerle ilgili değerlendirmesi () bölümüne bakın ? )

Bu en basit gibi görünüyor.

>>> class Foo(object):
...     pass
... 
>>> eval("Foo")
<class '__main__.Foo'>

30
Eval kullanımının sonuçları. İletdiğiniz dizenin kullanıcıdan olmadığından emin olmalısınız.
Overclock

18
Eval () kullanılması, rastgele kod yürütme için kapıyı açık bırakır, güvenlik uğruna bundan kaçınılmalıdır.
m.kocikowski

55
Eval kapıyı hiçbir şeye açık bırakmaz. Kötü amaçlı ve kötü niyetli olarak değerlendirmek için kötü değerleri iletebilecek kötü niyetli, kötü kullanıcılarınız varsa, sadece python kaynağını düzenleyebilirler. Python kaynağını sadece düzenleyebildikleri için kapı açık, öyleydi ve her zaman açık olacak.
S.Lott

34
Söz konusu program bir sunucuda çalışmadığı sürece.
Vatsu1

12
@ s-lott Üzgünüm, ama burada çok kötü tavsiyeler verdiğinizi düşünüyorum. Şunu düşünün: Bilgisayarınız sizden parola istediğinde, kötü niyetli, kötü niyetli kullanıcı aynı zamanda ilgili yürütülebilir dosyayı veya kitaplığı değiştirebilir ve bu denetimi atlayabilir. Bu nedenle, ilk etapta şifre kontrolleri eklemenin anlamsız olduğu iddia edilebilir. Yoksa öyle mi?
Daniel Sk

148

Bu işe yarayabilir:

import sys

def str_to_class(classname):
    return getattr(sys.modules[__name__], classname)

Sınıf yoksa ne olur?
riza

7
Sadece mevcut modülde tanımlanan sınıf için çalışacaktır
luc

1
Gerçekten güzel bir çözüm: Burada içe aktarma değil, daha genel bir sys modülü kullanıyorsunuz.
Stefano Falsetto

Bu herhangi bir fark olabilir mi def str_to_class(str): return vars()[str]?
Arne

113

Şöyle bir şey yapabilirsiniz:

globals()[class_name]

6
Bunu 'eval' çözümünden daha iyi seviyorum çünkü (1) bu kadar kolay ve (2) sizi rastgele kod yürütmeye açık bırakmıyor.
mjumbewu

2
ama kullanıyorsunuz globals()... kaçınılması gereken başka bir şey
Greg

5
@Greg Belki de, ama globals()muhtemelen AFAIK'tan çok daha kötü evaldeğil ve gerçekten daha kötü değil sys.modules[__name__].
Laurence Gonsalves

2
Evet, ne demek istediğini anlıyorum, çünkü sen sadece hiçbir şey
Greg

Değişken bir sınıfta kullanılıyorsa ve o sınıfın birden çok örneği aynı anda yapılırsa. Bu küresel bir değişken olduğu için soruna neden olur mu?
Alok Kumar Singh

98

BazModül içinde yaşayan sınıfı istiyorsunuz foo.bar. Python 2.7 ile, importlib.import_module()Python 3'e geçişi kolaylaştıracağından kullanmak istersiniz :

import importlib

def class_for_name(module_name, class_name):
    # load the module, will raise ImportError if module cannot be loaded
    m = importlib.import_module(module_name)
    # get the class, will raise AttributeError if class cannot be found
    c = getattr(m, class_name)
    return c

Python <2.7 ile:

def class_for_name(module_name, class_name):
    # load the module, will raise ImportError if module cannot be loaded
    m = __import__(module_name, globals(), locals(), class_name)
    # get the class, will raise AttributeError if class cannot be found
    c = getattr(m, class_name)
    return c

kullanın:

loaded_class = class_for_name('foo.bar', 'Baz')

19
import sys
import types

def str_to_class(field):
    try:
        identifier = getattr(sys.modules[__name__], field)
    except AttributeError:
        raise NameError("%s doesn't exist." % field)
    if isinstance(identifier, (types.ClassType, types.TypeType)):
        return identifier
    raise TypeError("%s is not a class." % field)

Bu, hem eski stil hem de yeni stil sınıflarını doğru şekilde işler.


Türlere ihtiyacınız yoktur. sadece yerleşik "tip" için bir takma addır.
Glenn Maynard

1
Evet biliyorum, ama okumanın daha net olduğunu hissediyorum. Yine de, sadece tercih için kaynar.
Evan Fosmark

17

Django'nun bunu nasıl ele aldığına baktım

django.utils.module_loading buna sahiptir

def import_string(dotted_path):
    """
    Import a dotted module path and return the attribute/class designated by the
    last name in the path. Raise ImportError if the import failed.
    """
    try:
        module_path, class_name = dotted_path.rsplit('.', 1)
    except ValueError:
        msg = "%s doesn't look like a module path" % dotted_path
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

    module = import_module(module_path)

    try:
        return getattr(module, class_name)
    except AttributeError:
        msg = 'Module "%s" does not define a "%s" attribute/class' % (
            module_path, class_name)
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

Gibi kullanabilirsiniz import_string("module_path.to.all.the.way.to.your_class")



3

Evet, bunu yapabilirsiniz. Sınıflarınızın genel ad alanında olduğunu varsayarsak, böyle bir şey bunu yapar:

import types

class Foo:
    pass

def str_to_class(s):
    if s in globals() and isinstance(globals()[s], types.ClassType):
            return globals()[s]
    return None

str_to_class('Foo')

==> <class __main__.Foo at 0x340808cc>

1
Python 3'te artık ClassType yoktur.
riza

1
İsinstance (x, type) kullanın.
Glenn Maynard

3

İsteğe bağlı kod yürütme veya istenmeyen kullanıcı tarafından iletilen adlar açısından, kabul edilebilir işlev / sınıf adlarının bir listesine sahip olabilirsiniz ve giriş listedeki biriyle eşleşirse değerlendirilir.

PS: Biliyorum .... biraz geç .... ama gelecekte tökezleyen herkes içindir.


9
Listeyi koruyacaksanız, bunun yerine isimden sınıfa bir diksiyon da koruyabilirsiniz. Sonra eval () gerekmez.
Fasaxc

3

İmportlib kullanmak benim için en iyisi oldu.

import importlib

importlib.import_module('accounting.views') 

Bu, içe aktarmak istediğiniz python modülü için dize nokta gösterimini kullanır .


3

Gerçekten bir dize ile yapmak sınıfları almak istiyorsanız, saklamalısınız (veya düzgün, ifadeli referans ) bir sözlükte onları. Sonuçta, bu aynı zamanda sınıflarınızı daha yüksek bir seviyede adlandırmanıza ve istenmeyen sınıfları açığa çıkarmanıza izin vermez.

Örneğin, Python'da aktör sınıflarının tanımlandığı ve diğer genel sınıfların kullanıcı girdisi tarafından erişilmesini önlemek istediğiniz bir oyundan.

Başka bir yaklaşım (aşağıdaki örnekte olduğu gibi), dictyukarıdakileri içeren tamamen yeni bir sınıf yapmak olacaktır . Bu:

  • Daha kolay düzenleme için birden fazla sınıf sahibinin oluşturulmasına izin verin (biri aktör sınıfları için diğeri ses türleri için);
  • Hem tutucu hem de tutulan sınıflarda değişiklikler yapın;
  • Ve dikteye sınıf eklemek için sınıf yöntemlerini kullanabilirsiniz. (Aşağıdaki soyutlama gerçekten gerekli olmasa da, sadece ... "gösterim" içindir ).

Misal:

class ClassHolder(object):
    def __init__(self):
        self.classes = {}

    def add_class(self, c):
        self.classes[c.__name__] = c

    def __getitem__(self, n):
        return self.classes[n]

class Foo(object):
    def __init__(self):
        self.a = 0

    def bar(self):
        return self.a + 1

class Spam(Foo):
    def __init__(self):
        self.a = 2

    def bar(self):
        return self.a + 4

class SomethingDifferent(object):
    def __init__(self):
        self.a = "Hello"

    def add_world(self):
        self.a += " World"

    def add_word(self, w):
        self.a += " " + w

    def finish(self):
        self.a += "!"
        return self.a

aclasses = ClassHolder()
dclasses = ClassHolder()
aclasses.add_class(Foo)
aclasses.add_class(Spam)
dclasses.add_class(SomethingDifferent)

print aclasses
print dclasses

print "======="
print "o"
print aclasses["Foo"]
print aclasses["Spam"]
print "o"
print dclasses["SomethingDifferent"]

print "======="
g = dclasses["SomethingDifferent"]()
g.add_world()
print g.finish()

print "======="
s = []
s.append(aclasses["Foo"]())
s.append(aclasses["Spam"]())

for a in s:
    print a.a
    print a.bar()
    print "--"

print "Done experiment!"

Bu beni döndürür:

<__main__.ClassHolder object at 0x02D9EEF0>
<__main__.ClassHolder object at 0x02D9EF30>
=======
o
<class '__main__.Foo'>
<class '__main__.Spam'>
o
<class '__main__.SomethingDifferent'>
=======
Hello World!
=======
0
1
--
2
6
--
Done experiment!

Bunlarla yapılacak bir başka eğlenceli deney, turşu yapan bir yöntem eklemektir, ClassHolderböylece yaptığınız tüm sınıfları asla kaybetmezsiniz: ^)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.