2012-01-16 39 views
5

Trong Python, có cách nào cho một thể hiện của một đối tượng để xem tên biến được gán cho nó không? Hãy thực hiện theo các ví dụ sau:Đối tượng có thể kiểm tra tên của biến mà nó được gán không?

class MyObject(object): 
    pass 

x = MyObject() 

Có thể cho MyObject thấy nó được gán cho tên biến x tại bất kỳ điểm nào không? Giống như phương pháp __init__ của nó?

+0

Nó không phải là có thể - mặc dù thời gian gần đây (3-4 months ago) trên Python-ý tưởng liệt kê đã có một số cuộc thảo luận về việc thêm khả năng cho một cái gì đó như thế này. – jsbueno

+3

Câu trả lời ngắn gọn là: không, đừng thử ..và câu trả lời thực sự là có, nhưng đừng thử .. :) – wim

Trả lời

4

Không. Đối tượng và tên sống ở các kích thước riêng biệt. Một đối tượng có thể có nhiều tên trong suốt vòng đời của nó và không thể xác định được cái nào có thể là cái bạn muốn. Thậm chí ở đây:

class Foo(object): 
    def __init__(self): pass 

x = Foo() 

hai tên biểu thị cùng một đối tượng (self khi __init__ chạy, x trong phạm vi toàn cầu).

4

Có, có thể *. Tuy nhiên, vấn đề là khó khăn hơn có vẻ như khi Thoạt nhìn:

  • Có thể có nhiều tên giao cho cùng một đối tượng.
  • Có thể có không có tên.

Bất kể, biết làm thế nào để tìm ra tên của một đối tượng đôi khi có thể hữu ích cho mục đích gỡ lỗi - và đây là cách để làm điều đó:

import gc, inspect 

def find_names(obj): 
    frame = inspect.currentframe() 
    for frame in iter(lambda: frame.f_back, None): 
     frame.f_locals 
    obj_names = [] 
    for referrer in gc.get_referrers(obj): 
     if isinstance(referrer, dict): 
      for k, v in referrer.items(): 
       if v is obj: 
        obj_names.append(k) 
    return obj_names 

Nếu bạn đã từng bị cám dỗ logic cơ sở xung quanh tên của các biến của bạn, tạm dừng một lúc và xem xét việc thiết kế lại/tái cấu trúc mã có thể giải quyết được vấn đề hay không. Sự cần thiết phải khôi phục tên của đối tượng từ đối tượng chính nó thường có nghĩa là các cấu trúc dữ liệu cơ bản trong chương trình của bạn cần được suy nghĩ lại.

* ít nhất là trong CPython

0

Nó có thể không được thực hiện thường, mặc dù điều này có thể đạt được bằng cách sử dụng cơ sở vật chất mẫn và có nghĩa là cho gỡ một chương trình. Mã phải chạy từ tệp ".py", chứ không phải từ mã bytecode được biên dịch, hoặc bên trong mô-đun đã nén - vì nó dựa vào việc đọc mã nguồn của tệp, từ bên trong phương thức sẽ tìm thấy "nó ở đâu đang chạy".

Bí quyết là truy cập khung thực thi nơi đối tượng được khởi tạo từ - với inspect.currentframe - đối tượng khung có giá trị "f_lineno" cho biết số dòng nơi cuộc gọi đến phương thức đối tượng (trong trường hợp này, __init__) đã được gọi. Hàm inspect.filename cho phép truy xuất mã nguồn của tệp và tìm nạp số dòng thích hợp.

Phân tích cú pháp ngây thơ sau đó xem trước phần đã đặt trước dấu "=" và giả sử đó là biến chứa đối tượng.

from inspect import currentframe, getfile 

class A(object): 
    def __init__(self): 
     f = currentframe(1) 
     filename = getfile(f) 
     code_line = open(filename).readlines()[f.f_lineno - 1] 
     assigned_variable = code_line.split("=")[0].strip() 
     print assigned_variable 

my_name = A() 
other_name = A() 

Điều đó sẽ không làm việc cho nhiều assignents, biểu thức sáng tác với các đối tượng trước khi assignemtn được thực hiện, đối tượng được nối thêm vào danh sách hoặc thêm vào từ điển hoặc bộ, đối tượng instantiation trong intialization của for vòng, và có Chúa mới biết nhiều tình huống hơn - Và hãy nhớ rằng sau lần ghi nhận đầu tiên, đối tượng có thể được tham chiếu bởi bất kỳ biến số nào khác.

dòng Botton: nó có thể, nhưng như một món đồ chơi - nó không thể được sử dụng i mã sản xuất - chỉ có tên varibal để được thông qua như là một chuỗi trong quá trình khởi đối tượng, cũng giống như người ta phải làm khi tạo một

các "đúng cách" collections.namedtuple để làm điều đó, nếu bạn đang cần tên, là phải vượt qua một cách rõ ràng tên cho khởi đối tượng, như một tham số chuỗi, như trong:

class A(object): 
    def __init__(self, name): 
     self.name = name 

x = A("x") 

Và vẫn còn, nếu hoàn toàn cần phải gõ các đối tượng'name chỉ một lần, có là một cách khác - đọc tiếp. Do cú pháp của Python, một số nhiệm vụ đặc biệt, không sử dụng toán tử "=" cho phép một đối tượng biết nó được gán tên. Vì vậy, các statemtns khác thực hiện các phép gán trong Python là các từ khóa for, with, def và class - Có thể lạm dụng nó, như một tạo lớp và định nghĩa hàm là các câu lệnh gán tạo các đối tượng "biết" tên của chúng.

Hãy tập trung vào tuyên bố def. Nó thường tạo ra một hàm. Nhưng sử dụng một trang trí, bạn có thể sử dụng "def" để tạo ra bất kỳ loại đối tượng - và có tên được sử dụng cho các chức năng có sẵn cho các nhà xây dựng:

class MyObject(object): 
    def __new__(cls, func): 
     # Calls the superclass constructor and actually instantiates the object: 
     self = object.__new__(cls) 
     #retrieve the function name: 
     self.name = func.func_name 
     #returns an instance of this class, instead of a decorated function: 
     return self 
    def __init__(self, func): 
     print "My name is ", self.name 

#and the catch is that you can't use "=" to create this object, you have to do: 

@MyObject 
def my_name(): pass 

(Bằng cách này, cuối cùng làm việc đó thể được sử dụng trong mã sản xuất, không giống như một trong đó khu du lịch để đọc các tập tin nguồn)

+0

rất thú vị! – wim

0

đây là một chức năng đơn giản để đạt được những gì bạn muốn, giả sử bạn muốn lấy tên của biến nơi dụ được gán từ một phương pháp gọi :

import inspect 

def get_instance_var_name(method_frame, instance): 
    parent_frame = method_frame.f_back 
    matches = {k: v for k,v in parent_frame.f_globals.items() if v is instance} 
    assert len(matches) < 2 
    return matches.keys()[0] if matches else None 

Dưới đây là một ví dụ sử dụng:

class Bar: 
    def foo(self): 
     print get_instance_var_name(inspect.currentframe(), self) 

bar = Bar() 
bar.foo() # prints 'bar' 

def nested(): 
    bar.foo() 
nested() # prints 'bar' 

Bar().foo() # prints None 
0

Như nhiều người khác đã nói, nó không thể được thực hiện đúng cách. Tuy nhiên lấy cảm hứng từ jsbueno, tôi có một giải pháp thay thế cho anh ấy.

Giống như giải pháp của mình, tôi kiểm tra khung ngăn xếp người gọi, có nghĩa là nó chỉ hoạt động đúng cho người gọi được Python triển khai (xem lưu ý bên dưới). Không giống như anh ta, tôi kiểm tra bytecode của người gọi trực tiếp (thay vì tải và phân tích cú pháp mã nguồn). Sử dụng Python 3.4 + 's dis.get_instructions() điều này có thể được thực hiện với một số hy vọng khả năng tương thích tối thiểu. Mặc dù đây vẫn là một số mã hacky.

import inspect 
import dis 

def take1(iterator): 
    try: 
     return next(iterator) 
    except StopIteration: 
     raise Exception("missing bytecode instruction") from None 

def take(iterator, count): 
    for x in range(count): 
     yield take1(iterator) 

def get_assigned_name(frame): 
    """Takes a frame and returns a description of the name(s) to which the 
    currently executing CALL_FUNCTION instruction's value will be assigned. 

    fn()     => None 
    a = fn()    => "a" 
    a, b = fn()    => ("a", "b") 
    a.a2.a3, b, c* = fn() => ("a.a2.a3", "b", Ellipsis) 
    """ 

    iterator = iter(dis.get_instructions(frame.f_code)) 
    for instr in iterator: 
     if instr.offset == frame.f_lasti: 
      break 
    else: 
     assert False, "bytecode instruction missing" 
    assert instr.opname.startswith('CALL_') 
    instr = take1(iterator) 
    if instr.opname == 'POP_TOP': 
     raise ValueError("not assigned to variable") 
    return instr_dispatch(instr, iterator) 

def instr_dispatch(instr, iterator): 
    opname = instr.opname 
    if (opname == 'STORE_FAST'    # (co_varnames) 
      or opname == 'STORE_GLOBAL'  # (co_names) 
      or opname == 'STORE_NAME'  # (co_names) 
      or opname == 'STORE_DEREF'): # (co_cellvars++co_freevars) 
     return instr.argval 
    if opname == 'UNPACK_SEQUENCE': 
     return tuple(instr_dispatch(instr, iterator) 
        for instr in take(iterator, instr.arg)) 
    if opname == 'UNPACK_EX': 
     return (*tuple(instr_dispatch(instr, iterator) 
        for instr in take(iterator, instr.arg)), 
       Ellipsis) 
    # Note: 'STORE_SUBSCR' and 'STORE_ATTR' should not be possible here. 
    # `lhs = rhs` in Python will evaluate `lhs` after `rhs`. 
    # Thus `x.attr = rhs` will first evalute `rhs` then load `a` and finally 
    # `STORE_ATTR` with `attr` as instruction argument. `a` can be any 
    # complex expression, so full support for understanding what a 
    # `STORE_ATTR` will target requires decoding the full range of expression- 
    # related bytecode instructions. Even figuring out which `STORE_ATTR` 
    # will use our return value requires non-trivial understanding of all 
    # expression-related bytecode instructions. 
    # Thus we limit ourselfs to loading a simply variable (of any kind) 
    # and a arbitary number of LOAD_ATTR calls before the final STORE_ATTR. 
    # We will represents simply a string like `my_var.loaded.loaded.assigned` 
    if opname in {'LOAD_CONST', 'LOAD_DEREF', 'LOAD_FAST', 
        'LOAD_GLOBAL', 'LOAD_NAME'}: 
     return instr.argval + "." + ".".join(
      instr_dispatch_for_load(instr, iterator)) 
    raise NotImplementedError("assignment could not be parsed: " 
           "instruction {} not understood" 
           .format(instr)) 

def instr_dispatch_for_load(instr, iterator): 
    instr = take1(iterator) 
    opname = instr.opname 
    if opname == 'LOAD_ATTR': 
     yield instr.argval 
     yield from instr_dispatch_for_load(instr, iterator) 
    elif opname == 'STORE_ATTR': 
     yield instr.argval 
    else: 
     raise NotImplementedError("assignment could not be parsed: " 
            "instruction {} not understood" 
            .format(instr)) 

Lưu ý: Các hàm do C thực hiện không hiển thị dưới dạng khung ngăn xếp Python và do đó được ẩn với tập lệnh này. Điều này sẽ dẫn đến kết quả dương tính giả. Hãy xem xét hàm Python f() gọi hàm a = g(). g() được C triển khai và gọi b = f2(). Khi f2() tìm cách tra cứu tên được chỉ định, nó sẽ nhận được a thay vì b vì tập lệnh không biết đến hàm C. (Ít nhất đây là cách tôi đoán nó sẽ làm việc: P) ví dụ

Cách sử dụng:

class MyItem(): 
    def __init__(self): 
     self.name = get_assigned_name(inspect.currentframe().f_back) 

abc = MyItem() 
assert abc.name == "abc" 
Các vấn đề liên quan