Temel sınıftan dinamik olarak nasıl türetilmiş sınıflar oluşturabilirim


95

Örneğin aşağıdaki gibi bir temel sınıfım var:

class BaseClass(object):
    def __init__(self, classtype):
        self._type = classtype

Bu sınıftan birkaç başka sınıf türetiyorum, örneğin

class TestClass(BaseClass):
    def __init__(self):
        super(TestClass, self).__init__('Test')

class SpecialClass(BaseClass):
    def __init__(self):
        super(TestClass, self).__init__('Special')

Yeni sınıfı şu anki kapsamıma yerleştiren bir işlev çağrısı ile bu sınıfları dinamik olarak oluşturmanın güzel, pitonik bir yolu var mı, örneğin:

foo(BaseClass, "My")
a = MyClass()
...

Buna neden ihtiyacım olduğuna dair yorumlar ve sorular olacağından: Türetilen sınıfların tümü, kurucunun önceden tanımlanmamış bir dizi argüman aldığı farkla aynı iç yapıya sahiptir. Yani, örneğin, MyClassanahtar kelimeler alır asınıfın yapıcı ederken TestClassalır bve c.

inst1 = MyClass(a=4)
inst2 = MyClass(a=5)
inst3 = TestClass(b=False, c = "test")

Ancak sınıfın türünü ASLA giriş argümanı olarak kullanmamalıdırlar.

inst1 = BaseClass(classtype = "My", a=4)

Bunu çalıştırdım ama diğer yolu tercih ederim, yani dinamik olarak oluşturulmuş sınıf nesneleri.


Emin olmak için, örnek türünün sağlanan argümanlara bağlı olarak değişmesini mi istiyorsunuz? Sanki bir verirsem a, her zaman olacak MyClassve TestClassasla almayacak amı? Neden 3 bağımsız değişkenin tümünü açıklayıp hepsini BaseClass.__init__()varsayılan olarak belirtmiyorsunuz None? def __init__(self, a=None, b=None, C=None)?
acattle

Kullanabileceğim tüm argümanları bilmediğim için temel sınıfta hiçbir şey bildiremiyorum. Her biri 5 farklı argüman içeren 30 farklı sınıfım olabilir, bu nedenle yapıda 150 argüman bildirmek bir çözüm değildir.
Alex

Yanıtlar:


145

Bu kod parçası, dinamik adlar ve parametre adları ile yeni sınıflar oluşturmanıza olanak tanır. Parametre doğrulaması, __init__bilinmeyen parametrelere izin vermez, tür gibi başka doğrulamalara ihtiyacınız varsa veya bunların zorunlu olduğunu, sadece mantığı buraya ekleyin:

class BaseClass(object):
    def __init__(self, classtype):
        self._type = classtype

def ClassFactory(name, argnames, BaseClass=BaseClass):
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            # here, the argnames variable is the one passed to the
            # ClassFactory call
            if key not in argnames:
                raise TypeError("Argument %s not valid for %s" 
                    % (key, self.__class__.__name__))
            setattr(self, key, value)
        BaseClass.__init__(self, name[:-len("Class")])
    newclass = type(name, (BaseClass,),{"__init__": __init__})
    return newclass

Ve bu şu şekilde çalışır, örneğin:

>>> SpecialClass = ClassFactory("SpecialClass", "a b c".split())
>>> s = SpecialClass(a=2)
>>> s.a
2
>>> s2 = SpecialClass(d=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __init__
TypeError: Argument d not valid for SpecialClass

Dinamik isimleri adlandırma kapsamına eklemeyi istediğinizi görüyorum - şimdi, bu Python'da iyi bir uygulama olarak görülmüyor - ya kodlama zamanında bilinen değişken isimleriniz ya da verileriniz var - ve çalışma zamanında öğrenilen isimler daha fazla " veriler "değişkenlerden" -

Yani, sınıflarınızı bir sözlüğe ekleyebilir ve oradan kullanabilirsiniz:

name = "SpecialClass"
classes = {}
classes[name] = ClassFactory(name, params)
instance = classes[name](...)

Ve tasarımınız kesinlikle adların kapsamına ihtiyaç duyuyorsa, sadece aynısını yapın, ancak globals() rastgele bir sözlük yerine çağrı tarafından döndürülen sözlüğü kullanın :

name = "SpecialClass"
globals()[name] = ClassFactory(name, params)
instance = SpecialClass(...)

(Sınıf fabrikası işlevinin adı dinamik olarak arayanın genel kapsamına eklemesi gerçekten mümkün olabilir - ancak bu daha da kötü bir uygulamadır ve Python uygulamalarıyla uyumlu değildir. Bunu yapmanın yolu, arayanın yürütme çerçevesi, sys._getframe (1) aracılığıyla ve sınıf adını çerçevenin genel sözlüğünde f_globalsözniteliğinde ayarlama).

update, tl; dr: Bu cevap popüler hale geldi, hala soru gövdesine özgü. Python'da "bir temel sınıftan dinamik olarak türetilmiş sınıfların nasıl oluşturulacağına " ilişkin genel yanıt type, yeni sınıf adını, temel sınıf (lar) ı içeren bir demet ve yeni sınıf için gövdeyi iletmeye yönelik basit bir çağrıdır __dict__- buna benzer:

>>> new_class = type("NewClassName", (BaseClass,), {"new_method": lambda self: ...})

update Buna
ihtiyaç duyan herkes dereotu projesini de kontrol etmelidir - tıpkı sıradan nesnelere turşunun yaptığı gibi sınıfları turşu yapıp çözebildiğini iddia ediyor ve bazı testlerimde bunu yaşamıştı.


2
Doğru hatırlıyorsam , yeni sınıflar alt sınıflara ayrıldığında daha iyi oynayan daha BaseClass.__init__()genel olan daha iyi olurdu super(self.__class__).__init__(). (Referans: rhettinger.wordpress.com/2011/05/26/super-consaceful-super )
Eric O Lebigot

@EOL: Statik olarak bildirilen sınıflar için olurdu - ancak Super'in ilk parametresi olarak kodlanacak gerçek sınıf adına sahip olmadığınız için, bu çok fazla dans etmeyi gerektirir. Bunu superyukarıdakiyle değiştirmeyi deneyin ve anlamak için dinamik olarak oluşturulmuş bir sınıfın bir alt sınıfını oluşturun; Ve öte yandan, bu durumda, temel sınıfı çağırmak için genel bir nesne olarak elde edebilirsiniz __init__.
jsbueno

Şimdi önerilen çözüme bakmak için biraz zamanım vardı, ancak istediğim tam olarak bu değil. Birincisi, benziyor __init__ait BaseClassbir argümanla çağrılır, ama aslında BaseClass.__init__her zaman anahtar kelime argümanların rastgele bir listesini alır. İkinci olarak, yukarıdaki çözüm, izin verilen tüm parametre adlarını öznitelikler olarak ayarlıyor, ki bu istediğim şey değil. HERHANGİ BİR argümana gitmek ZORUNDA BaseClass, ancak türetilmiş sınıfı oluştururken hangisini bildiğim. Muhtemelen soruyu güncelleyeceğim veya daha net hale getirmek için daha kesin bir soru soracağım.
Alex

@jsbueno: Doğru, super()bahsettiğim verirleri kullanarak TypeError: must be type, not SubSubClass. Doğru anladıysam, bu ilk argüman gelen selfbir __init__()bir olduğunu, SubSubClassbir yerde typenesne beklenir: Bu gerçeği ile ilgili görünüyor super(self.__class__)bir ilişkisiz süper nesnesidir. Onun nedir __init__()yöntemi? Böyle bir yöntemin hangi türden bir ilk argüman gerektirebileceğinden emin değilim type. Açıklar mısın (Yan not: Burada benim super()yaklaşımım gerçekten mantıklı değil, çünkü __init__()değişken imzası var.)
Eric O Lebigot

1
@EOL: asıl sorun aslında çarpanlara ayrılmış sınıfın başka bir alt sınıfını yaratırsanız: self .__ sınıf__ "süper" olarak adlandırılan sınıfa değil, bu alt sınıfa atıfta bulunacaktır - ve sonsuz özyineleme elde edersiniz.
jsbueno

91

type() sınıfları (ve özellikle alt sınıfları) oluşturan işlevdir:

def set_x(self, value):
    self.x = value

SubClass = type('SubClass', (BaseClass,), {'set_x': set_x})
# (More methods can be put in SubClass, including __init__().)

obj = SubClass()
obj.set_x(42)
print obj.x  # Prints 42
print isinstance(obj, BaseClass)  # True

Python 2.7 kullanarak bu örneği anlamaya çalışırken, bir var TypeErrordedi __init__() takes exactly 2 arguments (1 given). Boşluğu doldurmak için bir şeyler (herhangi bir şey?) Eklemenin yeterli olacağını buldum. Örneğin obj = SubClass('foo')hatasız çalışır.
DaveL17

Çünkü bu, normaldir SubClassbir alt sınıfıdır BaseClasssöz konusu ve BaseClass(bir parametre alır classtypeolduğunu 'foo'sizin örnekte).
Eric O Lebigot

@EricOLebigot Burada BaseClass'ı başlatmak için bir şekilde çağrı yapabilir miyim? süper gibi mi?
mikazz

-3

Dinamik öznitelik değerine sahip bir sınıf oluşturmak için aşağıdaki kodu kontrol edin. NB. Bu, python programlama dilindeki kod parçacıklarıdır

def create_class(attribute_data, **more_data): # define a function with required attributes
    class ClassCreated(optional extensions): # define class with optional inheritance
          attribute1 = adattribute_data # set class attributes with function parameter
          attribute2 = more_data.get("attribute2")

    return ClassCreated # return the created class

# use class

myclass1 = create_class("hello") # *generates a class*
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.