Làm cách nào để tạo động các lớp dẫn xuất từ ​​một lớp cơ sở


92

Ví dụ, tôi có một lớp cơ sở như sau:

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

Từ lớp này, tôi lấy ra một số lớp khác, ví dụ:

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

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

Có cách nào hay ho, hay ho để tạo động các lớp đó bằng một lệnh gọi hàm đặt lớp mới vào phạm vi hiện tại của tôi không, như:

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

Vì sẽ có những nhận xét và câu hỏi tại sao tôi cần điều này: Các lớp dẫn xuất đều có cấu trúc bên trong giống hệt nhau với sự khác biệt là hàm tạo lấy một số đối số chưa được xác định trước đó. Ví dụ,MyClass lấy các từ khóa atrong khi hàm tạo của lớp TestClasslấy bc.

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

Nhưng họ KHÔNG BAO GIỜ nên sử dụng kiểu của lớp làm đối số đầu vào như

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

Tôi có cách này để làm việc nhưng sẽ thích cách khác, tức là các đối tượng lớp được tạo động.


Chỉ để chắc chắn, bạn muốn loại cá thể thay đổi tùy thuộc vào các đối số được cung cấp? Giống như nếu tôi cho một anó sẽ luôn luôn được MyClassTestClasssẽ không bao giờ mất một a? Tại sao không chỉ khai báo tất cả 3 đối số trong BaseClass.__init__()mà mặc định tất cả chúng thành None? def __init__(self, a=None, b=None, C=None)?
acattle

Tôi không thể khai báo bất cứ điều gì trong lớp cơ sở, vì tôi không biết tất cả các đối số mà tôi có thể sử dụng. Tôi có thể có 30 clases khác nhau với 5 đối số khác nhau, vì vậy việc khai báo 150 đối số trong constructur không phải là một giải pháp.
Alex

Câu trả lời:


141

Bit mã này cho phép bạn tạo các lớp mới với tên động và tên tham số. Việc xác minh tham số trong __init__chỉ không cho phép các tham số không xác định, nếu bạn cần các xác minh khác, như loại hoặc chúng là bắt buộc, chỉ cần thêm logic vào đó:

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

Và điều này hoạt động như thế này, ví dụ:

>>> 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

Tôi thấy bạn đang yêu cầu chèn các tên động trong phạm vi đặt tên - bây giờ, điều đó không được coi là một phương pháp hay trong Python - bạn có tên biến, được biết đến tại thời điểm mã hóa hoặc dữ liệu - và các tên được học trong thời gian chạy thì nhiều hơn " dữ liệu "hơn" biến "-

Vì vậy, bạn chỉ có thể thêm các lớp của mình vào từ điển và sử dụng chúng từ đó:

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

Và nếu thiết kế của bạn thực sự cần các tên trong phạm vi, chỉ cần làm tương tự, nhưng sử dụng từ điển được trả về bởi globals() cuộc gọi thay vì từ điển tùy ý:

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

(Thực sự sẽ có thể cho hàm nhà máy lớp chèn tên động trên phạm vi toàn cục của trình gọi - nhưng điều đó thậm chí còn tệ hơn và không tương thích trên các triển khai Python. Cách để làm điều đó là lấy tên gọi khung thực thi, thông qua sys._getframe (1) và đặt tên lớp trong từ điển chung của khung trong f_globalsthuộc tính của nó ).

update, tl; dr: Câu trả lời này đã trở nên phổ biến, vẫn rất cụ thể cho phần nội dung câu hỏi. Câu trả lời chung về cách "tạo động các lớp dẫn xuất từ ​​một lớp cơ sở" trong Python là một lệnh gọi đơn giản để typechuyển tên lớp mới, một bộ giá trị với (các) baseclass và phần __dict__thân cho lớp mới giống như sau:

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

cập nhật
Bất kỳ ai cần điều này cũng nên kiểm tra dự án thì là - nó tuyên bố có thể chọn và bỏ chọn các lớp giống như pickle đối với các vật thể thông thường và đã sống với nó trong một số thử nghiệm của tôi.


2
Nếu tôi nhớ không nhầm, BaseClass.__init__()sẽ tốt hơn vì cái tổng quát hơn super(self.__class__).__init__(), chơi đẹp hơn khi các lớp mới được phân lớp. (Tham khảo: rhettinger.wordpress.com/2011/05/26/super-considered-super )
Eric O Lebigot

@EOL: Nó sẽ dành cho các lớp được khai báo tĩnh - nhưng vì bạn không có tên lớp thực sự để mã hóa cứng làm tham số đầu tiên cho Super, điều đó sẽ đòi hỏi rất nhiều bước nhảy xung quanh. Hãy thử thay thế nó bằng superở trên và tạo một lớp con của một lớp được tạo động để hiểu nó; Và, mặt khác, trong trường hợp này, bạn có thể có baseclass làm đối tượng chung mà từ đó gọi __init__.
jsbueno

Bây giờ tôi đã có một chút thời gian để xem xét giải pháp được đề xuất, nhưng nó không hoàn toàn như những gì tôi muốn. Đầu tiên, có vẻ như __init__of BaseClassđược gọi với một đối số, nhưng thực tế BaseClass.__init__luôn có một danh sách các đối số từ khóa tùy ý. Thứ hai, giải pháp ở trên đặt tất cả các tên tham số được phép làm thuộc tính, đây không phải là điều tôi muốn. BẤT KỲ đối số NÀO ĐỀU đi đếnBaseClass , nhưng tôi biết đối số nào khi tạo lớp dẫn xuất. Tôi có thể sẽ cập nhật câu hỏi hoặc hỏi một câu chính xác hơn để làm cho nó rõ ràng hơn.
Alex

@jsbueno: Đúng vậy, bằng cách sử dụng super()tôi đã đề cập cho TypeError: must be type, not SubSubClass. Nếu tôi hiểu chính xác, điều này xuất phát từ đối số đầu tiên selfcủa __init__(), làSubSubClass nơi mà một typeđối tượng được mong đợi: điều này có vẻ liên quan đến thực tế super(self.__class__)là một siêu đối tượng không bị ràng buộc. __init__()Phương pháp của nó là gì? Tôi không chắc phương thức nào như vậy có thể yêu cầu đối số đầu tiên của loại type. Bạn có thể giải thích? (Lưu ý Side: tôi super()cách tiếp cận thực sự không có ý nghĩa, ở đây, bởi vì __init__()có một chữ ký khác nhau.)
Eric O Lebigot

1
@EOL: vấn đề chính thực sự là nếu bạn tạo một lớp con khác của lớp nhân tử hóa: self .__ class__ sẽ tham chiếu đến lớp con đó, không phải lớp mà "super" được gọi - và bạn nhận được đệ quy vô hạn.
jsbueno

89

type() là hàm tạo các lớp (và cụ thể là các lớp con):

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

Khi cố gắng hiểu ví dụ này bằng Python 2.7, tôi đã TypeErrornói __init__() takes exactly 2 arguments (1 given). Tôi thấy rằng thêm một cái gì đó (bất cứ thứ gì?) Để lấp đầy khoảng trống là đủ. Ví dụ, obj = SubClass('foo')chạy mà không có lỗi.
DaveL17

Đây là điều bình thường, vì SubClasslà một sub-class của BaseClasstrong câu hỏi và BaseClassphải mất một tham số ( classtype, đó là 'foo'trong ví dụ của bạn).
Eric O Lebigot

-2

Để tạo một lớp có giá trị thuộc tính động, hãy kiểm tra mã bên dưới. NB. Đây là các đoạn mã trong ngôn ngữ lập trình python

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*
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.