Hướng dẫn python formatting variable name

Như bài trước thì mình đã đi tìm hiểu qua cơ bản về python và chạy chương trình đầu tiên, chương trình muôn thủa Hello world. Nếu bạn nào chưa xem thì xem tại đây. Còn phần này mình sẽ đi qua về variable trong python. Let's go.

Quy tắc đặt tên biến

Đầu tiên thì chúng ta cùng lướt qua về quy tắc đặt tên biến. Làm gì thì cũng phải biết quy tắc của nó.

Quy tắc:

  • Tên biến chỉ có thể chứa chữ cái, số và dấu gạch dưới _. Có thể bắt đầu bằng chữ cái hoặc dấu gạch dưới, nhưng không được bắt đầu bằng số. VD: bien, _bien nhưng không thể đặt là 1_bien.
  • Khoảng trống (space) không được cho phép khi đặt tên biến, nhưng bạn có thể sử dụng dấu gạch dưới để tách các từ.
  • Khi đặt tên biến không được sử dụng từ khóa Python và tên của hàm. Các từ khóa trong python mà ta không thể đặt cho biến.

Hướng dẫn python formatting variable name

Các kiểu dữ liệu trong python

5 kiểu dữ liệu chuẩn trong python là:

  • Number
  • String
  • List
  • Tuple
  • Set
  • Dictionary

Ta có thể sử dụng hàm type() để kiểm tra kiểu dữ liệu của biến

Number

Python hỗ trợ 5 kiểu dữ liệu numbers khác nhau:

  • Int
  • Float
  • Long
  • Complex (số phức)
  • Fraction (số thập phân)

Các phép toán cơ bản với kiểu number: +, -, /, *, %, //, **

String

Bên cạnh số, Python cũng có thể thao tác với chuỗi, được biểu diễn bằng nhiều cách. Chúng có thể được để trong dấu nháy đơn ('...') hoặc kép ("...") ("""...""") với cùng một kết quả. Các chuỗi có thể được lập chỉ mục với số thứ tự của ký thự trong chuỗi.

word = "Python"

word[0] => P

word[0:2] => "Py"

word[2:5] => "tho"

Nối chuỗi sử dụng toán tử +.

List

Trong Python, list được biểu diễn bằng dãy các giá trị, được phân tách nhau bằng dấu phẩy, nằm trong dấu []. Các danh sách có thể chứa nhiều mục với kiểu khác nhau, nhưng thông thường là các mục có cùng kiểu (có vẻ giống array, nhưng khác với array là các phần tử có thể khác kiểu). squares = [1, 4, 9, 16, 25]

list = [1, "Hello", 9.6]

Index (chỉ mục) của list: để truy cập các phần tử của list chúng ta dùng chỉ mục, thứ tự của phần tử trong list để lấy ra. vd:

Các phương thức khác:

Vì các kiểu dữ liêu trên các ngôn ngữ lập trình khác đều có nên mình sẽ viết nó ngắn gọn nhất. Còn 2 kiểu dữ liệu dưới đây là các kiểu dữ liệu chỉ có ở python.

Tuple

Tuple trong Python là một chuỗi các phần tử có thứ tự giống như list. Sự khác biệt giữa list và tuple là chúng ta không thể thay đổi các phần tử trong tuple khi đã gán, nhưng trong list thì các phần tử có thể thay đổi. Tuple thường được sử dụng cho các dữ liệu không cho phép sửa đổi.

Tuple thường được sử dụng cho các phần tử không cùng kiểu dữ liệu (khác nhau) và list thường sử dụng cho các phần tử có cùng kiểu dữ liệu. Việc Tuple không thay đổi khi sử dụng nên khi sử dụng sẽ đảm bảo rằng dữ liệu đó không bị thay đổi.

Tuple được tạo bằng cách đặt tất cả các phần tử của nó trong dấu ngoặc đơn (), phân tách bằng dấu phẩy. Bạn có thể bỏ dấu ngoặc đơn nếu muốn, nhưng nên thêm nó vào cho code rõ ràng hơn.

myTuple = (2, 4, 16, 256)
myTuple = 100, "nghiem tuan", 10.9

Tuple không bị giới hạn số lượng phần tử và có thể có nhiều kiểu dữ liệu khác nhau như số nguyên, số thập phân, list, string,...

Tạo tuple chỉ có một phần tử hơi phức tạp chút, nếu bạn tạo theo cách thông thường là cho phần tử đó vào trong cặp dấu () là chưa đủ, cần phải thêm dấu phẩy để chỉ ra rằng, đây là tuple.

Để truy cập đến từng phần tử trong tuple cũng tương tự như list ta sử dụng []:

myTuple = (2, 4, 16, [50, 100])
print(myTuple[3][1]) //100

Không giống như list, tuple không thể thay đổi. Điều này có nghĩa là các phần tử của một tuple không thể thay đổi một khi đã được gán. Nhưng, nếu bản thân phần tử đó là một kiểu dữ liệu có thể thay đổi (như list chẳng hạn) thì các phần tử lồng nhau có thể được thay đổi.

Các phần tử trong tuple không thể thay đổi nên chúng ta cũng không thể xóa, loại bỏ phần tử khỏi tuple. Nhưng việc xóa hoàn toàn một tuple có thể thực hiện được với từ khóa del.

del myTuple

Kết luận bài này cũng khá dài rồi, về kiểu biến trong python còn 2 loại nữa là set và dictionary mình xin để lại cho số sau.

Như bài trước thì mình đã đi tìm hiểu qua cơ bản về python và chạy chương trình đầu tiên, chương trình muôn thủa Hello world. Nếu bạn nào chưa xem thì xem tại đây. Còn phần này mình sẽ đi qua về variable trong python. Let's go.

Quy tắc đặt tên biến

Đầu tiên thì chúng ta cùng lướt qua về quy tắc đặt tên biến. Làm gì thì cũng phải biết quy tắc của nó.

Quy tắc:

  • Tên biến chỉ có thể chứa chữ cái, số và dấu gạch dưới _. Có thể bắt đầu bằng chữ cái hoặc dấu gạch dưới, nhưng không được bắt đầu bằng số. VD: bien, _bien nhưng không thể đặt là 1_bien.
  • Khoảng trống (space) không được cho phép khi đặt tên biến, nhưng bạn có thể sử dụng dấu gạch dưới để tách các từ.
  • Khi đặt tên biến không được sử dụng từ khóa Python và tên của hàm. Các từ khóa trong python mà ta không thể đặt cho biến.

Các kiểu dữ liệu trong python

5 kiểu dữ liệu chuẩn trong python là:

  • Number
  • String
  • List
  • Tuple
  • Set
  • Dictionary

Ta có thể sử dụng hàm type() để kiểm tra kiểu dữ liệu của biến

Number

Python hỗ trợ 5 kiểu dữ liệu numbers khác nhau:

  • Int
  • Float
  • Long
  • Complex (số phức)
  • Fraction (số thập phân)

Các phép toán cơ bản với kiểu number: +, -, /, *, %, //, **

String

Bên cạnh số, Python cũng có thể thao tác với chuỗi, được biểu diễn bằng nhiều cách. Chúng có thể được để trong dấu nháy đơn ('...') hoặc kép ("...") ("""...""") với cùng một kết quả. Các chuỗi có thể được lập chỉ mục với số thứ tự của ký thự trong chuỗi.

word = "Python"

word[0] => P

word[0:2] => "Py"

word[2:5] => "tho"

Nối chuỗi sử dụng toán tử +.

List

Trong Python, list được biểu diễn bằng dãy các giá trị, được phân tách nhau bằng dấu phẩy, nằm trong dấu []. Các danh sách có thể chứa nhiều mục với kiểu khác nhau, nhưng thông thường là các mục có cùng kiểu (có vẻ giống array, nhưng khác với array là các phần tử có thể khác kiểu). squares = [1, 4, 9, 16, 25]

list = [1, "Hello", 9.6]

Index (chỉ mục) của list: để truy cập các phần tử của list chúng ta dùng chỉ mục, thứ tự của phần tử trong list để lấy ra. vd:

Các phương thức khác:

Vì các kiểu dữ liêu trên các ngôn ngữ lập trình khác đều có nên mình sẽ viết nó ngắn gọn nhất. Còn 2 kiểu dữ liệu dưới đây là các kiểu dữ liệu chỉ có ở python.

Tuple

Tuple trong Python là một chuỗi các phần tử có thứ tự giống như list. Sự khác biệt giữa list và tuple là chúng ta không thể thay đổi các phần tử trong tuple khi đã gán, nhưng trong list thì các phần tử có thể thay đổi. Tuple thường được sử dụng cho các dữ liệu không cho phép sửa đổi.

Tuple thường được sử dụng cho các phần tử không cùng kiểu dữ liệu (khác nhau) và list thường sử dụng cho các phần tử có cùng kiểu dữ liệu. Việc Tuple không thay đổi khi sử dụng nên khi sử dụng sẽ đảm bảo rằng dữ liệu đó không bị thay đổi.

Tuple được tạo bằng cách đặt tất cả các phần tử của nó trong dấu ngoặc đơn (), phân tách bằng dấu phẩy. Bạn có thể bỏ dấu ngoặc đơn nếu muốn, nhưng nên thêm nó vào cho code rõ ràng hơn.

myTuple = (2, 4, 16, 256)
myTuple = 100, "nghiem tuan", 10.9

Tuple không bị giới hạn số lượng phần tử và có thể có nhiều kiểu dữ liệu khác nhau như số nguyên, số thập phân, list, string,...

Tạo tuple chỉ có một phần tử hơi phức tạp chút, nếu bạn tạo theo cách thông thường là cho phần tử đó vào trong cặp dấu () là chưa đủ, cần phải thêm dấu phẩy để chỉ ra rằng, đây là tuple.

Để truy cập đến từng phần tử trong tuple cũng tương tự như list ta sử dụng []:

myTuple = (2, 4, 16, [50, 100])
print(myTuple[3][1]) //100

Không giống như list, tuple không thể thay đổi. Điều này có nghĩa là các phần tử của một tuple không thể thay đổi một khi đã được gán. Nhưng, nếu bản thân phần tử đó là một kiểu dữ liệu có thể thay đổi (như list chẳng hạn) thì các phần tử lồng nhau có thể được thay đổi.

Các phần tử trong tuple không thể thay đổi nên chúng ta cũng không thể xóa, loại bỏ phần tử khỏi tuple. Nhưng việc xóa hoàn toàn một tuple có thể thực hiện được với từ khóa del.

del myTuple

Kết luận bài này cũng khá dài rồi, về kiểu biến trong python còn 2 loại nữa là set và dictionary mình xin để lại cho số sau.

TL;DR

Use the Wrapper helper from python-varname:

from varname.helpers import Wrapper

foo = Wrapper(dict())

# foo.name == 'foo'
# foo.value == {}
foo.value['bar'] = 2

For list comprehension part, you can do:

n_jobs = Wrapper(<original_value>) 
users = Wrapper(<original_value>) 
queues = Wrapper(<original_value>) 
priorities = Wrapper(<original_value>) 

list_of_dicts = [n_jobs, users, queues, priorities]
columns = [d.name for d in list_of_dicts]
# ['n_jobs', 'users', 'queues', 'priorities']
# REMEMBER that you have to access the <original_value> by d.value

I am the author of the python-varname package. Please let me know if you have any questions or you can submit issues on Github.

The long answer

Is it even possible?

Yes and No.

We are retrieving the variable names at runtime, so we need a function to be called to enable us to access the previous frames to retrieve the variable names. That's why we need a Wrapper there. In that function, at runtime, we are parsing the source code/AST nodes in the previous frames to get the exact variable name.

However, the source code/AST nodes in the previous frames are not always available, or they could be modified by other environments (e.g: pytest's assert statement). One simple example is that the codes run via exec(). Even though we are still able to retrieve some information from the bytecode, it needs too much effort and it is also error-prone.

How to do it?

First of all, we need to identify which frame the variable is given. It's not always simply the direct previous frame. For example, we may have another wrapper for the function:

from varname import varname

def func():
  return varname()

def wrapped():
  return func()

x = wrapped()

In the above example, we have to skip the frame inside wrapped to get to the right frame x = wrapped() so that we are able to locate x. The arguments frame and ignore of varname allow us to skip some of these intermediate frames. See more details in the README file and the API docs of the package.

Then we need to parse the AST node to locate where the variable is assigned value (function call) to. It's not always just a simple assignment. Sometimes there could be complex AST nodes, for example, x = [wrapped()]. We need to identify the correct assignment by traversing the AST tree.

How reliable is it?

Once we identify the assignment node, it is reliable.

varname is all depending on executing package to look for the node. The node executing detects is ensured to be the correct one (see also this).

It partially works with environments where other AST magics apply, including pytest, ipython, macropy, birdseye, reticulate with R, etc. Neither executing nor varname is 100% working with those environments.

Do we need a package to do it?

Well, yes and no, again.

If your scenario is simple, the code provided by @juan Isaza or @scohe001 probably is enough for you to work with the case where a variable is defined at the direct previous frame and the AST node is a simple assignment. You just need to go one frame back and retrieve the information there.

However, if the scenario becomes complicated, or we need to adopt different application scenarios, you probably need a package like python-varname, to handle them. These scenarios may include to:

  1. present more friendly messages when the source code is not available or AST nodes are not accessible
  2. skip intermediate frames (allows the function to be wrapped or called in other intermediate frames)
  3. automatically ignores calls from built-in functions or libraries. For example: x = str(func())
  4. retrieve multiple variable names on the left-hand side of the assignment
  5. etc.

How about the f-string?

Like the answer provided by @Aivar Paalberg. It's definitely fast and reliable. However, it's not at runtime, meaning that you have to know it's foo before you print the name out. But with varname, you don't have to know that variable is coming:

from varname import varname

def func():
  return varname()

# In external uses
x = func() # 'x'
y = func() # 'y'

Finally

python-varname is not only able to detect the variable name from an assignment, but also:

  • Retrieve variable names directly, using nameof
  • Detect next immediate attribute name, using will
  • Fetch argument names/sources passed to a function using argname

Read more from its documentation.

However, the final word I want to say is that, try to avoid using it whenever you can.

Because you can't make sure that the client code will run in an environment where the source node is available or AST node is accessible. And of course, it costs resources to parse the source code, identify the environment, retrieve the AST nodes and evaluate them when needed.